Comment obtenir le type de T d'un membre d'une classe ou d'une méthode générique?


675

Disons que j'ai un membre générique dans une classe ou une méthode, donc:

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

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

Quand j'instancier la classe, le Tdevient MyTypeObject1, si la classe a une propriété de liste générique: List<MyTypeObject1>. La même chose s'applique à une méthode générique dans une classe non générique:

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

        // get type of T
    }
}

Je voudrais savoir quel type d'objets contient la liste de ma classe. Ainsi, la propriété list appelée Barou la variable locale baz, contient quel type de T?

Je ne peux pas le faire Bar[0].GetType(), car la liste peut contenir zéro élément. Comment puis-je le faire?

Réponses:


695

Si je comprends bien, votre liste a le même paramètre de type que la classe de conteneur elle-même. Si tel est le cas, alors:

Type typeParameterType = typeof(T);

Si vous avez la chance d'avoir objectcomme paramètre de type, voyez la réponse de Marc .


4
Lol - oui, très vrai; Je suppose que l'OP n'avait object, IListou similaire - mais cela pourrait très bien être la bonne réponse.
Marc Gravell

32
J'adore la lisibilité typeof. Si vous voulez connaître le type de T, utilisez simplement typeof(T):)
demoncodemonkey

2
En fait, je viens d'utiliser typeof (Type) et cela fonctionne très bien.
Anton

1
Cependant, vous ne pouvez pas utiliser typeof () avec un paramètre générique.
Reynevan

2
@Reynevan Bien sûr, vous pouvez utiliser typeof()un paramètre générique. Avez-vous un exemple où cela ne fonctionnerait pas? Ou confondez-vous les paramètres de type et les références?
Luaan

520

(note: je suppose que tout ce que vous savez est objectou IListou similaire, et que la liste peut être de n'importe quel type au moment de l'exécution)

Si vous savez que c'est un List<T>, alors:

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

Une autre option consiste à regarder l'indexeur:

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

Utilisation de la nouvelle TypeInfo:

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

1
Tapez type = abc.GetType (). GetGenericArguments () [0]; ==> Index de tableau hors limites ...
Patrick Desjardins

28
@Daok: alors ce n'est pas une liste <T>
Marc Gravell

Besoin de quelque chose pour BindingList ou List ou tout autre objet contenant un <T>. Ce que je fais, utilisez un BindingListView personnalisé <T>
Patrick Desjardins

1
Essayez avec BindingList <T>, notre BindingListView <T> hérite de BindingList <T> et les deux J'ai essayé les deux de votre option et cela ne fonctionne pas. Je peux faire quelque chose de mal ... mais je pense que cette solution fonctionne pour le type List <T> mais pas pour un autre type de liste.
Patrick Desjardins

Tapez type = abc.GetType (). GetProperty ("Item"). PropertyType; retourner BindingListView <MyObject> au lieu de MyObject ...
Patrick Desjardins

49

Avec la méthode d'extension suivante, vous pouvez vous évader sans réflexion:

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

Ou plus général:

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

Usage:

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
Ceci n'est utile que si vous connaissez déjà le type de Tau moment de la compilation. Dans ce cas, vous n'avez vraiment pas besoin de code.
récursif

'return default (T);'
fantastory

1
@recursive: c'est utile si vous travaillez avec une liste de type anonyme.
JJJ

J'ai fait exactement la même chose avant de lire votre réponse mais j'avais appelé la mienneItemType
toddmo

31

Essayer

list.GetType().GetGenericArguments()

7
new List <int> () .GetType (). GetGenericArguments () renvoie System.Type [1] ici avec System.Int32 en entrée
Rauhotz

@Rauhotz, la GetGenericArgumentsméthode renvoie un objet Array de Type, dont vous devez ensuite analyser la position du type générique dont vous avez besoin. Tels que Type<TKey, TValue>: vous devez GetGenericArguments()[0]obtenir le TKeytype et GetGenericArguments()[1]obtenir le TValuetype
GoldBishop

14

C'est du travail pour moi. Où myList est une sorte de liste inconnue.

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

1
Je reçois une erreur qui nécessite un argument de type (ie <T>)
Joseph Humfrey

Joseph et d'autres, pour se débarrasser de l'erreur, c'est dans System.Collections.
user2334883

1
Juste la deuxième ligne est nécessaire pour moi. A Listest déjà une implémentation de IEnumerable, donc la distribution ne semble rien ajouter. Mais merci, c'est une bonne solution.
pipedreambomb

10

Si vous n'avez pas besoin de la variable Type entière et que vous voulez simplement vérifier le type, vous pouvez facilement créer une variable temporaire et utiliser son opérateur.

T checkType = default(T);

if (checkType is MyClass)
{}

9

Vous pouvez utiliser celui-ci pour le type de retour de liste générique:

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

8

Considérez ceci: je l'utilise pour exporter 20 listes dactylographiées de la même manière:

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
Cela ne fonctionne pas si T est une superclasse abstraite des objets réellement ajoutés. Sans parler, new T();ferait la même chose que (T)Activator.CreateInstance(typeof(T));. Cela nécessite que vous ajoutiez where T : new()à la définition de classe / fonction, mais si vous voulez créer des objets, cela devrait être fait de toute façon.
Nyerguds

En outre, vous appelez GetTypeune FirstOrDefaultentrée entraînant une exception de référence null potentielle. Si vous êtes sûr qu'il retournera au moins un article, pourquoi ne pas l'utiliser à la Firstplace?
Mathias Lykkegaard Lorenzen

6

J'utilise cette méthode d'extension pour accomplir quelque chose de similaire:

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

Vous l'utilisez comme ceci:

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

Ce n'est pas tout à fait ce que vous recherchez, mais c'est utile pour démontrer les techniques impliquées.


Bonjour, merci pour votre réponse, pourriez-vous également ajouter l'extension pour "StripStartingWith"?
Cedric Arnould

1
@CedricArnould - Ajouté.
Ken Smith

5

La GetGenericArgument()méthode doit être définie sur le type de base de votre instance (dont la classe est une classe génériquemyClass<T> ). Sinon, il renvoie un type [0]

Exemple:

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

5

Vous pouvez obtenir le type de "T" à partir de n'importe quel type de collection qui implémente IEnumerable <T> avec les éléments suivants:

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

Notez qu'il ne prend pas en charge les types de collection qui implémentent IEnumerable <T> deux fois, par exemple

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

Je suppose que vous pourriez renvoyer un tableau de types si vous aviez besoin de prendre en charge cela ...

En outre, vous pouvez également vouloir mettre en cache le résultat par type de collection si vous faites beaucoup cela (par exemple dans une boucle).


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
Cela semble répondre à la question de savoir si le type est une sorte de liste-y, mais la question est plus de savoir comment déterminer avec quel paramètre de type générique un type connu pour être une liste a déjà été initialisé.
Nathan Tuggy

1

Utilisation de la solution de 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

Si vous souhaitez connaître le type sous-jacent d'une propriété, essayez ceci:

propInfo.PropertyType.UnderlyingSystemType.GenericTypeArguments[0]

0

C'est comme ça que je l'ai fait

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

Alors appelle ça comme ça

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

OU

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

-9

Type:

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

1
Cela déclencherait une NullReferenceException si la liste ne contient aucun élément à l'intérieur duquel elle pourrait être testée.
rossisdead

1
SingleOrDefault()lance également InvalidOperationExceptionlorsqu'il y a deux éléments ou plus.
devgeezer

Cette réponse est fausse, comme le soulignent correctement \ \ rossisdead et \ @devgeezer.
Oliver
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.