简体   繁体   English

泛型类型的C#高级约束

[英]C# advanced constrains on generic types

im looking for a nice way to a synthactic problem. 我正在寻找一种解决句法问题的好方法。 I want to realize a navigation system that allows to switch between different Views. 我想实现一个导航系统,允许在不同的视图之间切换。 The next view should be specified by its type TView (not an instance of that type). 下一个视图应由其类型TView指定(而不是该类型的实例)。 The view then will be initialized with some generic arguments 然后,将使用一些通用参数来初始化视图

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

        /* ... */
    }
}

The code above should work, but I don't like the long list of generic types each time I call Navigate: 上面的代码应该可以工作,但是每次调用Navigate时,我都不喜欢一长串的泛型类型:

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

Assuming a derived class: 假设派生类:

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

In genereal it could be possible to reduce the to: 一般而言,可以将其简化为:

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

because the type information is redundant. 因为类型信息是多余的。

Do you have any suggestions? 你有什么建议吗? I'm missing variadic templates ;) 我缺少可变参数模板;)

Thanks 谢谢

Type inference is all or nothing. 类型推断是全有还是全无。 If any type arguments are specified, they must all be specified. 如果指定了任何类型参数,则必须全部指定它们。 Therefore, you need a way to either: 因此,您需要一种方法来执行以下任一操作:

  • allow TView to be inferred from an argument 允许从参数推断TView
  • avoid having TView as a type argument 避免将TView作为类型参数
  • avoid having T0 and T1 as type arguments 避免将T0T1作为类型参数
  • find a way to split the type arguments 找到一种拆分类型参数的方法

You can allow TView to be inferred by passing in a factory function to specify TView 's type: 您可以允许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);
    // ...
}

Now, instead of Navigate<DerivedView>("Joe", 42) , call Navigate(() => new DerivedView(), "Joe", 42) . 现在,代替Navigate<DerivedView>("Joe", 42) ,调用Navigate(() => new DerivedView(), "Joe", 42)

Note: this requires the types of arg0 and arg1 to match exactly to what TView specifies. 注意:这需要arg0arg1的类型与TView指定的完全匹配。 Implicit conversions will not work. 隐式转换将不起作用。 If the view derives from ViewBase<int, object> , and arg1 is "Hello" , then T1 will be inferred as type string , causing a compiler error message. 如果视图是从ViewBase<int, object>派生的,而arg1"Hello" ,则将T1推断为string类型,从而导致编译器错误消息。


Since you avoid using TView 's type other than to construct the instance, you can avoid having TView as a type argument by using a factory function too: 由于除了构造实例之外,避免使用TView的类型,因此也可以通过使用工厂函数来避免将TView作为类型参数:

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

This also avoids the problem with implicit conversions, since both T0 and T1 can be deduced from factory . 由于T0T1都可以从factory推导,因此这也避免了隐式转换的问题。


As for avoiding T0 and T1 as type arguments: the name Initialize somewhat suggests that its functionality may belong in a constructor. 至于避免将T0T1作为类型参数:名称Initialize暗示其功能可能属于构造函数。 If that's the case for you, you can trivially avoid them as type arguments by leaving the construction to the caller: the factory function could be () => new DerivedView("Joe", 42) . 在这种情况下,您可以通过将构造留给调用者来避免使用它们作为类型参数:factory函数可以是() => new DerivedView("Joe", 42) However, if that's not the case for you, if you really need a separate Initialize method that gets called by Navigate , then I don't see any options that avoid T0 and T1 as type arguments. 但是,如果不是这种情况,那么如果您确实需要由Navigate调用的单独的Initialize方法,那么我看不到任何避免将T0T1作为类型参数的选项。


Finally, splitting the type arguments: 最后,拆分类型参数:

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

This allows you to call Navigator.WithView<DerivedView>().Navigate("Joe", 42) . 这使您可以调用Navigator.WithView<DerivedView>().Navigate("Joe", 42) It needs Navigate to be an extension method because otherwise the generic type constraint cannot be expressed. 它需要Navigate作为扩展方法,因为否则无法表达泛型类型约束。 It has problems again with implicit conversions: Navigator.WithView<DerivedView>().Navigate(null, 42) will fail to compile: even though null is convertible to string , the compiler will not figure out that T0 should be string . 隐式转换又遇到了问题: Navigator.WithView<DerivedView>().Navigate(null, 42)将无法编译:即使null可转换为string ,编译器也不会确定T0应该是string

I guess what you want is a constraint that boils down to: 我想您想要的是一个约束,可以归结为:

class Generic<T> where T : Generic2<T1, T2> and have access to T1, T2 in your Generic member signatures. class Generic<T> where T : Generic2<T1, T2>并且可以在您的泛型成员签名中访问T1,T2。

Unfortunately this is not currently supported in C#. 不幸的是,C#当前不支持此功能。

Besides the presented approaches I came up with creating the object from the ViewBase class: 除了提出的方法外,我还想出了从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;
    }
}

this allows the following call: 这允许以下调用:

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

but this also comes with redundant code.. or: 但这也带有冗余代码..或:

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

Consider also using a generic factory. 还考虑使用通用工厂。 I cache the constructor call signatures for faster retrieval the second time. 我缓存了构造函数调用签名,以便第二次更快地进行检索。

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