pobieranie typu T z IEnumerable <T>

106

czy jest jakiś sposób, aby odzyskać typ Tod IEnumerable<T>poprzez odbicie?

na przykład

mam zmienne IEnumerable<Child>informacje; chcę odzyskać typ dziecka poprzez refleksję

Usman Masood
źródło
1
W jakim kontekście? Co to za IEnumerable <T>? Czy jest to instancja obiektu wysłana jako argument? Albo co?
Mehrdad Afshari,

Odpowiedzi:

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

W ten sposób

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

wydruki System.String.

Zobacz MSDN dla Type.GetGenericArguments.

Edycja: wierzę, że to rozwiąże obawy w komentarzach:

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

Niektóre obiekty implementują więcej niż jeden rodzaj, IEnumerablewięc konieczne jest zwrócenie ich wyliczenia.

Edycja: Chociaż, muszę powiedzieć, to okropny pomysł, aby zaimplementować klasę IEnumerable<T>dla więcej niż jednej T.

Jason
źródło
Albo jeszcze gorzej, napisz metodę, która zwraca yield i spróbuj wywołać GetType na zmiennej utworzonej tą metodą. Poinformuje Cię, że nie jest to zdarzenie typu ogólnego. Więc w zasadzie nie ma uniwersalnego sposobu, aby uzyskać T, biorąc pod uwagę zmienną instancji typu IEnumerable <T>
Darin Dimitrov
1
Lub spróbuj z klasą MyClass: IEnumerable <int> {}. Ta klasa nie ma ogólnego interfejsu.
Stefan Steinegger
1
Dlaczego ktoś miałby kiedykolwiek uciekać się do pobierania ogólnych argumentów, a następnie pobierania typu z jego indeksatora? To tylko prośba o katastrofę, zwłaszcza gdy język obsługuje typ (T), jak sugeruje @amsprich w swojej odpowiedzi, której można również użyć z typem ogólnym lub znanym ...
Robert Petz
To kończy się niepowodzeniem, gdy jest używane z zapytaniami linq - pierwszy argument ogólny WhereSelectEnumerableIterator nie jest . Otrzymujesz ogólny argument obiektu bazowego, a nie sam interfejs.
Pxtl
myEnumerable.GetType (). GetGenericArguments () [0] udostępnia właściwość FullName, która określa namespace.classname. Jeśli szukasz tylko nazwy klasy, użyj myEnumerable.GetType (). GetGenericArguments () [0] .Name
user5534263
38

Zrobiłbym tylko metodę rozszerzenia. To działało ze wszystkim, co w to rzuciłem.

public static Type GetItemType<T>(this IEnumerable<T> enumerable)
{
    return typeof(T);
}
amsprich
źródło
6
Nie zadziała, jeśli odniesienie czasu kompilacji jest tylko obiektem typu.
Stijn Van Antwerpen
27

Miałem podobny problem. Wybrana odpowiedź działa w rzeczywistych instancjach. W moim przypadku miałem tylko typ (od aPropertyInfo ).

Wybrana odpowiedź nie powiedzie się, jeśli sam typ typeof(IEnumerable<T>)nie jest implementacją IEnumerable<T>.

W tym przypadku działa:

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
źródło
Uratowałem mój dzień. W moim przypadku dodałem oddzielną instrukcję if do obsługi ciągów, ponieważ implementuje ona IEnumerable <char>
Edmund P Charumbira
Type.GenericTypeArguments- tylko dla dotNet FrameWork w wersji> = 4.5. W przeciwnym razie - użyj Type.GetGenericArgumentszamiast tego.
Кое Кто
20

Jeśli znasz IEnumerable<T>(przez generics), to typeof(T)powinno działać. W przeciwnym razie (dla objectlub nieogólne IEnumerable) sprawdź zaimplementowane interfejsy:

        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
źródło
3
Niektóre obiekty implementują więcej niż jeden ogólny IEnumerable.
jason
5
@Jason - iw tych przypadkach kwestia „znajdź T” jest już wątpliwym pytaniem; Nic nie mogę na to poradzić ...
Marc Gravell
Jedna mała pułapka dla każdego, kto próbuje użyć tego z Type typeparametrem, a nie z object objparametrem: nie możesz po prostu zastąpić obj.GetType()go, typeponieważ jeśli przekażesz typeof(IEnumerable<T>), nic nie otrzymasz. Aby to obejść, przetestuj typesamą siebie, aby zobaczyć, czy jest to generyczna, IEnumerable<>a następnie jej interfejsy.
Ian Mercer
8

Bardzo dziękuję za rozmowę. Użyłem go jako podstawy do poniższego rozwiązania, które działa dobrze we wszystkich interesujących mnie przypadkach (IEnumerable, klasy pochodne itp.). Pomyślałem, że powinienem podzielić się tutaj na wypadek, gdyby ktoś również tego potrzebował:

  Type GetItemType(object someCollection)
  {
    var type = someCollection.GetType();
    var ienum = type.GetInterface(typeof(IEnumerable<>).Name);
    return ienum != null
      ? ienum.GetGenericArguments()[0]
      : null;
  }
Bernardo
źródło
Oto jednowierszowy, który robi to wszystko przy użyciu zerowego operatora warunkowego: someCollection.GetType().GetInterface(typeof(IEnumerable<>).Name)?.GetGenericArguments()?.FirstOrDefault()
Mass Dot Net
2

Po prostu użyj typeof(T)

EDYCJA: Lub użyj .GetType (). GetGenericParameter () na utworzonym obiekcie, jeśli nie masz T.

wodza
źródło
Nie zawsze masz T.
jason
To prawda, w takim przypadku możesz użyć .GetType (). Zmodyfikuję odpowiedź.
wodnik
2

Alternatywa dla prostszych sytuacji, w których będzie to użycie IEnumerable<T>lub T- uwaga GenericTypeArgumentszamiast zamiast GetGenericArguments().

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

    return genericType;
} else {
    return inputType;
}
Rob Church
źródło
1

Jest to ulepszenie rozwiązania Eli Algranti, ponieważ będzie działać również tam, gdzie IEnumerable<> typ znajduje się na dowolnym poziomie w drzewie dziedziczenia.

To rozwiązanie pozwoli uzyskać typ elementu z dowolnego Type. Jeśli typ nie jest typu IEnumerable<>, zwróci przekazany typ. W przypadku obiektów użyj GetType. W przypadku typów użyj typeof, a następnie wywołaj tę metodę rozszerzenia w wyniku.

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
źródło
1

Wiem, że to trochę stare, ale uważam, że ta metoda obejmie wszystkie problemy i wyzwania wymienione w komentarzach. Podziękowania dla Eli Algranti za inspirację do mojej pracy.

/// <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
źródło
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();
}

Jest to funkcja rekurencyjna, która przejdzie najpierw w dół listy typów ogólnych, aż uzyska definicję konkretnego typu bez wewnętrznych typów ogólnych.

Testowałem tę metodę z tym typem: ICollection<IEnumerable<ICollection<ICollection<IEnumerable<IList<ICollection<IEnumerable<IActionResult>>>>>>>>

który powinien wrócić IActionResult

Tyler Huskins
źródło
0

tak to zwykle robię (metodą rozszerzenia):

public static Type GetIEnumerableUnderlyingType<T>(this T iEnumerable)
    {
        return typeof(T).GetTypeInfo().GetGenericArguments()[(typeof(T)).GetTypeInfo().GetGenericArguments().Length - 1];
    }
H7O
źródło
0

Oto moja nieczytelna wersja wyrażenia zapytania 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);
}

Zauważ, że metoda bierze również IEnumerablepod uwagę nieogólne, zwraca objectw tym przypadku, ponieważ Typejako argument przyjmuje raczej instancję niż konkretną. Nawiasem mówiąc, ponieważ x reprezentuje nieznane , uznałem ten film za interesujący , chociaż nie ma on znaczenia.

Ken Kin
źródło