繁体   English   中英

泛型类型的C#高级约束

[英]C# advanced constrains on generic types

我正在寻找一种解决句法问题的好方法。 我想实现一个导航系统,允许在不同的视图之间切换。 下一个视图应由其类型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> 
{
    /* ... */
}

一般而言,可以将其简化为:

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

因为类型信息是多余的。

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

谢谢

类型推断是全有还是全无。 如果指定了任何类型参数,则必须全部指定它们。 因此,您需要一种方法来执行以下任一操作:

  • 允许从参数推断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暗示其功能可能属于构造函数。 在这种情况下,您可以通过将构造留给调用者来避免使用它们作为类型参数:factory函数可以是() => new DerivedView("Joe", 42) 但是,如果不是这种情况,那么如果您确实需要由Navigate调用的单独的Initialize方法,那么我看不到任何避免将T0T1作为类型参数的选项。


最后,拆分类型参数:

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 ,编译器也不会确定T0应该是string

我想您想要的是一个约束,可以归结为:

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

不幸的是,C#当前不支持此功能。

除了提出的方法外,我还想出了从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); 

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

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);
    }                
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM