generics约束,其中T:struct和where T:class

我想区分以下情况:

  1. 一个普通的值types(例如int
  2. 可以为null的值types(例如int?
  3. 一个引用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 : structwhere 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