2008-09-16 11 views
22

ejemplo:Cómo detectar si el tipo es otro tipo genérico

public static void DoSomething<K,V>(IDictionary<K,V> items) { 
    items.Keys.Each(key => { 
     if (items[key] **is IEnumerable<?>**) { /* do something */ } 
     else { /* do something else */ } 
} 

Se puede hacer esto sin necesidad de utilizar la reflexión? ¿Cómo digo IEnumerable en C#? ¿Debo usar IEnumerable ya que IEnumerable <> implementa IEnumerable?

Respuesta

35

The previously accepted answer es agradable pero es un error. Afortunadamente, el error es pequeño. La comprobación de IEnumerable no es suficiente si realmente desea conocer la versión genérica de la interfaz; hay muchas clases que implementan solo la interfaz no genérica. Daré la respuesta en un minuto. Primero, sin embargo, me gustaría señalar que la respuesta aceptada es demasiado complicado, ya que el código siguiente lograría el mismo bajo las circunstancias dadas:

if (items[key] is IEnumerable) 

Esto hace aún más, ya que funciona para cada artículo por separado (y no en su subclase común, V).

Ahora, para la solución correcta. Esto es un poco más complicado ya que hay que tomar el tipo genérico IEnumerable`1 (es decir, el tipo IEnumerable<> con un parámetro de tipo) e inyectar el argumento genérico derecha:

static bool IsGenericEnumerable(Type t) { 
    var genArgs = t.GetGenericArguments(); 
    if (genArgs.Length == 1 && 
      typeof(IEnumerable<>).MakeGenericType(genArgs).IsAssignableFrom(t)) 
     return true; 
    else 
     return t.BaseType != null && IsGenericEnumerable(t.BaseType); 
} 

Puede probar la exactitud de este código con facilidad :

var xs = new List<string>(); 
var ys = new System.Collections.ArrayList(); 
Console.WriteLine(IsGenericEnumerable(xs.GetType())); 
Console.WriteLine(IsGenericEnumerable(ys.GetType())); 

rendimientos:

True 
False 

no se preocupe demasiado por el hecho de que este utiliza la reflexión. Si bien es cierto que esto agrega sobrecarga de tiempo de ejecución, también lo hace el uso del operador is.

Por supuesto, el código anterior está terriblemente restringido y podría ampliarse a un método de aplicación más general, IsAssignableToGenericType. La siguiente implementación es ligeramente incorrecta y la dejo aquí solo para fines históricos. No lo use.En su lugar, James has provided an excellent, correct implementation in his answer.

public static bool IsAssignableToGenericType(Type givenType, Type genericType) { 
    var interfaceTypes = givenType.GetInterfaces(); 

    foreach (var it in interfaceTypes) 
     if (it.IsGenericType) 
      if (it.GetGenericTypeDefinition() == genericType) return true; 

    Type baseType = givenType.BaseType; 
    if (baseType == null) return false; 

    return baseType.IsGenericType && 
     baseType.GetGenericTypeDefinition() == genericType || 
     IsAssignableToGenericType(baseType, genericType); 
} 

falla cuando el genericType es lo mismo que givenType; por la misma razón, se produce un error de tipos anulables, es decir

IsAssignableToGenericType(typeof(List<int>), typeof(List<>)) == false 
IsAssignableToGenericType(typeof(int?), typeof(Nullable<>)) == false 

He creado un gist with a comprehensive suite of test cases.

+0

Esto es exactamente lo que estaba buscando. ¡Gracias! Todavía usa reflexión, pero estaba buscando la sintaxis "typeof (IEnumerable <>)". ¡Gracias! –

+0

¿No hay otra forma de hacer la verificación del tipo, que no sea el uso de "es"? –

+0

la última línea debería leer 'return (baseType.IsGenericType && baseType.GetGenericTypeDefinition() == genericType) || IsAssignableToGenericType (baseType, genericType); 'No puede evitar la recursividad solo porque la clase base es genérica. –

4
if (typeof(IEnumerable).IsAssignableFrom(typeof(V))) { 
+0

Eso usa la reflexión. Funciona ... pero me preguntaba si puede hacerlo sin reflexionar, especialmente cómo dice especificar un tipo genérico no especializado en C#. ¡Gracias! –

+0

No estoy tan seguro. Creo que el tiempo de ejecución tiene suficiente información en el sistema de tipos para poder responder la pregunta IsAssignableFrom sin reflejar el ensamblaje. –

0

No estoy seguro de entender lo que quieres decir aquí. ¿Desea saber si el objeto es de algún tipo genérico o desea probar si es un tipo genérico específico ? ¿O solo quieres saber si es enumerable?

No creo que el primero sea posible. El segundo es definitivamente posible, solo tráelo como cualquier otro tipo. Para el tercero, simplemente pruébelo contra IEnumerable como sugirió.

Además, no puede usar el operador 'es' en los tipos.

// Not allowed 
if (string is Object) 
    Foo(); 
// You have to use 
if (typeof(object).IsAssignableFrom(typeof(string)) 
    Foo(); 

Ver this question about types para más detalles. Tal vez te ayude.

+0

Quiero saber si V en mi ejemplo implementa IEnumerable de algo, y no me importa qué. Debería haber dicho que los elementos [clave] son ​​IEnumerable en mi ejemplo. Gracias por atrapar esto –

1

que haría uso de la sobrecarga:

public static void DoSomething<K,V>(IDictionary<K,V> items) 
    where V : IEnumerable 
{ 
    items.Keys.Each(key => { /* do something */ }); 
} 

public static void DoSomething<K,V>(IDictionary<K,V> items) 
{ 
    items.Keys.Each(key => { /* do something else */ }); 
} 
5

Una palabra de advertencia sobre los tipos genéricos y el uso de IsAssignableFrom() ...

Digamos que tiene lo siguiente:

public class MyListBase<T> : IEnumerable<T> where T : ItemBase 
{ 
} 

public class MyItem : ItemBase 
{ 
} 

public class MyDerivedList : MyListBase<MyItem> 
{ 
} 

Calling IsAssignableFrom del tipo de lista de base o del tipo de lista se deriva devuelve falso, pero claramente MyDerivedList hereda MyListBase<T>. (Una nota rápida para Jeff, genéricos absolutamente debe estar envuelto en un bloque de código o tildes para obtener el <T>, de lo contrario se omite. ¿Esto es así?) El problema se debe al hecho de que MyListBase<MyItem> se trata como un tipo completamente diferente MyListBase<T>. El siguiente artículo podría explicar esto un poco mejor. http://mikehadlow.blogspot.com/2006/08/reflecting-generics.html

En su lugar, pruebe lo siguiente función recursiva:

public static bool IsDerivedFromGenericType(Type givenType, Type genericType) 
    { 
     Type baseType = givenType.BaseType; 
     if (baseType == null) return false; 
     if (baseType.IsGenericType) 
     { 
      if (baseType.GetGenericTypeDefinition() == genericType) return true; 
     } 
     return IsDerivedFromGenericType(baseType, genericType); 
    } 

/EDIT: nuevo puesto de Konrad que toma la recursividad genérica en cuenta, así como las interfaces en el clavo. Muy buen trabajo. :)

/EDIT2: Si se realiza una comprobación sobre si genericType es una interfaz, se podrían obtener beneficios de rendimiento. El cheque puede ser un bloque de si todo el código de la interfaz actual, pero si usted está interesado en utilizar .NET 3.5, un amigo mío ofrece lo siguiente:

public static bool IsAssignableToGenericType(Type givenType, Type genericType) 
    { 
     var interfaces = givenType.GetInterfaces().Where(it => it.IsGenericType).Select(it => it.GetGenericTypeDefinition()); 
     var foundInterface = interfaces.FirstOrDefault(it => it == genericType); 
     if (foundInterface != null) return true; 

     Type baseType = givenType.BaseType; 
     if (baseType == null) return false; 

     return baseType.IsGenericType ? 
      baseType.GetGenericTypeDefinition() == genericType : 
      IsAssignableToGenericType(baseType, genericType); 
    } 
+0

Re nota para Jeff: Esto es por diseño porque Markdown permite HTML en línea. Gracias por su aviso, lo integraré en mi código. –

+0

Rico, de hecho prefiero tu código al mío, pero desafortunadamente, no funciona si 'genericType' es un tipo de interfaz como en cuestión. Sin embargo, esto puede arreglarse fácilmente. –

+0

Consulte la respuesta de James para obtener una mejor respuesta: http://stackoverflow.com/a/1075059/320623 – Joel

0

Yo solía pensar que tal situación puede ser solucionable de una manera similar a @Thomas Danecker de solution, pero la adición de otro de los argumentos de plantilla:

public static void DoSomething<K, V, U>(IDictionary<K,V> items) 
    where V : IEnumerable<U> { /* do something */ } 
public static void DoSomething<K, V>(IDictionary<K,V> items) 
          { /* do something else */ } 

pero me di cuenta ahora que does't trabajo a menos que especifique los argumentos de plantilla del primer método explícitamente. Esto claramente no se personaliza para cada artículo en el diccionario, pero puede ser una especie de solución de pobre.

Estaría muy agradecido si alguien pudiera señalar algo incorrecto que podría haber hecho aquí.

82

Muchas gracias por esta publicación. Quería proporcionar una versión de la solución de Konrad Rudolph que me haya funcionado mejor. Tenía problemas menores con esa versión, sobre todo cuando se prueba si un tipo es un tipo de valor anulable:

public static bool IsAssignableToGenericType(Type givenType, Type genericType) 
{ 
    var interfaceTypes = givenType.GetInterfaces(); 

    foreach (var it in interfaceTypes) 
    { 
     if (it.IsGenericType && it.GetGenericTypeDefinition() == genericType) 
      return true; 
    } 

    if (givenType.IsGenericType && givenType.GetGenericTypeDefinition() == genericType) 
     return true; 

    Type baseType = givenType.BaseType; 
    if (baseType == null) return false; 

    return IsAssignableToGenericType(baseType, genericType); 
} 
+1

¡Excelente trabajo! Aquí hay una versión corta (er) para las personas que prefieren la brevedad: 'bool estático público IsAssignableToGenericType (este Tipo tipo, Tipo genericType) { tipo de retorno.GetInterfaces(). Cualquiera (it => it.IsGenericType && it.GetGenericTypeDefinition () == genericType) || (type.IsGenericType && type.GetGenericTypeDefinition() == genericType) || (type.BaseType! = null && IsAssignableToGenericType (type.BaseType, genericType)); } ' – Moeri

+3

Esto es genial, pero parece que esto sería mucho más rápido si moviera la comparación directa para estar por encima de la comparación de la interfaz si la comparación directa coincide? – Shazwazza

+1

@Moeri Reduzca aún más la eliminación del predicado redundante y el uso de '? .':' bool estático público IsAssignableToGenericType (este tipo Tipo, Tipo genérico) {return new [] {type} .Concat (type.GetTypeInfo(). ImplementedInterfaces). Cualquier (i => i.IsConstructedGenericType && i.GetGenericTypeDefinition() == genérico) || (type.GetTypeInfo(). BaseType? .IsAssignableToGenericType (genérico) ?? false); } ' – HappyNomad

4

Gracias por la gran información. Por conveniencia, he refactorizado esto en un método de extensión y lo redujo a una sola declaración.

public static bool IsAssignableToGenericType(this Type givenType, Type genericType) 
{ 
    return givenType.GetInterfaces().Any(t => t.IsGenericType && t.GetGenericTypeDefinition() == genericType) || 
     givenType.BaseType != null && (givenType.BaseType.IsGenericType && givenType.BaseType.GetGenericTypeDefinition() == genericType || 
             givenType.BaseType.IsAssignableToGenericType(genericType)); 
} 

Ahora se le puede llamar fácilmente con:

sometype.IsAssignableToGenericType (typeof (MyGenericType <>))

+0

+1 para un trazador de líneas –

Cuestiones relacionadas