Come ottenere il tipo di T da un membro di una classe o metodo generico?


675

Supponiamo che io abbia un membro generico in una classe o in un metodo, quindi:

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

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

Quando ho un'istanza della classe, l' Tdiviene MyTypeObject1, quindi la classe ha una proprietà elenco generico: List<MyTypeObject1>. Lo stesso vale per un metodo generico in una classe non generica:

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

        // get type of T
    }
}

Vorrei sapere quale tipo di oggetti contiene l'elenco della mia classe. Quindi la proprietà list chiamata Baro la variabile locale baz, contiene che tipo di T?

Non posso farlo Bar[0].GetType(), perché l'elenco potrebbe contenere zero elementi. Come posso farlo?

Risposte:


695

Se ho capito bene, la tua lista ha lo stesso parametro di tipo della classe contenitore stessa. In questo caso, quindi:

Type typeParameterType = typeof(T);

Se sei nella fortunata situazione di avere objectcome parametro di tipo, vedi la risposta di Marc .


4
Lol: sì, molto vero; Ho ipotizzato che il PO aveva solo object, IListo simili - ma questo potrebbe benissimo essere la risposta giusta.
Marc Gravell

32
Adoro quanto sia leggibile typeof. Se vuoi conoscere il tipo di T, usa semplicemente typeof(T):)
demoncodemonkey,

2
In realtà ho appena usato typeof (Type) e funziona benissimo.
Anton,

1
Tuttavia, non puoi usare typeof () con un parametro generico.
Reynevan,

2
@Reynevan Naturalmente puoi usare typeof()un parametro generico. Hai qualche esempio di dove non funzionerebbe? O stai confondendo parametri e riferimenti di tipo?
Luaan,

520

(nota: presumo che tutto ciò che sai sia objecto IListo simile e che l'elenco potrebbe essere di qualsiasi tipo in fase di esecuzione)

Se sai che è un List<T>, quindi:

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

Un'altra opzione è guardare l'indicizzatore:

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

Utilizzando il nuovo TypeInfo:

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

1
Digitare type = abc.GetType (). GetGenericArguments () [0]; ==> Indice array fuori limite ...
Patrick Desjardins,

28
@Daok: allora non è un elenco <T>
Marc Gravell

Hai bisogno di qualcosa per BindingList o List o qualunque oggetto che contenga un <T>. Quello che sto facendo usa un BindingListView personalizzato <T>
Patrick Desjardins,

1
Prova con BindingList <T>, il nostro BindingListView <T> eredita da BindingList <T> ed entrambi ho provato entrambe le opzioni e non funziona. Potrei fare qualcosa di sbagliato ... ma penso che questa soluzione funzioni per il tipo Elenco <T> ma non per altri tipi di elenco.
Patrick Desjardins,

Digitare type = abc.GetType (). GetProperty ("Item"). PropertyType; return BindingListView <MyObject> invece di MyObject ...
Patrick Desjardins,

49

Con il seguente metodo di estensione puoi cavartela senza riflettere:

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

O più generale:

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

Uso:

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
Questo è utile solo se conosci già il tipo di Tal momento della compilazione. In tal caso, non è necessario alcun codice.
ricorsivo

'return default (T);'
fantastico

1
@recursive: è utile se lavori con un elenco di tipo anonimo.
JJJ,

Ho fatto la stessa identica cosa prima di leggere la tua risposta, ma avevo chiamato la miaItemType
toddmo il

31

Provare

list.GetType().GetGenericArguments()

7
new List <int> () .GetType (). GetGenericArguments () restituisce System.Type [1] qui con System.Int32 come voce
Rauhotz,

@Rauhotz il GetGenericArgumentsmetodo restituisce un oggetto Array Typedi cui è necessario analizzare la posizione del Tipo generico necessario. Come ad esempio Type<TKey, TValue>: dovresti GetGenericArguments()[0]ottenere il TKeytipo e GetGenericArguments()[1]ottenere il TValuetipo
GoldBishop,

14

Questo è lavoro per me. Dove myList è un tipo sconosciuto di elenco.

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

1
Ottengo un errore che richiede un argomento di tipo (es. <T>)
Joseph Humfrey,

Joseph e altri, per sbarazzarsi dell'errore è in System.Collections.
user2334883

1
Per me è necessaria solo la seconda riga. A Listè già un'implementazione di IEnumerable, quindi il cast non sembra aggiungere nulla. Ma grazie, è una buona soluzione.
pipedreambomb,

10

Se non hai bisogno dell'intera variabile Type e vuoi solo controllare il tipo, puoi facilmente creare una variabile temp e usare is operator.

T checkType = default(T);

if (checkType is MyClass)
{}

9

È possibile utilizzare questo per il tipo di ritorno dell'elenco generico:

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

8

Considera questo: lo uso per esportare 20 elenchi digitati allo stesso modo:

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
Questo non funziona se T è una superclasse astratta degli oggetti effettivamente aggiunti. Per non parlare, new T();farebbe la stessa cosa di (T)Activator.CreateInstance(typeof(T));. Richiede di aggiungere where T : new()alla definizione di classe / funzione, ma se si desidera creare oggetti, ciò dovrebbe essere fatto comunque.
Nyerguds,

Inoltre, si sta chiamando GetTypeuna FirstOrDefaultvoce risultante in una potenziale eccezione di riferimento null. Se sei sicuro che restituirà almeno un articolo, perché non utilizzare Firstinvece?
Mathias Lykkegaard Lorenzen,

6

Uso questo metodo di estensione per realizzare qualcosa di simile:

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

Lo usi in questo modo:

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

Questo non è proprio quello che stai cercando, ma è utile per dimostrare le tecniche coinvolte.


Ciao, grazie per la risposta, potresti aggiungere anche l'estensione per "StripStartingWith"?
Cedric Arnould il

1
@CedricArnould - Aggiunto.
Ken Smith,

5

Il GetGenericArgument()metodo deve essere impostato sul Tipo di base dell'istanza (la cui classe è una classe generica myClass<T>). Altrimenti, restituisce un tipo [0]

Esempio:

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

5

È possibile ottenere il tipo di "T" da qualsiasi tipo di raccolta che implementa IEnumerable <T> con il seguente:

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

Si noti che non supporta i tipi di raccolta che implementano IEnumerable <T> due volte, ad es

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

Suppongo che potresti restituire una serie di tipi se dovessi supportare questo ...

Inoltre, potresti voler memorizzare nella cache il risultato per tipo di raccolta se lo stai facendo molto (ad esempio in un ciclo).


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
Questo sembra rispondere alla domanda se il tipo sia un tipo di cose list-y, ma la domanda è più su come determinare con quale parametro di tipo generico è noto un tipo che è già stato inizializzato.
Nathan Tuggy,

1

Utilizzando la soluzione di 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

Se vuoi conoscere il tipo sottostante di una proprietà, prova questo:

propInfo.PropertyType.UnderlyingSystemType.GenericTypeArguments[0]

0

È così che l'ho fatto

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

Quindi chiamalo così

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

O

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

-9

Genere:

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

1
Ciò genererebbe un'eccezione NullReferenceException se l'elenco non contiene elementi al suo interno da testare.
Rossisdead,

1
SingleOrDefault()genera anche InvalidOperationExceptionquando ci sono due o più elementi.
Devgeezer,

Questa risposta è errata, come sottolineato correttamente da \ @rossisdead e \ @devgeezer.
Oliver,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.