2010-06-04 14 views
39

me gustaría diferenciar entre casos siguientes:limitaciones genérico, donde T: struct y donde T: clase

  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 importa si esta asignada a (1) o (2) por encima de

he llegado con la siguiente c oda, que funciona bien para los casos (1) y (2):

static void Foo<T>(T a) where T : struct { } // 1 

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

Sin embargo, si intento de detectar el caso (3) de este tipo, no se compila:

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

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

Si quito la tercera función (3), el siguiente código no se compila ya sea:

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 compilar, la cartografía a una de las funciones anteriores (o un tercero con otra restricción, que no he pensado)?

+0

Para los tipos de referencia se encuentra: new(), sin embargo, esto tiene un comportamiento extraño con los tipos de valor anulables. –

Respuesta

36

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

Así que vamos a poner la restricción en un parámetro. Es feo, pero funciona.

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 

(? Mejores seis años tarde que nunca)

+2

¡Ha, gran idea! De hecho, no es necesario agregar el parámetro 'ignorar' a la segunda función' Foo 'tomando' T? '. –

+0

Esto me dio la oportunidad de blog sobre el tema en http://code.fitness/post/2016/04/generic-type-resolution.html –

+0

Me llegó la idea de [una de las publicaciones del blog de Eric Lippert] (https: //blogs.msdn.microsoft.com/ericlippert/2009/12/10/constraints-are-not-part-of-the-signature/). Siempre me han gustado los chanchullos. En cuanto a la T ?, la situación en la que necesitaba esto solo tenía los casos 1 y 3, y olvidé las pruebas si es necesario. – Alcaro

17

Desafortunadamente, no se puede diferenciar el tipo de método para llamar basado en las limitaciones.

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

+2

+1. Por supuesto, el primero y el segundo trabajo porque 'T' y' T? 'Son argumentos diferentes. ('' T' y anulables ') – Powerlord

+0

1 Véase: http://blogs.msdn.com/b/ericlippert/archive/2009/12/10/constraints-are-not-part-of-the-signature .aspx –

+0

Gracias por su rápida respuesta; si no puedo diferenciar los tipos, ¿hay alguna forma de compilar mi último ejemplo, relajando alguna restricción? –

5

Suelte 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 
     } 
     } 
+2

@Maxim: Gracias. El problema al que me enfrento es que en el método no nulo, tengo que ser capaz de invocar otras funciones que toman y devuelven 'T?', Y esto no es válido sin la restricción 'where T: struct'. –

10

En respuesta a su comentario sobre Marnix's answer, se puede lograr lo que desea mediante el uso de un poco de reflexión.

En el ejemplo siguiente, el método Foo<T> no restringido utiliza el reflejo para agotar las llamadas al método restringido apropiado - FooWithStruct<T> o FooWithClass<T>. Por motivos de rendimiento, crearemos y almacenaremos en caché un delegado fuertemente tipado en lugar de utilizar el reflejo simple cada vez que se llame al método Foo<T>.

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

(Tenga en cuenta que este ejemplo no se multihebra Si necesita hilo de seguridad a continuación, ya sea que usted tiene que utilizar algún tipo de bloqueo alrededor de todo el acceso al diccionario caché, o -. Si' puede apuntar a .NET4 - use ConcurrentDictionary<K,V> en su lugar.)

+1

Podría uno mejorar las cosas mediante el uso de un enfoque similar al 'Comparer .Default', p. crear una clase genérica estática privada 'FooInvoker ' con un campo público 'FooMethod' de tipo' Acción '(ya que' FooInvoker 'sería inaccesible fuera de' MyClass' no habría riesgo de que el código externo abuse del campo público)? Si constructor de la clase de '' FooInvoker conjuntos 'FooMethod' adecuada, creo que eso podría evitar la necesidad de un diccionario de consulta en tiempo de ejecución (no sé si.net necesitaría realizar uno internamente cada vez que se llamara 'Foo '). – supercat

+1

Consulte la respuesta publicada para obtener un resumen de cómo se usaría una clase estática. Probablemente cometí algunos errores de sintaxis, ya que escribo desde la memoria (y sobre todo el programa en vb.net), pero debe haber suficiente esquema para que empieces. – supercat

2

Amplificando mi comentario a LukeH, un patrón útil si uno necesita 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 estática algo como la siguiente (el código exacto no se ha probado, pero he hecho este tipo de cosas antes):

 
static class FooInvoker<T> 
{ 
    public Action<Foo> theAction = configureAction; 
    void ActionForOneKindOfThing<TT>(TT param) where TT:thatKindOfThing,T 
    { 
    ... 
    } 
    void ActionForAnotherKindOfThing<TT>(TT param) where TT:thatOtherKindOfThing,T 
    { 
    ... 
    } 
    void configureAction(T param) 
    { 
    ... Determine which kind of thing T is, and set `theAction` to one of the 
    ... above methods. Then end with ... 
    theAction(param); 
    } 
} 

Tenga en cuenta que la reflexión lanzará una excepción si se intenta crear un delegado para ActionForOneKindOfThing<TT>(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, se puede invocar de forma segura theAction sin más verificación de tipos. Tenga en cuenta también que si el código externo sí lo hace:

 
    FooInvoker<T>.theAction(param); 

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

1

Si usted no necesita parámetros genéricos y sólo quiere diferenciar entre estos 3 casos en tiempo de compilación que puede utilizar el código siguiente.

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