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.