Typ T von IEnumerable <T> abrufen

106

gibt es eine Möglichkeit Art abzurufen Taus IEnumerable<T>durch Reflexion?

z.B

Ich habe eine variable IEnumerable<Child>Info. Ich möchte den Typ des Kindes durch Reflexion abrufen

Usman Masood
quelle
1
In welchem ​​Kontext? Was ist das IEnumerable <T>? Ist es eine Objektinstanz, die als Argument gesendet wird? Oder was?
Mehrdad Afshari

Antworten:

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

Somit ist

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

druckt System.String.

Siehe MSDN für Type.GetGenericArguments.

Bearbeiten: Ich glaube, dies wird die Bedenken in den Kommentaren ansprechen:

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

Einige Objekte implementieren mehr als ein generisches Objekt, IEnumerabledaher muss eine Aufzählung von ihnen zurückgegeben werden.

Bearbeiten: Obwohl, ich muss sagen, es eine schreckliche Idee für eine Klasse ist, IEnumerable<T>für mehr als eine zu implementieren T.

Jason
quelle
Oder noch schlimmer: Schreiben Sie eine Methode mit Ertragsrenditen und versuchen Sie, GetType für eine mit dieser Methode erstellte Variable aufzurufen. Es wird Ihnen sagen, dass es sich nicht um einen generischen Typ handelt. Grundsätzlich gibt es also keine universelle Möglichkeit, T mit einer Instanzvariablen vom Typ IEnumerable <T>
Darin Dimitrov
1
Oder versuchen Sie es mit der Klasse MyClass: IEnumerable <int> {}. Diese Klasse hat keine generische Schnittstelle.
Stefan Steinegger
1
Warum sollte jemand jemals darauf zurückgreifen, die generischen Argumente abzurufen und dann den Typ aus seinem Indexer zu holen? Das ist nur eine Katastrophe, besonders wenn die Sprache typeof (T) unterstützt, wie @amsprich in seiner Antwort vorschlägt, was auch mit einem generischen oder einem bekannten Typ verwendet werden kann ...
Robert Petz
Dies schlägt bei Verwendung von Linq-Abfragen kläglich fehl - das erste generische Argument eines WhereSelectEnumerableIterator ist dies nicht . Sie erhalten das generische Argument des zugrunde liegenden Objekts, nicht die Schnittstelle selbst.
Pxtl
myEnumerable.GetType (). GetGenericArguments () [0] gibt Ihnen die FullName-Eigenschaft, die Ihnen den Namespace.classname mitteilt. Wenn Sie nur nach dem Klassennamen suchen, verwenden Sie myEnumerable.GetType (). GetGenericArguments () [0] .Name
user5534263
38

Ich würde nur eine Erweiterungsmethode machen. Dies funktionierte mit allem, was ich darauf warf.

public static Type GetItemType<T>(this IEnumerable<T> enumerable)
{
    return typeof(T);
}
amsprich
quelle
6
Es funktioniert nicht, wenn Ihre Kompilierungszeitreferenz nur vom Typ Objekt ist.
Stijn Van Antwerpen
27

Ich hatte ein ähnliches Problem. Die ausgewählte Antwort funktioniert für tatsächliche Instanzen. In meinem Fall hatte ich nur einen Typ (von a PropertyInfo).

Die ausgewählte Antwort schlägt fehl, wenn der Typ selbst typeof(IEnumerable<T>)keine Implementierung von ist IEnumerable<T>.

In diesem Fall funktioniert Folgendes:

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
quelle
Hat meinen Tag gerettet. Für meinen Fall habe ich eine separate if-Anweisung hinzugefügt, um Zeichenfolgen zu behandeln, da sie IEnumerable <char>
Edmund P Charumbira
Type.GenericTypeArguments- nur für dotNet FrameWork Version> = 4.5. Ansonsten - Type.GetGenericArgumentsstattdessen verwenden.
20ое Кто
20

Wenn Sie das kennen IEnumerable<T>(über Generika), dann typeof(T)sollte es einfach funktionieren. Andernfalls (für objectoder nicht generisch IEnumerable) überprüfen Sie die implementierten Schnittstellen:

        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
quelle
3
Einige Objekte implementieren mehr als eine generische IEnumerable.
Jason
5
@ Jason - und in diesen Fällen ist die Frage "finde das T" bereits eine zweifelhafte Frage;
Daran
Ein kleiner Gotcha für jedermann mit einem Versuch , diese zu verwenden , Type typeParameter und nicht als object objParameter: Sie können nicht nur ersetzen obj.GetType()mit , typedenn wenn man in geben typeof(IEnumerable<T>)Sie bekommen nichts. Um dies zu umgehen, testen Sie das typeselbst, um festzustellen, ob es sich um ein Generikum von IEnumerable<>und dann um seine Schnittstellen handelt.
Ian Mercer
8

Vielen Dank für die Diskussion. Ich habe es als Grundlage für die unten stehende Lösung verwendet, die für alle Fälle, die für mich von Interesse sind (IEnumerable, abgeleitete Klassen usw.), gut funktioniert. Ich dachte, ich sollte hier teilen, falls jemand es auch braucht:

  Type GetItemType(object someCollection)
  {
    var type = someCollection.GetType();
    var ienum = type.GetInterface(typeof(IEnumerable<>).Name);
    return ienum != null
      ? ienum.GetGenericArguments()[0]
      : null;
  }
Bernardo
quelle
Hier ist ein Einzeiler, der all dies mit dem Null-Bedingungsoperator erledigt: someCollection.GetType().GetInterface(typeof(IEnumerable<>).Name)?.GetGenericArguments()?.FirstOrDefault()
Mass Dot Net
2

Benutz einfach typeof(T)

BEARBEITEN : Oder verwenden Sie .GetType (). GetGenericParameter () für ein instanziiertes Objekt, wenn Sie kein T haben.

Zügel
quelle
Sie haben nicht immer T.
Jason
True, in diesem Fall können Sie .GetType () verwenden. Ich werde meine Antwort ändern.
Zügel
2

Eine Alternative für einfachere Situationen, in denen entweder eine IEnumerable<T>oder eine TNotiz GenericTypeArgumentsanstelle von verwendet wird GetGenericArguments().

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

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

Dies ist eine Verbesserung der Lösung von Eli Algranti, da sie auch dort funktioniert, wo sich der IEnumerable<>Typ auf einer beliebigen Ebene im Vererbungsbaum befindet.

Diese Lösung erhält den Elementtyp von jedem Type. Wenn der Typ kein ist IEnumerable<>, wird der übergebene Typ zurückgegeben. Verwenden Sie für Objekte GetType. Verwenden Sie für Typen typeofdiese Erweiterungsmethode und rufen Sie sie für das Ergebnis auf.

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
quelle
1

Ich weiß, dass dies ein bisschen alt ist, aber ich glaube, dass diese Methode alle in den Kommentaren genannten Probleme und Herausforderungen abdecken wird. Dank an Eli Algranti für die Inspiration meiner Arbeit.

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

Dies ist eine rekursive Funktion, die zuerst die Liste der generischen Typen vertieft, bis eine konkrete Typdefinition ohne innere generische Typen erhalten wird.

Ich habe diese Methode mit folgendem Typ getestet: ICollection<IEnumerable<ICollection<ICollection<IEnumerable<IList<ICollection<IEnumerable<IActionResult>>>>>>>>

was zurückkehren sollte IActionResult

Tyler Huskins
quelle
0

typeof(IEnumerable<Foo>). gibt das erste generische Argument zurück - in diesem Fall .GetGenericArguments()[0]typeof(Foo)

Daniel Brückner
quelle
0

So mache ich es normalerweise (über die Erweiterungsmethode):

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

Hier ist meine unlesbare Version des Linq-Abfrageausdrucks.

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

Beachten Sie, dass die Methode auch nicht generische IEnumerableElemente berücksichtigt. objectIn diesem Fall wird sie zurückgegeben, da aType als Argument eher eine als eine konkrete Instanz verwendet wird. Übrigens, da x das Unbekannte darstellt , fand ich dieses Video interessant, obwohl es irrelevant ist.

Ken Kin
quelle