Testando se o objeto é do tipo genérico em C #

134

Eu gostaria de realizar um teste se um objeto é de um tipo genérico. Eu tentei o seguinte sem sucesso:

public bool Test()
{
    List<int> list = new List<int>();
    return list.GetType() == typeof(List<>);
}

O que estou fazendo de errado e como faço esse teste?

Richbits
fonte

Respostas:

201

Se você deseja verificar se é uma instância de um tipo genérico:

return list.GetType().IsGenericType;

Se você deseja verificar se é genérico List<T>:

return list.GetType().GetGenericTypeDefinition() == typeof(List<>);

Como Jon aponta, isso verifica a equivalência exata do tipo. Retornar falsenão significa necessariamente list is List<T>retornos false(ou seja, o objeto não pode ser atribuído a uma List<T>variável).

Mehrdad Afshari
fonte
9
Porém, isso não detecta subtipos. Veja minha resposta. É também muito mais difícil para interfaces :(
Jon Skeet
1
A chamada para GetGenericTypeDefinition será lançada se não for um tipo genérico. Certifique-se de verificar isso primeiro.
Kilhoffer 16/01/19
85

Suponho que você não queira apenas saber se o tipo é genérico, mas se um objeto é uma instância de um tipo genérico específico, sem conhecer os argumentos de tipo.

Infelizmente, não é muito simples. Não é tão ruim se o tipo genérico for uma classe (como é neste caso), mas é mais difícil para interfaces. Aqui está o código para uma classe:

using System;
using System.Collections.Generic;
using System.Reflection;

class Test
{
    static bool IsInstanceOfGenericType(Type genericType, object instance)
    {
        Type type = instance.GetType();
        while (type != null)
        {
            if (type.IsGenericType &&
                type.GetGenericTypeDefinition() == genericType)
            {
                return true;
            }
            type = type.BaseType;
        }
        return false;
    }

    static void Main(string[] args)
    {
        // True
        Console.WriteLine(IsInstanceOfGenericType(typeof(List<>),
                                                  new List<string>()));
        // False
        Console.WriteLine(IsInstanceOfGenericType(typeof(List<>),
                                                  new string[0]));
        // True
        Console.WriteLine(IsInstanceOfGenericType(typeof(List<>),
                                                  new SubList()));
        // True
        Console.WriteLine(IsInstanceOfGenericType(typeof(List<>),
                                                  new SubList<int>()));
    }

    class SubList : List<string>
    {
    }

    class SubList<T> : List<T>
    {
    }
}

EDIT: Como observado nos comentários, isso pode funcionar para interfaces:

foreach (var i in type.GetInterfaces())
{
    if (i.IsGenericType && i.GetGenericTypeDefinition() == genericType)
    {
        return true;
    }
}

Eu tenho uma suspeita furtiva de que possa haver alguns casos estranhos em torno disso, mas não consigo encontrar um no qual ele falhe agora.

Jon Skeet
fonte
2
Acabei de descobrir um problema com isso. Ele apenas desce uma única linha de herança. Se, ao longo do caminho, você tiver uma base com a classe base e a interface que procura, isso seguirá apenas o caminho da classe.
Groxx
1
@Groxx: Verdade. Acabei de constatar que mencionei isso na resposta: "Não é tão ruim se o tipo genérico for uma classe (como é neste caso), mas é mais difícil para interfaces. Aqui está o código para uma classe"
Jon Skeet
1
E se você não tem como saber <T>? Pode ser int ou string, mas você não sabe disso. Isso gera, ao que parece, falsos negativos ... para que você não tenha um T para usar, basta olhar através das propriedades de algum objeto e um é uma lista. Como você sabe que é uma lista para poder descascá-la? Com isso, quero dizer, você não tem um T em nenhum lugar nem um tipo para usar. Você poderia adivinhar todos os tipos (é Lista <int>? Lista <string>?), Mas o que você quer saber é ESTA LISTA AA? Essa pergunta parece difícil de responder.
@RiverC: Sim, você está certo - é bastante difícil responder, por várias razões. Se você está falando apenas de uma classe, não é tão ruim ... você pode continuar subindo na árvore de herança e ver se você bate List<T>de uma forma ou de outra. Se você incluir interfaces, é realmente complicado.
Jon Skeet
3
você não poderia substituir o loop IsInstanceOfGenericTypepor uma chamada para, em IsAssignableFromvez de operador de igualdade ( ==)?
#
7

Você pode usar um código mais curto usando a dinâmica, mas isso pode ser mais lento que o reflexo puro:

public static class Extension
{
    public static bool IsGenericList(this object o)
    {
       return IsGeneric((dynamic)o);
    }

    public static bool IsGeneric<T>(List<T> o)
    {
       return true;
    }

    public static bool IsGeneric( object o)
    {
        return false;
    }
}



var l = new List<int>();
l.IsGenericList().Should().BeTrue();

var o = new object();
o.IsGenericList().Should().BeFalse();
David Desmaisons
fonte
7

Estes são meus dois métodos de extensão favoritos que abrangem a maioria dos casos extremos de verificação de tipo genérico:

Funciona com:

  • Múltiplas interfaces (genéricas)
  • Várias classes base (genéricas)
  • Tem uma sobrecarga que 'expulsa' o tipo genérico específico se retornar verdadeiro (consulte o teste de unidade para obter amostras):

    public static bool IsOfGenericType(this Type typeToCheck, Type genericType)
    {
        Type concreteType;
        return typeToCheck.IsOfGenericType(genericType, out concreteType); 
    }
    
    public static bool IsOfGenericType(this Type typeToCheck, Type genericType, out Type concreteGenericType)
    {
        while (true)
        {
            concreteGenericType = null;
    
            if (genericType == null)
                throw new ArgumentNullException(nameof(genericType));
    
            if (!genericType.IsGenericTypeDefinition)
                throw new ArgumentException("The definition needs to be a GenericTypeDefinition", nameof(genericType));
    
            if (typeToCheck == null || typeToCheck == typeof(object))
                return false;
    
            if (typeToCheck == genericType)
            {
                concreteGenericType = typeToCheck;
                return true;
            }
    
            if ((typeToCheck.IsGenericType ? typeToCheck.GetGenericTypeDefinition() : typeToCheck) == genericType)
            {
                concreteGenericType = typeToCheck;
                return true;
            }
    
            if (genericType.IsInterface)
                foreach (var i in typeToCheck.GetInterfaces())
                    if (i.IsOfGenericType(genericType, out concreteGenericType))
                        return true;
    
            typeToCheck = typeToCheck.BaseType;
        }
    }

Aqui está um teste para demonstrar a funcionalidade (básica):

 [Test]
    public void SimpleGenericInterfaces()
    {
        Assert.IsTrue(typeof(Table<string>).IsOfGenericType(typeof(IEnumerable<>)));
        Assert.IsTrue(typeof(Table<string>).IsOfGenericType(typeof(IQueryable<>)));

        Type concreteType;
        Assert.IsTrue(typeof(Table<string>).IsOfGenericType(typeof(IEnumerable<>), out concreteType));
        Assert.AreEqual(typeof(IEnumerable<string>), concreteType);

        Assert.IsTrue(typeof(Table<string>).IsOfGenericType(typeof(IQueryable<>), out concreteType));
        Assert.AreEqual(typeof(IQueryable<string>), concreteType);


    }
Wiebe Tijsma
fonte
0
return list.GetType().IsGenericType;
Stan R.
fonte
3
Está correto para uma pergunta diferente. Para esta pergunta, está incorreto, pois trata apenas (significativamente menos que) da metade do problema.
Groxx
1
A resposta de Stan R de fato responde à pergunta como colocada, mas o que o OP realmente queria dizer era "Testando se o objeto é de um tipo genérico específico em C #", para o qual essa resposta é realmente incompleta.
yoyo
as pessoas estão votando negativamente em mim porque eu respondi à pergunta no contexto de "é um" tipo genérico em vez de "é de um" tipo genérico. O inglês é meu segundo idioma e essas nuances de idioma geralmente me passam despercebidas. Para minha defesa, o OP não pediu especificamente para testar contra um tipo específico e, no título, pergunta "é do tipo genérico ... não sabe por que mereço votos negativos para uma pergunta ambígua.
Stan R.
2
Agora você sabe disso e pode melhorar sua resposta para ser mais específico e correto.
Peter Ivan