obtenir le type T de IEnumerable <T>


106

existe-t-il un moyen de récupérer le type à Tpartir IEnumerable<T>de la réflexion?

par exemple

j'ai une IEnumerable<Child>info variable ; je veux récupérer le type de l'enfant par réflexion


1
Dans quel contexte? Qu'est-ce que c'est IEnumerable <T>? Est-ce une instance d'objet envoyée en tant qu'argument? Ou quoi?
Mehrdad Afshari

Réponses:


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

Ainsi,

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

impressions System.String.

Voir MSDN pour Type.GetGenericArguments.

Edit: Je pense que cela répondra aux préoccupations dans les commentaires:

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

Certains objets implémentent plusieurs génériques IEnumerable, il est donc nécessaire d'en renvoyer une énumération.

Edit: Bien que, je dois dire, c'est une idée terrible pour une classe à implémenter IEnumerable<T>pour plus d'un T.


Ou pire encore, écrivez une méthode avec des retours de rendement et essayez d'appeler GetType sur une variable créée avec cette méthode. Cela vous dira qu'il ne s'agit pas d'un événement de type générique. Donc, fondamentalement, il n'y a pas de moyen universel d'obtenir T étant donné une variable d'instance de type IEnumerable <T>
Darin Dimitrov

1
Ou essayez avec la classe MyClass: IEnumerable <int> {}. Cette classe n'a pas d'interface générique.
Stefan Steinegger

1
Pourquoi quelqu'un recourrait-il à obtenir les arguments génériques et à récupérer ensuite le type de son indexeur? C'est juste demander un désastre, surtout lorsque le langage prend en charge typeof (T) comme @amsprich le suggère dans sa réponse, qui peut également être utilisé avec un type générique ou connu ...
Robert Petz

Cela échoue lamentablement lorsqu'il est utilisé avec des requêtes linq - le premier argument générique d'un WhereSelectEnumerableIterator ne l' est pas . Vous obtenez l'argument générique de l'objet sous-jacent, pas l'interface elle-même.
Pxtl

myEnumerable.GetType (). GetGenericArguments () [0] vous donne la propriété FullName qui vous indique le namespace.classname. Si vous recherchez uniquement le nom de la classe, utilisez myEnumerable.GetType (). GetGenericArguments () [0] .Name
user5534263

38

Je ferais juste une méthode d'extension. Cela a fonctionné avec tout ce que je lui ai lancé.

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

6
Cela ne fonctionnera pas si votre référence au moment de la compilation est simplement de type objet.
Stijn Van Antwerpen

27

J'avais un problème similaire. La réponse sélectionnée fonctionne pour les instances réelles. Dans mon cas, je n'avais qu'un type (de a PropertyInfo).

La réponse sélectionnée échoue lorsque le type lui-même n'est typeof(IEnumerable<T>)pas une implémentation de IEnumerable<T>.

Pour ce cas, les travaux suivants:

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

J'ai sauvé ma journée. Pour mon cas, j'ai ajouté une instruction if distincte pour gérer les chaînes car elle implémente IEnumerable <char>
Edmund P Charumbira

Type.GenericTypeArguments- uniquement pour la version dotNet FrameWork> = 4.5. Sinon, utilisez Type.GetGenericArgumentsplutôt.
Кое Кто

20

Si vous connaissez le IEnumerable<T>(via les génériques), alors cela typeof(T)devrait fonctionner. Sinon (pour object, ou le non générique IEnumerable), vérifiez les interfaces implémentées:

        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
Certains objets implémentent plus d'un IEnumerable générique.
jason

5
@Jason - et dans ces cas, la question de "trouver le T" est déjà une question douteuse; Je ne peux rien faire à ce sujet ...
Marc Gravell

Un petit piège pour quiconque essaie d'utiliser ceci avec un Type typeparamètre plutôt qu'un object objparamètre: vous ne pouvez pas simplement remplacer obj.GetType()par typeparce que si vous passez, typeof(IEnumerable<T>)vous n'obtenez rien. Pour contourner cela, testez le typelui - même pour voir s'il s'agit d'un générique de IEnumerable<>, puis de ses interfaces.
Ian Mercer

8

Merci beaucoup pour la discussion. Je l'ai utilisé comme base pour la solution ci-dessous, qui fonctionne bien pour tous les cas qui m'intéressent (IEnumerable, classes dérivées, etc.). J'ai pensé que je devrais partager ici au cas où quelqu'un en aurait également besoin:

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

Voici un one-liner qui fait tout cela en utilisant l'opérateur conditionnel nul: someCollection.GetType().GetInterface(typeof(IEnumerable<>).Name)?.GetGenericArguments()?.FirstOrDefault()
Mass Dot Net

2

Juste utiliser typeof(T)

EDIT: Ou utilisez .GetType (). GetGenericParameter () sur un objet instancié si vous n'avez pas T.


Vous n'avez pas toujours T.
jason

True, auquel cas vous pouvez utiliser .GetType (). Je modifierai ma réponse.
rein

2

Une alternative pour les situations plus simples où ce sera soit une utilisation IEnumerable<T>ou T- notez l'utilisation de GenericTypeArgumentsau lieu de GetGenericArguments().

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

    return genericType;
} else {
    return inputType;
}

1

Il s'agit d'une amélioration par rapport à la solution d'Eli Algranti en ce sens qu'elle fonctionnera également là où le IEnumerable<> type se trouve à n'importe quel niveau de l'arborescence d'héritage.

Cette solution obtiendra le type d'élément de any Type. Si le type n'est pas an IEnumerable<>, il renverra le type passé. Pour les objets, utilisez GetType. Pour les types, utilisez typeof, puis appelez cette méthode d'extension sur le résultat.

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

Je sais que c'est un peu vieux, mais je pense que cette méthode couvrira tous les problèmes et défis mentionnés dans les commentaires. Merci à Eli Algranti pour avoir inspiré mon travail.

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

Il s'agit d'une fonction récursive qui ira en profondeur d'abord dans la liste des types génériques jusqu'à ce qu'elle obtienne une définition de type concrète sans types génériques internes.

J'ai testé cette méthode avec ce type: ICollection<IEnumerable<ICollection<ICollection<IEnumerable<IList<ICollection<IEnumerable<IActionResult>>>>>>>>

qui devrait revenir IActionResult



0

voici comment je le fais habituellement (via la méthode d'extension):

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

0

Voici ma version illisible de l'expression de requête 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);
}

Notez que la méthode prend également IEnumerableen compte le non générique , elle renvoie objectdans ce cas, car elle prend une Typeinstance plutôt qu'une instance concrète comme argument. Au fait, pour x représente l'inconnu , j'ai trouvé cette vidéo insteresting, bien que ce ne soit pas pertinent.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.