C#高级约束泛型类型

时间:2016-12-19 16:49:12

标签: c# generics

我正在寻找一种解决合成问题的好方法。我想实现一个允许在不同视图之间切换的导航系统。下一个视图应该由其类型TView指定(不是该类型的实例)。然后将使用一些通用参数

初始化视图
public abstract class ViewBase { }

public abstract class ViewBase<T0, T1> : ViewBase 
{
    public abstract void Initialize(T0 arg0, T1 arg1);
}

public static class Navigator
{
    public static void Navigate<TView, T0, T1>(T0 arg0, T1 arg1) where TView : ViewBase<T0, T1>
    {
        var view = (ViewBase<T0, T1>)Activator.CreateInstance<TView>();
        view.Initialize(arg0, arg1);

        /* ... */
    }
}

上面的代码应该可行,但每次调用Navigate时我都不喜欢通用类型的长列表:

Navigate<DerivedView, string, int>("Joe", 42);

假设派生类:

public class DerivedView : ViewBase<string, int> 
{
    /* ... */
}

在genereal中,有可能减少到:

Navigate<DerivedView>("Joe", 42);

因为类型信息是多余的。

你有什么建议吗? 我缺少可变参数模板;)

由于

4 个答案:

答案 0 :(得分:1)

类型推断是全有或全无的。如果指定了任何类型参数,则必须全部指定它们。因此,您需要一种方法:

  • 允许从参数
  • 推断TView
  • 避免将TView作为类型参数
  • 避免将T0T1作为类型参数
  • 找到一种分割类型参数的方法

您可以通过传入工厂函数来指定TView的类型来推断TView

public static void Navigate<TView, T0, T1>(Func<TView> factory, T0 arg0, T1 arg1)
        where TView : ViewBase<T0, T1> {
    var view = factory();
    view.Initialize(arg0, arg1);
    // ...
}

现在,请拨打Navigate<DerivedView>("Joe", 42)

,而不是Navigate(() => new DerivedView(), "Joe", 42)

注意:这要求arg0arg1的类型与TView指定的内容完全匹配。隐式转换不起作用。如果视图派生自ViewBase<int, object>arg1"Hello",那么T1将被推断为类型string,从而导致编译器错误消息。

由于除了构造实例之外,您避免使用TView的类型,因此您也可以通过使用工厂函数避免将TView作为类型参数:

public static void Navigate<T0, T1>(Func<ViewBase<T0, T1>> factory, T0 arg0, T1 arg1) {
    var view = factory();
    view.Initialize(arg0, arg1);
    // ...
}

这也避免了隐式转化的问题,因为T0T1都可以从factory中推断出来。

至于避免T0T1作为类型参数:名称Initialize稍微暗示其功能可能属于构造函数。如果是这种情况,您可以通过将构造留给调用者来简单地将它们作为类型参数来避免:工厂函数可以是() => new DerivedView("Joe", 42)。但是,如果您不是这种情况,如果您真的需要一个由Initialize调用的单独Navigate方法,那么我没有看到任何避免T0和{{{{}}}的选项1}}作为类型参数。

最后,拆分类型参数:

T1

这允许您致电public static class Navigator { public static WithViewHelper<TView> WithView<TView>() where TView : new() => new WithViewHelper<TView>(); public struct WithViewHelper<TView> where TView : new() { } } public static class NavigatorExtensions { public static void Navigate<TView, T0, T1>(this Navigator.WithViewHelper<TView> withViewHelper, T0 arg0, T1 arg1) where TView : ViewBase<T0, T1>, new() { var view = new TView(); view.Initialize(arg0, arg1); } } 。它需要Navigator.WithView<DerivedView>().Navigate("Joe", 42)作为扩展方法,否则无法表达泛型类型约束。隐式转换再次出现问题:Navigate将无法编译:即使Navigator.WithView<DerivedView>().Navigate(null, 42)可转换为null,编译器也不会发现string应该是{{} 1}}。

答案 1 :(得分:0)

我想你想要的是一个归结为:

的约束

class Generic<T> where T : Generic2<T1, T2>并且可以访问您的通用成员签名中的T1,T2。

不幸的是,C#目前不支持。

答案 2 :(得分:0)

除了提出的方法,我想出了从ViewBase类创建对象:

public abstract class ViewBase<T0, T1>
{
    public abstract void Initialize(T0 arg0, T1 arg1);

    public static T Navigate<T>(T0 arg0, T1 arg1) where T : ViewBase<T0, T1>
    {
        var view = Activator.CreateInstance<T>();
        view.Initialize(arg0, arg1);
        return view;
    }
}

这允许以下呼叫:

DerivedView.Navigate<DerivedView>("Joe", 42);

但这也带有冗余代码..或:

ViewBase.Navigate<DerivedView>("Joe", 42); 

答案 3 :(得分:0)

还考虑使用通用工厂。我缓存构造函数调用签名,以便第二次更快地检索。

public static class Factory
{
    //store constructors for type T
    static Dictionary<string, ConstructorInfo> ctors=new Dictionary<string, ConstructorInfo>();

    public static T New<T>(params object[] args)
    {
        var arg_types=args.Select((obj) => obj.GetType());
        var arg_types_names=args.Select((obj) => obj.GetType().Name);

        string key=string.Format("{0}({1})", typeof(T).Name, string.Join(",", arg_types_names);

        if(!ctors.ContainsKey(key))
        {
            // if constructor not found yet, assign it
            var ctor=typeof(T).GetConstructor(arg_types.ToArray());
            if(ctor==null)
            {
                throw new MissingMethodException("Could not find appropriate constructor");
            }
            ctors[key]=ctor;
        }
        // invoke constructor to create a new T
        return (T)ctors[key].Invoke(args);
    }                
}