Restricciones genéricas, donde T: struct y donde T: clase

Me gustaría diferenciar entre los siguientes casos:

  1. Un tipo de valor simple (por ejemplo, int )
  2. Un tipo de valor anulable (por ejemplo, int? )
  3. Un tipo de referencia (por ejemplo, string ) – opcionalmente, no me importaría si esto se asigna a (1) o (2) arriba

He encontrado el siguiente código, que funciona bien para los casos (1) y (2):

 static void Foo(T a) where T : struct { } // 1 static void Foo(T? a) where T : struct { } // 2 

Sin embargo, si trato de detectar el caso (3) de esta manera, no comstack:

 static void Foo(T a) where T : class { } // 3 

El mensaje de error es Tipo ‘X’ ya define un miembro llamado ‘Foo’ con los mismos tipos de parámetros . Bueno, de alguna manera no puedo hacer una diferencia entre where T : struct y where T : class .

Si elimino la tercera función (3), el siguiente código tampoco comstack:

 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 ... 

¿Cómo puedo obtener Foo(z) para comstackr, asignándolo a una de las funciones anteriores (o una tercera con otra restricción, que no he pensado)?

Las restricciones no son parte de la firma, pero los parámetros sí lo son. Y las restricciones en los parámetros se aplican durante la resolución de sobrecarga.

Pongamos la restricción en un parámetro. Es feo, pero funciona.

 class RequireStruct where T : struct { } class RequireClass where T : class { } static void Foo(T a, RequireStruct ignore = null) where T : struct { } // 1 static void Foo(T? a) where T : struct { } // 2 static void Foo(T a, RequireClass ignore = null) where T : class { } // 3 

(¿Mejor seis años tarde que nunca?)

Por desgracia, no se puede diferenciar el tipo de método para llamar basado en las restricciones.

Por lo tanto, necesita definir un método en una clase diferente o con un nombre diferente en su lugar.

Además de su comentario sobre la respuesta de Marnix , puede lograr lo que quiere utilizando un poco de reflexión.

En el ejemplo siguiente, el método Foo no restringido utiliza la reflexión para FooWithStruct llamadas al método restringido apropiado, ya sea FooWithStruct o FooWithClass . Por motivos de rendimiento, crearemos y almacenaremos en caché un delegado fuertemente tipado en lugar de utilizar el reflection simple cada vez que se llame al método Foo .

 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? a) where T : struct { Console.WriteLine("Nullable Struct"); } public static void Foo(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), mi.MakeGenericMethod(t)); FooDelegateCache.Add(t, action); } ((Action)action)(a); } private static void FooWithStruct(T a) where T : struct { Console.WriteLine("Non-Nullable Struct"); } private static void FooWithClass(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 FooDelegateCache = new Dictionary(); } 

(Tenga en cuenta que este ejemplo no es inseguro . Si necesita seguridad de subprocesos, deberá usar algún tipo de locking en todo el acceso al diccionario de la memoria caché o, si puede usar .NET4, use ConcurrentDictionary lugar)

Deja caer el struct contraint en el primer método. Si necesita diferenciar entre tipos de valores y clases, puede usar el tipo de argumento para hacerlo.

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

Amplificando mi comentario a LukeH, un patrón útil si uno necesitaría usar Reflection para invocar diferentes acciones basadas en un parámetro de tipo (a diferencia del tipo de una instancia de objeto) es crear una clase privada genérica privada algo así como la siguiente ( el código exacto no se ha probado, pero he hecho este tipo de cosas antes):

 clase estática FooInvoker 
 {
   public Action  theAction = configureAction;
   void ActionForOneKindOfThing  (TT param) donde TT: thatKindOfThing, T
   {
     ...
   }
   void ActionForAnotherKindOfThing  (TT param) donde TT: thatOtherKindOfThing, T
   {
     ...
   }
   void configureAction (T param)
   {
     ... Determine qué tipo de cosa es T y establezca `theAction` en una de las
     ... métodos anteriores.  Entonces termina con ...
     theAction (param);
   }
 }

Tenga en cuenta que Reflection lanzará una excepción si intenta crear un delegado para ActionForOneKindOfThing(TT param) cuando TT no cumple con las restricciones de ese método. Debido a que el sistema validó el tipo de TT cuando se creó el delegado, uno puede invocar de forma segura la theAction sin más comprobación de tipos. Tenga en cuenta también que si el código externo hace:

   FooInvoker  .theAction (param);

solo la primera llamada requerirá Reflexión. Las llamadas posteriores simplemente invocarán al delegado directamente.

Si no necesita parámetros generics y solo desea diferenciar entre estos 3 casos en tiempo de comstackción, puede usar el siguiente código.

 static void Foo(object a) { } // reference type static void Foo(T? a) where T : struct { } // nullable static void Foo(ValueType a) { } // valuetype