Wie erhalte ich den Typ von T von einem Mitglied einer generischen Klasse oder Methode?


674

Angenommen, ich habe ein generisches Mitglied in einer Klasse oder Methode, also:

public class Foo<T>
{
    public List<T> Bar { get; set; }

    public void Baz()
    {
        // get type of T
    }   
}

Wenn ich die Klasse instanziiere, Twird das MyTypeObject1, sodass die Klasse eine generische Listeneigenschaft hat : List<MyTypeObject1>. Gleiches gilt für eine generische Methode in einer nicht generischen Klasse:

public class Foo
{
    public void Bar<T>()
    {
        var baz = new List<T>();

        // get type of T
    }
}

Ich würde gerne wissen, welche Art von Objekten die Liste meiner Klasse enthält. Die aufgerufene Eigenschaft list Baroder die lokale Variable bazenthält also welche Art von T?

Ich kann nicht Bar[0].GetType(), weil die Liste möglicherweise keine Elemente enthält. Wie kann ich es tun?

Antworten:


693

Wenn ich das richtig verstehe, hat Ihre Liste den gleichen Typparameter wie die Containerklasse selbst. Wenn dies der Fall ist, dann:

Type typeParameterType = typeof(T);

Wenn Sie die glückliche Situation haben object, einen Typparameter zu haben, lesen Sie Marc's Antwort .


4
Lol - ja, sehr wahr; Ich nahm an, dass die OP nur hatte object, IListoder ähnlich - aber das könnte sehr wohl die richtige Antwort sein.
Marc Gravell

32
Ich liebe, wie lesbar typeofist. Wenn Sie die Art von T wissen möchten, verwenden Sie einfach typeof(T):)
Demoncodemonkey

2
Ich habe eigentlich nur typeof (Type) verwendet und es funktioniert großartig.
Anton

1
Sie können typeof () jedoch nicht mit einem generischen Parameter verwenden.
Reynevan

2
@Reynevan Natürlich können Sie typeof()mit einem generischen Parameter verwenden. Haben Sie ein Beispiel, wo es nicht funktionieren würde? Oder verwechseln Sie Typparameter und Referenzen?
Luaan

519

(Anmerkung: Ich gehe davon aus, dass alles , was Sie wissen , ist , objectoder IListoder ähnliches, und dass die Liste jede Art zur Laufzeit sein könnte)

Wenn Sie wissen, dass es ein ist List<T>, dann:

Type type = abc.GetType().GetGenericArguments()[0];

Eine andere Möglichkeit ist, sich den Indexer anzusehen:

Type type = abc.GetType().GetProperty("Item").PropertyType;

Verwenden neuer TypeInfo:

using System.Reflection;
// ...
var type = abc.GetType().GetTypeInfo().GenericTypeArguments[0];

1
Typ type = abc.GetType (). GetGenericArguments () [0]; ==> Out of Bounds Array Index ...
Patrick Desjardins

28
@ Daok: dann ist es keine Liste <T>
Marc Gravell

Benötigen Sie etwas für BindingList oder List oder ein beliebiges Objekt, das ein <T> enthält. Was ich tue, verwenden Sie eine benutzerdefinierte BindingListView <T>
Patrick Desjardins

1
Probieren Sie es mit BindingList <T> aus, unser BindingListView <T> erbt von BindingList <T> und beides. Ich habe beide Optionen ausprobiert und es funktioniert nicht. Ich könnte etwas falsch machen ... aber ich denke, diese Lösung funktioniert für den Typ List <T>, aber nicht für einen anderen Listentyp.
Patrick Desjardins

Typ type = abc.GetType (). GetProperty ("Item"). PropertyType; Rückgabe BindingListView <MyObject> anstelle von MyObject ...
Patrick Desjardins

49

Mit der folgenden Erweiterungsmethode können Sie ohne Reflexion davonkommen:

public static Type GetListType<T>(this List<T> _)
{
    return typeof(T);
}

Oder allgemeiner:

public static Type GetEnumeratedType<T>(this IEnumerable<T> _)
{
    return typeof(T);
}

Verwendungszweck:

List<string>        list    = new List<string> { "a", "b", "c" };
IEnumerable<string> strings = list;
IEnumerable<object> objects = list;

Type listType    = list.GetListType();           // string
Type stringsType = strings.GetEnumeratedType();  // string
Type objectsType = objects.GetEnumeratedType();  // BEWARE: object

10
Dies ist nur nützlich, wenn Sie den Typ Tzum Zeitpunkt der Kompilierung bereits kennen . In diesem Fall benötigen Sie überhaupt keinen Code.
rekursiv

'return default (T);'
fantastische

1
@recursive: Es ist nützlich, wenn Sie mit einer Liste eines anonymen Typs arbeiten.
JJJ

Ich habe genau das Gleiche getan, bevor ich Ihre Antwort gelesen habe, aber ich hatte meine angerufenItemType
toddmo

31

Versuchen

list.GetType().GetGenericArguments()

7
neue Liste <int> () .GetType (). GetGenericArguments () gibt System.Type [1] hier mit System.Int32 als Eintrag zurück
Rauhotz

@Rauhotz Die GetGenericArgumentsMethode gibt ein Array-Objekt zurück Type, von dem Sie dann die Position des benötigten generischen Typs analysieren müssen. Wie Type<TKey, TValue>: müssten Sie GetGenericArguments()[0]bekommen TKeyArt und GetGenericArguments()[1]bekommen TValueTyp
GoldBishop

14

Das ist Arbeit für mich. Wo myList eine unbekannte Art von Liste ist.

IEnumerable myEnum = myList as IEnumerable;
Type entryType = myEnum.AsQueryable().ElementType;

1
Ich erhalte die Fehlermeldung, dass ein Typargument (dh <T>) erforderlich ist
Joseph Humfrey

Joseph und andere, um den Fehler in System.Collections zu beseitigen.
user2334883

1
Nur die zweite Zeile wird für mich benötigt. A Listist bereits eine Implementierung von IEnumerable, daher scheint die Besetzung nichts hinzuzufügen. Aber danke, es ist eine gute Lösung.
Pipedreambomb

10

Wenn Sie nicht die gesamte Typvariable benötigen und nur den Typ überprüfen möchten, können Sie einfach eine temporäre Variable erstellen und den Operator is verwenden.

T checkType = default(T);

if (checkType is MyClass)
{}

9

Sie können diese für den Rückgabetyp einer generischen Liste verwenden:

public string ListType<T>(T value)
{
    var valueType = value.GetType().GenericTypeArguments[0].FullName;
    return valueType;
}

8

Bedenken Sie Folgendes: Ich verwende es, um 20 typisierte Listen auf dieselbe Weise zu exportieren:

private void Generate<T>()
{
    T item = (T)Activator.CreateInstance(typeof(T));

    ((T)item as DemomigrItemList).Initialize();

    Type type = ((T)item as DemomigrItemList).AsEnumerable().FirstOrDefault().GetType();
    if (type == null) return;
    if (type != typeof(account)) //account is listitem in List<account>
    {
        ((T)item as DemomigrItemList).CreateCSV(type);
    }
}

1
Dies funktioniert nicht, wenn T eine abstrakte Oberklasse der tatsächlich hinzugefügten Objekte ist. Ganz zu schweigen davon, new T();würde einfach das Gleiche tun wie (T)Activator.CreateInstance(typeof(T));. Es ist erforderlich, dass Sie where T : new()der Klassen- / Funktionsdefinition hinzufügen. Wenn Sie jedoch Objekte erstellen möchten , sollte dies trotzdem erfolgen.
Nyerguds

Außerdem rufen Sie GetTypeeinen FirstOrDefaultEintrag auf, der zu einer möglichen Nullreferenzausnahme führt. Wenn Sie sicher sind, dass mindestens ein Artikel zurückgegeben wird, verwenden Sie ihn Firststattdessen.
Mathias Lykkegaard Lorenzen

6

Ich benutze diese Erweiterungsmethode, um etwas Ähnliches zu erreichen:

public static string GetFriendlyTypeName(this Type t)
{
    var typeName = t.Name.StripStartingWith("`");
    var genericArgs = t.GetGenericArguments();
    if (genericArgs.Length > 0)
    {
        typeName += "<";
        foreach (var genericArg in genericArgs)
        {
            typeName += genericArg.GetFriendlyTypeName() + ", ";
        }
        typeName = typeName.TrimEnd(',', ' ') + ">";
    }
    return typeName;
}

public static string StripStartingWith(this string s, string stripAfter)
{
    if (s == null)
    {
        return null;
    }
    var indexOf = s.IndexOf(stripAfter, StringComparison.Ordinal);
    if (indexOf > -1)
    {
        return s.Substring(0, indexOf);
    }
    return s;
}

Sie verwenden es so:

[TestMethod]
public void GetFriendlyTypeName_ShouldHandleReallyComplexTypes()
{
    typeof(Dictionary<string, Dictionary<string, object>>).GetFriendlyTypeName()
        .ShouldEqual("Dictionary<String, Dictionary<String, Object>>");
}

Dies ist nicht ganz das, wonach Sie suchen, aber es ist hilfreich, um die beteiligten Techniken zu demonstrieren.


Hallo, danke für deine Antwort, könntest du auch die Erweiterung für "StripStartingWith" hinzufügen?
Cedric Arnould

1
@ CedricArnould - Hinzugefügt.
Ken Smith

5

Die GetGenericArgument()Methode muss für den Basistyp Ihrer Instanz festgelegt werden (deren Klasse eine generische Klasse ist myClass<T>). Andernfalls wird ein Typ [0] zurückgegeben.

Beispiel:

Myclass<T> instance = new Myclass<T>();
Type[] listTypes = typeof(instance).BaseType.GetGenericArguments();

5

Sie können den Typ "T" von jedem Sammlungstyp abrufen, der IEnumerable <T> wie folgt implementiert:

public static Type GetCollectionItemType(Type collectionType)
{
    var types = collectionType.GetInterfaces()
        .Where(x => x.IsGenericType 
            && x.GetGenericTypeDefinition() == typeof(IEnumerable<>))
        .ToArray();
    // Only support collections that implement IEnumerable<T> once.
    return types.Length == 1 ? types[0].GetGenericArguments()[0] : null;
}

Beachten Sie, dass Sammlungstypen, die IEnumerable <T> zweimal implementieren, nicht unterstützt werden, z

public class WierdCustomType : IEnumerable<int>, IEnumerable<string> { ... }

Ich nehme an, Sie könnten eine Reihe von Typen zurückgeben, wenn Sie dies unterstützen müssten ...

Außerdem möchten Sie möglicherweise auch das Ergebnis pro Sammlungstyp zwischenspeichern, wenn Sie dies häufig tun (z. B. in einer Schleife).


1
public bool IsCollection<T>(T value){
  var valueType = value.GetType();
  return valueType.IsArray() || typeof(IEnumerable<object>).IsAssignableFrom(valueType) || typeof(IEnumerable<T>).IsAssignableFrom(valuetype);
}

1
Dies scheint die Frage zu beantworten, ob es sich bei dem Typ um eine Liste handelt, aber es geht eher darum, zu bestimmen, mit welchem ​​generischen Typparameter ein Typ, von dem bekannt ist, dass er bereits eine Liste ist, bereits initialisiert wurde.
Nathan Tuggy

1

Verwenden der Lösung von 3dGrabber:

public static T GetEnumeratedType<T>(this IEnumerable<T> _)
{
    return default(T);
}

//and now 

var list = new Dictionary<string, int>();
var stronglyTypedVar = list.GetEnumeratedType();

0

Wenn Sie den zugrunde liegenden Typ einer Eigenschaft kennen möchten, versuchen Sie Folgendes:

propInfo.PropertyType.UnderlyingSystemType.GenericTypeArguments[0]

0

So habe ich es gemacht

internal static Type GetElementType(this Type type)
{
        //use type.GenericTypeArguments if exist 
        if (type.GenericTypeArguments.Any())
         return type.GenericTypeArguments.First();

         return type.GetRuntimeProperty("Item").PropertyType);
}

Dann nenne es so

var item = Activator.CreateInstance(iListType.GetElementType());

ODER

var item = Activator.CreateInstance(Bar.GetType().GetElementType());

-9

Art:

type = list.AsEnumerable().SingleOrDefault().GetType();

1
Dies würde eine NullReferenceException auslösen, wenn die Liste keine Elemente enthält, gegen die getestet werden kann.
Rossisdead

1
SingleOrDefault()Wirft auch, InvalidOperationExceptionwenn zwei oder mehr Elemente vorhanden sind.
Devgeezer

Diese Antwort ist falsch, wie von \ @rossisdead und \ @devgeezer richtig hervorgehoben.
Oliver
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.