Como obter o tipo de T de um membro de uma classe ou método genérico?

674

Digamos que eu tenha um membro genérico em uma classe ou método, então:

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

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

Quando eu instanciar a classe, o Ttorna-se MyTypeObject1, por isso, a classe tem uma propriedade lista genérica: List<MyTypeObject1>. O mesmo se aplica a um método genérico em uma classe não genérica:

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

        // get type of T
    }
}

Gostaria de saber que tipo de objetos a lista da minha classe contém. Portanto, a propriedade list chamada Barou a variável local baz, contém que tipo de T?

Não posso Bar[0].GetType(), porque a lista pode conter zero elementos. Como eu posso fazer isso?

Patrick Desjardins
fonte

Respostas:

693

Se bem entendi, sua lista tem o mesmo parâmetro de tipo que a própria classe de contêiner. Se for esse o caso, então:

Type typeParameterType = typeof(T);

Se você tiver a sorte de ter objectcomo parâmetro de tipo, consulte a resposta de Marc .

Tamas Czinege
fonte
4
Lol - sim, é verdade; Supus que o OP só tivesse object, IListou similar - mas isso poderia muito bem ser a resposta certa.
Marc Gravell
32
Eu amo o quão legível typeofé. Se você quiser saber o tipo de T, basta usar typeof(T):)
demoncodemonkey
2
Na verdade, eu apenas usei typeof (Type) e funciona muito bem.
Anton
1
Você não pode usar typeof () com um parâmetro genérico, no entanto.
Reynevan
2
@Reynevan Claro que você pode usar typeof()com um parâmetro genérico. Você tem algum exemplo em que não funcionaria? Ou você está confundindo parâmetros e referências de tipo?
Luaan 26/07/19
520

(observação: estou assumindo que tudo o que você sabe é objectou IListou semelhante, e que a lista pode ser de qualquer tipo em tempo de execução)

Se você sabe que é um List<T>, então:

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

Outra opção é olhar para o indexador:

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

Usando o novo TypeInfo:

using System.Reflection;
// ...
var type = abc.GetType().GetTypeInfo().GenericTypeArguments[0];
Marc Gravell
fonte
1
Digite type = abc.GetType (). GetGenericArguments () [0]; ==> Fora de índice da matriz limites ...
Patrick Desjardins
28
@Daok: então não é um List <T>
Marc Gravell
Precisa de algo para BindingList ou List ou qualquer objeto que contenha um <T>. O que eu estou fazendo uso de um costume BindingListView <T>
Patrick Desjardins
1
Dê uma chance com BindingList <T>, nosso BindingListView <T> herda de BindingList <T> e ambos eu tentei sua opção e ela não funciona. Eu posso fazer algo errado ... mas acho que esta solução funciona para o tipo List <T>, mas não para outro tipo de lista.
224 Patrick Desjardins
Digite type = abc.GetType (). GetProperty ("Item"). PropertyType; retorno BindingListView <MyObject> em vez de MyObject ...
Patrick Desjardins
49

Com o seguinte método de extensão, você pode fugir sem reflexão:

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

Ou mais geral:

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
3dGrabber
fonte
10
Isso só é útil se você já conhece o tipo de Tem tempo de compilação. Nesse caso, você realmente não precisa de nenhum código.
recursivo
'retornar padrão (T);'
fantastory
1
@ recursivo: é útil se você estiver trabalhando com uma lista de um tipo anônimo.
JJJ
Eu fiz exatamente a mesma coisa antes de eu ler a sua resposta, mas eu tinha chamado a minhaItemType
toddmo
31

Tentar

list.GetType().GetGenericArguments()
Rauhotz
fonte
7
new List <int> () .GetType (). GetGenericArguments () retorna System.Type [1] aqui com System.Int32 como entrada
Rauhotz
@Rauhotz, o GetGenericArgumentsmétodo retorna um objeto Array Type, do qual você precisa analisar a posição do Tipo Genérico necessário. Tais como Type<TKey, TValue>: você precisa GetGenericArguments()[0]para obter TKeyo tipo e GetGenericArguments()[1]para obter TValuetipo
GoldBishop
14

Isso é trabalho para mim. Onde myList é algum tipo de lista não conhecido.

IEnumerable myEnum = myList as IEnumerable;
Type entryType = myEnum.AsQueryable().ElementType;
Carlos Rodriguez
fonte
1
Eu recebo um erro que requer um argumento de tipo (ou seja <T>)
Joseph Humfrey
Joseph e outros, para se livrar do erro está no System.Collections.
precisa saber é o seguinte
1
Apenas a segunda linha é necessária para mim. A Listjá é uma implementação de IEnumerable, portanto, o elenco não parece adicionar nada. Mas obrigado, é uma boa solução.
precisa
10

Se você não precisa da variável Type inteira e apenas deseja verificar o tipo, pode criar facilmente uma variável temp e usar o operador is.

T checkType = default(T);

if (checkType is MyClass)
{}
Sebi
fonte
9

Você pode usar este para retornar o tipo de lista genérica:

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

Considere o seguinte: eu o uso para exportar a lista de 20 tipos da mesma maneira:

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);
    }
}
Ferenc Mucsi
fonte
1
Isso não funciona se T for uma superclasse abstrata dos objetos adicionados reais. Para não mencionar, apenas new T();faria a mesma coisa que (T)Activator.CreateInstance(typeof(T));. Requer que você adicione where T : new()à definição de classe / função, mas se você quiser criar objetos, isso deve ser feito de qualquer maneira.
21413 Nyerguds
Além disso, você está chamando GetTypeuma FirstOrDefaultentrada que resulta em uma possível exceção de referência nula. Se você tem certeza de que ele retornará pelo menos um item, por que não usar First?
Mathias Lykkegaard Lorenzen
6

Eu uso esse método de extensão para realizar algo semelhante:

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

Você o usa assim:

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

Não é exatamente o que você está procurando, mas é útil para demonstrar as técnicas envolvidas.

Ken Smith
fonte
Olá, obrigado pela resposta, você também pode adicionar a extensão "StripStartingWith"?
Cedric Arnould
1
@CedricArnould - Adicionado.
Ken Smith
5

O GetGenericArgument()método deve ser definido no Tipo Base da sua instância (cuja classe é uma classe genérica myClass<T>). Caso contrário, ele retornará um tipo [0]

Exemplo:

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

Você pode obter o tipo de "T" de qualquer tipo de coleção que implemente IEnumerable <T> com o seguinte:

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

Observe que ele não suporta tipos de coleção que implementam IEnumerable <T> duas vezes, por exemplo

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

Suponho que você possa retornar uma matriz de tipos, se necessário, para suportar isso ...

Além disso, você também pode querer armazenar em cache o resultado por tipo de coleção, se estiver fazendo muito isso (por exemplo, em um loop).

Dan Malcolm
fonte
1
public bool IsCollection<T>(T value){
  var valueType = value.GetType();
  return valueType.IsArray() || typeof(IEnumerable<object>).IsAssignableFrom(valueType) || typeof(IEnumerable<T>).IsAssignableFrom(valuetype);
}
Karanvir Kang
fonte
1
Isso parece abordar a questão de saber se o tipo é do tipo lista y, mas a questão é mais sobre como determinar com qual parâmetro de tipo genérico um tipo que é conhecido por ser uma lista já foi inicializado.
Nathan Tuggy
1

Usando a solução do 3dGrabber:

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

//and now 

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

Se você deseja conhecer o tipo subjacente de uma propriedade, tente o seguinte:

propInfo.PropertyType.UnderlyingSystemType.GenericTypeArguments[0]
Fatih Çelik
fonte
0

Foi assim que eu fiz

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

Então chame assim

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

OU

var item = Activator.CreateInstance(Bar.GetType().GetElementType());
Alen.Toma
fonte
-9

Tipo:

type = list.AsEnumerable().SingleOrDefault().GetType();
Ferenc Mucsi
fonte
1
Isso lançaria uma NullReferenceException se a lista não contiver elementos dentro dela para teste.
Rossisdead 14/07/10
1
SingleOrDefault()também lança InvalidOperationExceptionquando há dois ou mais elementos.
devgeezer
Esta resposta está errada, conforme indicado corretamente por \ @rossisdead e \ @devgeezer.
Oliver