generics约束,其中T:struct和where T:class
我想区分以下情况:
- 一个普通的值types(例如
int
) - 可以为null的值types(例如
int?
) - 一个引用types(如
string
) – 可选的,我不在乎这是否映射到上面的(1)或(2)
我已经拿出了下面的代码,它适用于案件(1)和(2):
static void Foo<T>(T a) where T : struct { } // 1 static void Foo<T>(T? a) where T : struct { } // 2
但是,如果我试图检测这种情况(3),它不会编译:
static void Foo<T>(T a) where T : class { } // 3
错误消息是types'X'已经使用相同的参数types定义了一个名为'Foo'的成员 。 那么,不知何故,我不能where T : struct
和where T : class
之间做出区别。
如果我删除第三个函数(3),下面的代码不会编译:
int x = 1; int? y = 2; string z = "a"; Foo (x); // OK, calls (1) Foo (y); // OK, calls (2) Foo (z); // error: the type 'string' must be a non-nullable value type ...
我怎样才能得到Foo(z)
编译,映射到上述函数之一(或第三个与另一个约束,我没有想到)?
约束不是签名的一部分,但参数是。 参数中的约束在重载parsing期间被强制执行。
所以让我们把约束放在一个参数中。 这是丑陋的,但它的作品。
class RequireStruct<T> where T : struct { } class RequireClass<T> where T : class { } static void Foo<T>(T a, RequireStruct<T> ignore = null) where T : struct { } // 1 static void Foo<T>(T? a) where T : struct { } // 2 static void Foo<T>(T a, RequireClass<T> ignore = null) where T : class { } // 3
(比从未晚六年?)
不幸的是,您不能仅根据约束来区分方法的types。
所以你需要定义一个不同类或者不同名字的方法。
除了你对Marnix的回答的评论之外,你可以通过一些反思来达到你想要的。
在下面的示例中,不受约束的Foo<T>
方法使用reflection来调用相应的约束方法 – FooWithStruct<T>
或FooWithClass<T>
。 出于性能原因,我们将创build并caching强types的委托,而不是在每次调用Foo<T>
方法时都使用简单的reflection。
int x = 42; MyClass.Foo(x); // displays "Non-Nullable Struct" int? y = 123; MyClass.Foo(y); // displays "Nullable Struct" string z = "Test"; MyClass.Foo(z); // displays "Class" // ... public static class MyClass { public static void Foo<T>(T? a) where T : struct { Console.WriteLine("Nullable Struct"); } public static void Foo<T>(T a) { Type t = typeof(T); Delegate action; if (!FooDelegateCache.TryGetValue(t, out action)) { MethodInfo mi = t.IsValueType ? FooWithStructInfo : FooWithClassInfo; action = Delegate.CreateDelegate(typeof(Action<T>), mi.MakeGenericMethod(t)); FooDelegateCache.Add(t, action); } ((Action<T>)action)(a); } private static void FooWithStruct<T>(T a) where T : struct { Console.WriteLine("Non-Nullable Struct"); } private static void FooWithClass<T>(T a) where T : class { Console.WriteLine("Class"); } private static readonly MethodInfo FooWithStructInfo = typeof(MyClass).GetMethod("FooWithStruct", BindingFlags.NonPublic | BindingFlags.Static); private static readonly MethodInfo FooWithClassInfo = typeof(MyClass).GetMethod("FooWithClass", BindingFlags.NonPublic | BindingFlags.Static); private static readonly Dictionary<Type, Delegate> FooDelegateCache = new Dictionary<Type, Delegate>(); }
(请注意, 这个例子不是线程安全的 ,如果你需要线程安全,那么你需要在对caching字典的所有访问中使用某种locking,或者如果你能够以.NET4为目标的话, ConcurrentDictionary<K,V>
代替。)
在第一个方法上放置struct contraint。 如果需要区分值types和类,则可以使用参数的types来实现。
static void Foo( T? a ) where T : struct { // nullable stuff here } static void Foo( T a ) { if( a is ValueType ) { // ValueType stuff here } else { // class stuff } }
放大我的评论到LukeH,一个有用的模式,如果需要使用reflection来调用基于types参数(与对象实例的types不同)的不同操作是创build一个私有generics静态类,如下所示确切的代码是未经testing的,但我做过这种事情之前):
静态类FooInvoker <T> { public Action <Foo> theAction = configureAction; void ActionForOneKindOfThing <TT>(TT param)其中TT:thatKindOfThing,T { ... } void ActionForAnotherKindOfThing <TT>(TT param)其中TT:thatOtherKindOfThing,T { ... } void configureAction(T param) { ...确定T是哪种东西,并将`theAction`设置为其中的一个 ...以上的方法。 然后结束... theAction(PARAM); } }
请注意,如果在TT
不符合该方法的约束条件时尝试为ActionForOneKindOfThing<TT>(TT param)
创build委托,则Reflection将引发exception。 由于系统在创build委托时validation了TT
的types,因此可以安全地调用该theAction
而无需进一步的types检查。 还要注意,如果外部代码的确如此:
FooInvoker <T> .theAction(PARAM);
只有第一个电话将需要任何反思。 随后的调用将直接调用委托。
如果你不需要generics参数,只想在编译时区分这三种情况,你可以使用下面的代码。
static void Foo(object a) { } // reference type static void Foo<T>(T? a) where T : struct { } // nullable static void Foo(ValueType a) { } // valuetype