obteniendo el tipo T de IEnumerable <T>

106

¿hay una manera de recuperar el tipo Tde IEnumerable<T>medio de la reflexión?

p.ej

tengo una IEnumerable<Child>información variable ; quiero recuperar el tipo de niño a través de la reflexión

Usman Masood
fuente
1
¿En que contexto? ¿Qué es esto IEnumerable <T>? ¿Es una instancia de objeto enviada como argumento? ¿O que?
Mehrdad Afshari

Respuestas:

142
IEnumerable<T> myEnumerable;
Type type = myEnumerable.GetType().GetGenericArguments()[0]; 

Por lo tanto,

IEnumerable<string> strings = new List<string>();
Console.WriteLine(strings.GetType().GetGenericArguments()[0]);

impresiones System.String.

Consulte MSDN para Type.GetGenericArguments.

Editar: creo que esto abordará las preocupaciones en los comentarios:

// returns an enumeration of T where o : IEnumerable<T>
public IEnumerable<Type> GetGenericIEnumerables(object o) {
    return o.GetType()
            .GetInterfaces()
            .Where(t => t.IsGenericType
                && t.GetGenericTypeDefinition() == typeof(IEnumerable<>))
            .Select(t => t.GetGenericArguments()[0]);
}

Algunos objetos implementan más de un genérico IEnumerablepor lo que es necesario devolver una enumeración de ellos.

Editar: Aunque, debo decir, es una idea terrible que una clase implemente IEnumerable<T>para más de uno T.

jason
fuente
O peor aún, escriba un método con rendimiento e intente llamar a GetType en una variable creada con este método. Le dirá que no es un evento de tipo genérico. Entonces, básicamente, no hay una forma universal de obtener T dada una variable de instancia de tipo IEnumerable <T>
Darin Dimitrov
1
O pruebe con la clase MyClass: IEnumerable <int> {}. Esta clase no tiene una interfaz genérica.
Stefan Steinegger
1
¿Por qué alguien recurriría a obtener los argumentos genéricos y luego tomar el tipo de su indexador? Eso es solo pedir desastre, especialmente cuando el lenguaje admite el tipo de (T) como sugiere @amsprich en su respuesta, que también se puede usar con un tipo genérico o conocido ...
Robert Petz
Esto falla estrepitosamente cuando se usa con consultas linq; el primer argumento genérico de un WhereSelectEnumerableIterator no lo es . Obtiene el argumento genérico del objeto subyacente, no la interfaz en sí.
Pxtl
myEnumerable.GetType (). GetGenericArguments () [0] le da la propiedad FullName que le indica el espacio de nombres.nombre de clase. Si está buscando solo el nombre de la clase, use myEnumerable.GetType (). GetGenericArguments () [0] .Name
user5534263
38

Solo haría un método de extensión. Esto funcionó con todo lo que le lancé.

public static Type GetItemType<T>(this IEnumerable<T> enumerable)
{
    return typeof(T);
}
amsprich
fuente
6
No funcionará si su referencia de tiempo de compilación es solo de tipo objeto.
Stijn Van Antwerpen
27

Tuve un problema similar. La respuesta seleccionada funciona para instancias reales. En mi caso solo tenía un tipo (de a PropertyInfo).

La respuesta seleccionada falla cuando el tipo en sí typeof(IEnumerable<T>)no es una implementación de IEnumerable<T>.

Para este caso funciona lo siguiente:

public static Type GetAnyElementType(Type type)
{
   // Type is Array
   // short-circuit if you expect lots of arrays 
   if (type.IsArray)
      return type.GetElementType();

   // type is IEnumerable<T>;
   if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof (IEnumerable<>))
      return type.GetGenericArguments()[0];

   // type implements/extends IEnumerable<T>;
   var enumType = type.GetInterfaces()
                           .Where(t => t.IsGenericType && 
                                  t.GetGenericTypeDefinition() == typeof(IEnumerable<>))
                           .Select(t => t.GenericTypeArguments[0]).FirstOrDefault();
   return enumType ?? type;
}
Eli Algranti
fuente
Salvó mi día. Para mi caso, agregué una declaración if separada para manejar cadenas, ya que implementa IEnumerable <char>
Edmund P Charumbira
Type.GenericTypeArguments- solo para dotNet FrameWork versión> = 4.5. De lo contrario, utilice Type.GetGenericArgumentsen su lugar.
Кое Кто
20

Si conoce IEnumerable<T>(a través de genéricos), entonces typeof(T)debería funcionar. De lo contrario (para object, o el no genérico IEnumerable), verifique las interfaces implementadas:

        object obj = new string[] { "abc", "def" };
        Type type = null;
        foreach (Type iType in obj.GetType().GetInterfaces())
        {
            if (iType.IsGenericType && iType.GetGenericTypeDefinition()
                == typeof(IEnumerable<>))
            {
                type = iType.GetGenericArguments()[0];
                break;
            }
        }
        if (type != null) Console.WriteLine(type);
Marc Gravell
fuente
3
Algunos objetos implementan más de un IEnumerable genérico.
jason
5
@Jason - y en esos casos, la cuestión de "encontrar la T" ya es una cuestión dudosa; No puedo hacer nada al respecto ...
Marc Gravell
Un pequeño problema para cualquiera que intente usar esto con un Type typeparámetro en lugar de un object objparámetro: no puede simplemente reemplazarobj.GetType() con typeporque si pasa, typeof(IEnumerable<T>)no obtiene nada. Para solucionar esto, pruebe el typepropio para ver si es un genérico de IEnumerable<>y luego sus interfaces.
Ian Mercer
8

Muchas gracias por la discusión. Lo usé como base para la solución a continuación, que funciona bien para todos los casos que me interesan (IEnumerable, clases derivadas, etc.). Pensé que debería compartir aquí en caso de que alguien lo necesite también:

  Type GetItemType(object someCollection)
  {
    var type = someCollection.GetType();
    var ienum = type.GetInterface(typeof(IEnumerable<>).Name);
    return ienum != null
      ? ienum.GetGenericArguments()[0]
      : null;
  }
Bernardo
fuente
Aquí hay una sola línea que hace todo esto usando el operador condicional nulo: someCollection.GetType().GetInterface(typeof(IEnumerable<>).Name)?.GetGenericArguments()?.FirstOrDefault()
Mass Dot Net
2

Solo usa typeof(T)

EDITAR: O use .GetType (). GetGenericParameter () en un objeto instanciado si no tiene T.

rienda
fuente
No siempre tienes a T.
jason
Es cierto, en cuyo caso puede usar .GetType (). Modificaré mi respuesta.
reinicio
2

Una alternativa para situaciones más simples en las que se utilizará una IEnumerable<T>o una Tnota en GenericTypeArgumentslugar de GetGenericArguments().

Type inputType = o.GetType();
Type genericType;
if ((inputType.Name.StartsWith("IEnumerable"))
    && ((genericType = inputType.GenericTypeArguments.FirstOrDefault()) != null)) {

    return genericType;
} else {
    return inputType;
}
Iglesia de Rob
fuente
1

Esta es una mejora de la solución de Eli Algranti, ya que también funcionará cuando el IEnumerable<>tipo esté en cualquier nivel del árbol de herencia.

Esta solución obtendrá el tipo de elemento de any Type. Si el tipo no es unIEnumerable<> , devolverá el tipo pasado. Para objetos, use GetType. Para tipos, use typeof, luego llame a este método de extensión en el resultado.

public static Type GetGenericElementType(this Type type)
{
    // Short-circuit for Array types
    if (typeof(Array).IsAssignableFrom(type))
    {
        return type.GetElementType();
    }

    while (true)
    {
        // Type is IEnumerable<T>
        if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(IEnumerable<>))
        {
            return type.GetGenericArguments().First();
        }

        // Type implements/extends IEnumerable<T>
        Type elementType = (from subType in type.GetInterfaces()
            let retType = subType.GetGenericElementType()
            where retType != subType
            select retType).FirstOrDefault();

        if (elementType != null)
        {
            return elementType;
        }

        if (type.BaseType == null)
        {
            return type;
        }

        type = type.BaseType;
    }
}
Neo
fuente
1

Sé que esto es un poco antiguo, pero creo que este método cubrirá todos los problemas y desafíos indicados en los comentarios. Gracias a Eli Algranti por inspirar mi trabajo.

/// <summary>Finds the type of the element of a type. Returns null if this type does not enumerate.</summary>
/// <param name="type">The type to check.</param>
/// <returns>The element type, if found; otherwise, <see langword="null"/>.</returns>
public static Type FindElementType(this Type type)
{
   if (type.IsArray)
      return type.GetElementType();

   // type is IEnumerable<T>;
   if (ImplIEnumT(type))
      return type.GetGenericArguments().First();

   // type implements/extends IEnumerable<T>;
   var enumType = type.GetInterfaces().Where(ImplIEnumT).Select(t => t.GetGenericArguments().First()).FirstOrDefault();
   if (enumType != null)
      return enumType;

   // type is IEnumerable
   if (IsIEnum(type) || type.GetInterfaces().Any(IsIEnum))
      return typeof(object);

   return null;

   bool IsIEnum(Type t) => t == typeof(System.Collections.IEnumerable);
   bool ImplIEnumT(Type t) => t.IsGenericType && t.GetGenericTypeDefinition() == typeof(IEnumerable<>);
}
Dahall
fuente
1
public static Type GetInnerGenericType(this Type type)
{
  // Attempt to get the inner generic type
  Type innerType = type.GetGenericArguments().FirstOrDefault();

  // Recursively call this function until no inner type is found
  return innerType is null ? type : innerType.GetInnerGenericType();
}

Esta es una función recursiva que profundizará primero en la lista de tipos genéricos hasta que obtenga una definición de tipo concreta sin tipos genéricos internos.

Probé este método con este tipo: ICollection<IEnumerable<ICollection<ICollection<IEnumerable<IList<ICollection<IEnumerable<IActionResult>>>>>>>>

que debería volver IActionResult

Tyler Huskins
fuente
0

typeof(IEnumerable<Foo>). devolverá el primer argumento genérico, en este caso .GetGenericArguments()[0]typeof(Foo)

Daniel Brückner
fuente
0

así es como suelo hacerlo (a través del método de extensión):

public static Type GetIEnumerableUnderlyingType<T>(this T iEnumerable)
    {
        return typeof(T).GetTypeInfo().GetGenericArguments()[(typeof(T)).GetTypeInfo().GetGenericArguments().Length - 1];
    }
H7O
fuente
0

Aquí está mi versión ilegible de expresión de consulta Linq ...

public static Type GetEnumerableType(this Type t) {
    return !typeof(IEnumerable).IsAssignableFrom(t) ? null : (
    from it in (new[] { t }).Concat(t.GetInterfaces())
    where it.IsGenericType
    where typeof(IEnumerable<>)==it.GetGenericTypeDefinition()
    from x in it.GetGenericArguments() // x represents the unknown
    let b = it.IsConstructedGenericType // b stand for boolean
    select b ? x : x.BaseType).FirstOrDefault()??typeof(object);
}

Tenga en cuenta que el método también tiene IEnumerableen cuenta lo no genérico , objecten este caso devuelve , porque toma una Typeinstancia en lugar de una concreta como argumento. Por cierto, ya que x representa lo desconocido , este video me pareció interesante, aunque irrelevante.

Ken Kin
fuente