ottenere il tipo T da IEnumerable <T>


106

c'è un modo per recuperare il tipo Tdalla IEnumerable<T>riflessione?

per esempio

ho una variabile IEnumerable<Child>info; voglio recuperare il tipo di bambino attraverso la riflessione


1
In che contesto? Cos'è questo IEnumerable <T>? È un'istanza di oggetto inviata come argomento? O cosa?
Mehrdad Afshari,

Risposte:


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

Questa convenzione,

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

stampe System.String.

Vedere MSDN per Type.GetGenericArguments.

Modifica: credo che questo risolverà le preoccupazioni nei commenti:

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

Alcuni oggetti implementano più di un generico, IEnumerablequindi è necessario restituirne un'enumerazione.

Modifica: anche se, devo dire, è una pessima idea per una classe da implementare IEnumerable<T>per più di una T.


O ancora peggio scrivi un metodo con rendimenti di rendimento e prova a chiamare GetType su una variabile creata con questo metodo. Ti dirà che non è un evento di tipo generico. Quindi fondamentalmente non esiste un modo universale per ottenere T data una variabile di istanza di tipo IEnumerable <T>
Darin Dimitrov,

1
Oppure prova con la classe MyClass: IEnumerable <int> {}. Questa classe non ha un'interfaccia generica.
Stefan Steinegger

1
Perché qualcuno dovrebbe mai ricorrere a ottenere gli argomenti generici e quindi a prendere il tipo dal suo indicizzatore? Questo è solo chiedere un disastro, specialmente quando il linguaggio supporta il tipo di (T) come suggerisce @amsprich nella sua risposta, che può anche essere usato con un tipo generico o noto ...
Robert Petz

Questo fallisce miseramente se usato con le query linq - il primo argomento generico di un WhereSelectEnumerableIterator non lo è . Stai ottenendo l'argomento generico dell'oggetto sottostante, non l'interfaccia stessa.
Pxtl

myEnumerable.GetType (). GetGenericArguments () [0] ti dà la proprietà FullName che ti dice il namespace.classname. Se stai cercando solo il nome della classe usa myEnumerable.GetType (). GetGenericArguments () [0] .Name
user5534263

38

Creerei solo un metodo di estensione. Ha funzionato con tutto ciò che ho lanciato.

public static Type GetItemType<T>(this IEnumerable<T> enumerable)
{
    return typeof(T);
}

6
Non funzionerà se il riferimento al tempo di compilazione è solo di tipo oggetto.
Stijn Van Antwerpen

27

Ho avuto un problema simile. La risposta selezionata funziona per le istanze effettive. Nel mio caso avevo solo un tipo (da a PropertyInfo).

La risposta selezionata fallisce quando il tipo stesso typeof(IEnumerable<T>)non è un'implementazione di IEnumerable<T>.

In questo caso i seguenti lavori:

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

Mi ha salvato la giornata. Per il mio caso, ho aggiunto un'istruzione if separata per gestire le stringhe poiché implementa IEnumerable <char>
Edmund P Charumbira

Type.GenericTypeArguments- solo per dotNet FrameWork versione> = 4.5. Altrimenti, usa Type.GetGenericArgumentsinvece.
Кое Кто

20

Se conosci il IEnumerable<T>(tramite generici), allora typeof(T)dovrebbe funzionare. Altrimenti (per object, o non generico IEnumerable), controlla le interfacce implementate:

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

3
Alcuni oggetti implementano più di un IEnumerable generico.
jason

5
@ Jason - e in quei casi, la domanda "trova la T" è già una domanda dubbia; Non posso farci niente ...
Marc Gravell

Un piccolo trucco per chiunque cerchi di usarlo con un Type typeparametro piuttosto che un object objparametro: non puoi semplicemente sostituire obj.GetType()con typeperché se passi typeof(IEnumerable<T>)non ottieni nulla. Per ovviare a questo, prova lo typestesso per vedere se è un generico di IEnumerable<>e quindi le sue interfacce.
Ian Mercer

8

Grazie mille per la discussione. L'ho usato come base per la soluzione di seguito, che funziona bene per tutti i casi che mi interessano (IEnumerable, classi derivate, ecc.). Ho pensato di condividere qui nel caso qualcuno ne avesse bisogno anche:

  Type GetItemType(object someCollection)
  {
    var type = someCollection.GetType();
    var ienum = type.GetInterface(typeof(IEnumerable<>).Name);
    return ienum != null
      ? ienum.GetGenericArguments()[0]
      : null;
  }

Ecco un one-liner che fa tutto questo utilizzando l'operatore condizionale nullo: someCollection.GetType().GetInterface(typeof(IEnumerable<>).Name)?.GetGenericArguments()?.FirstOrDefault()
Mass Dot Net

2

Basta usare typeof(T)

EDIT: Oppure usa .GetType (). GetGenericParameter () su un oggetto istanziato se non hai T.


Non hai sempre T.
jason

Vero, nel qual caso puoi usare .GetType (). Modificherò la mia risposta.
redini

2

Un'alternativa per situazioni più semplici in cui sarà un IEnumerable<T>o T- nota l'uso di GenericTypeArgumentsinvece di GetGenericArguments().

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

    return genericType;
} else {
    return inputType;
}

1

Questo è un miglioramento rispetto alla soluzione di Eli Algranti in quanto funzionerà anche dove il IEnumerable<>tipo è a qualsiasi livello dell'albero dell'ereditarietà.

Questa soluzione otterrà il tipo di elemento da any Type. Se il tipo non è an IEnumerable<>, restituirà il tipo passato. Per gli oggetti, utilizzare GetType. Per i tipi, usa typeof, quindi chiama questo metodo di estensione sul risultato.

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

1

So che è un po 'vecchio, ma credo che questo metodo coprirà tutti i problemi e le sfide dichiarati nei commenti. Ringraziamo Eli Algranti per aver ispirato il mio lavoro.

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

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

Questa è una funzione ricorsiva che scenderà prima in profondità nell'elenco dei tipi generici fino a ottenere una definizione di tipo concreta senza tipi generici interni.

Ho provato questo metodo con questo tipo: ICollection<IEnumerable<ICollection<ICollection<IEnumerable<IList<ICollection<IEnumerable<IActionResult>>>>>>>>

che dovrebbe tornare IActionResult



0

questo è il modo in cui lo faccio di solito (tramite il metodo di estensione):

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

0

Ecco la mia versione illeggibile dell'espressione di query 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);
}

Nota che il metodo prende IEnumerablein considerazione anche il non generico , restituisce objectin questo caso, perché Typecome argomento prende un'istanza piuttosto che un'istanza concreta. A proposito, poiché x rappresenta l'ignoto , ho trovato questo video interessante, anche se irrilevante ..

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.