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?

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 .


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

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

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()

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;

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

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

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

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.


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

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).


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

0

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

propInfo.PropertyType.UnderlyingSystemType.GenericTypeArguments[0]

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

-9

Tipo:

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

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
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.