obtendo tipo T de IEnumerable <T>

106

existe uma maneira de recuperar o tipo Tpor IEnumerable<T>meio da reflexão?

por exemplo

eu tenho uma IEnumerable<Child>informação variável ; eu quero recuperar o tipo de criança através da reflexão

Usman Masood
fonte
1
Em que contexto? O que é isso IEnumerable <T>? É uma instância de objeto enviada como um argumento? Ou o que?
Mehrdad Afshari

Respostas:

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

Assim,

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

impressões System.String.

Consulte MSDN para Type.GetGenericArguments.

Edit: Acredito que isso resolverá as preocupações nos comentários:

// 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]);
}

Alguns objetos implementam mais de um genérico, IEnumerableportanto, é necessário retornar uma enumeração deles.

Edit: Embora, devo dizer, seja uma ideia terrível para uma classe implementar IEnumerable<T>para mais de um T.

Jason
fonte
Ou pior ainda, escreva um método com retornos de rendimento e tente chamar GetType em uma variável criada com esse método. Ele dirá que não é um tipo de evento genérico. Então, basicamente, não há uma maneira universal de obter T dada uma variável de instância do tipo IEnumerable <T>
Darin Dimitrov
1
Ou tente com a classe MyClass: IEnumerable <int> {}. Esta classe não possui uma interface genérica.
Stefan Steinegger
1
Por que alguém iria recorrer a obter os argumentos genéricos e, em seguida, obter o tipo de seu indexador? Isso é apenas pedir para o desastre, especialmente quando a linguagem suporta typeof (T) como @amsprich sugere em sua resposta, que também pode ser usado com um tipo genérico ou conhecido ...
Robert Petz
Isso falha miseravelmente quando usado com consultas linq - o primeiro argumento genérico de um WhereSelectEnumerableIterator não é . Você está obtendo o argumento genérico do objeto subjacente, não a própria interface.
Pxtl
myEnumerable.GetType (). GetGenericArguments () [0] fornece a propriedade FullName que informa o namespace.classname. Se você estiver procurando apenas o nome da classe, use myEnumerable.GetType (). GetGenericArguments () [0] .Name
user5534263
38

Eu acabaria de fazer um método de extensão. Isso funcionou com tudo que eu joguei nele.

public static Type GetItemType<T>(this IEnumerable<T> enumerable)
{
    return typeof(T);
}
Amsprich
fonte
6
Não funcionará se sua referência de tempo de compilação for apenas do tipo objeto.
Stijn Van Antwerpen
27

Eu tive um problema parecido. A resposta selecionada funciona para instâncias reais. No meu caso, tinha apenas um tipo (de a PropertyInfo).

A resposta selecionada falha quando o próprio tipo typeof(IEnumerable<T>)não é uma implementação de IEnumerable<T>.

Para este caso, funciona o seguinte:

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
fonte
Salvou meu dia. Para o meu caso, adicionei uma instrução if separada para manipular strings, uma vez que implementa IEnumerable <char>
Edmund P Charumbira
Type.GenericTypeArguments- apenas para dotNet FrameWork version> = 4.5. Caso contrário - use em seu Type.GetGenericArgumentslugar.
Кое Кто
20

Se você conhece o IEnumerable<T>(via genéricos), então typeof(T)deve funcionar. Caso contrário (para object, ou não genérico IEnumerable), verifique as 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
fonte
3
Alguns objetos implementam mais de um IEnumerable genérico.
jason
5
@Jason - e nesses casos, a questão de "encontrar o T" já é uma questão duvidosa; Não posso fazer nada a respeito ...
Marc Gravell
Uma pequena pegadinha para qualquer um que tente usar isso com um Type typeparâmetro em vez de um object objparâmetro: você não pode simplesmente substituir obj.GetType()por typeporque, se você passar, typeof(IEnumerable<T>)não receberá nada. Para contornar isso, teste o typepróprio para ver se é um genérico de IEnumerable<>e, em seguida, suas interfaces.
Ian Mercer,
8

Muito obrigado pela discussão. Usei-o como base para a solução abaixo, que funciona bem para todos os casos que me interessam (IEnumerable, classes derivadas, etc). Achei que deveria compartilhar aqui, caso alguém precise também:

  Type GetItemType(object someCollection)
  {
    var type = someCollection.GetType();
    var ienum = type.GetInterface(typeof(IEnumerable<>).Name);
    return ienum != null
      ? ienum.GetGenericArguments()[0]
      : null;
  }
Bernardo
fonte
Aqui está um one-liner que faz tudo isso usando o operador condicional nulo: someCollection.GetType().GetInterface(typeof(IEnumerable<>).Name)?.GetGenericArguments()?.FirstOrDefault()
Mass Dot Net de
2

Apenas use typeof(T)

EDIT: Ou use .GetType (). GetGenericParameter () em um objeto instanciado se você não tiver T.

rédea
fonte
Você nem sempre tem T.
jason
Verdadeiro, nesse caso você pode usar .GetType (). Vou modificar minha resposta.
reinará
2

Uma alternativa para situações mais simples em que será um IEnumerable<T>ou T- observe o uso de em GenericTypeArgumentsvez de GetGenericArguments().

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

    return genericType;
} else {
    return inputType;
}
Rob Church
fonte
1

Esta é uma melhoria na solução de Eli Algranti, pois também funcionará onde o IEnumerable<>tipo está em qualquer nível na árvore de herança.

Esta solução obterá o tipo de elemento de qualquer Type. Se o tipo não for um IEnumerable<>, ele retornará o tipo passado. Para objetos, use GetType. Para tipos, use typeofe chame este método de extensão no 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
fonte
1

Eu sei que isso é um pouco antigo, mas acredito que este método irá cobrir todos os problemas e desafios declarados nos comentários. Os meus agradecimentos a Eli Algranti por inspirar o meu trabalho.

/// <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
fonte
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 é uma função recursiva que irá aprofundar primeiro na lista de tipos genéricos até obter uma definição de tipo concreto sem tipos genéricos internos.

Testei este método com este tipo: ICollection<IEnumerable<ICollection<ICollection<IEnumerable<IList<ICollection<IEnumerable<IActionResult>>>>>>>>

que deve retornar IActionResult

Tyler Huskins
fonte
0

typeof(IEnumerable<Foo>). retornará o primeiro argumento genérico - neste caso .GetGenericArguments()[0]typeof(Foo)

Daniel Brückner
fonte
0

é assim que eu costumo fazer (via método de extensão):

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

Esta é a minha versão de expressão de consulta Linq ilegível.

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

Observe que o método também leva IEnumerableem consideração o não genérico , ele retorna objectneste caso, porque leva uma Typeinstância em vez de uma instância concreta como o argumento. A propósito, pois x representa o desconhecido , achei este vídeo interessante , embora seja irrelevante.

Ken Kin
fonte