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?

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


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.


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

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


    }

0
return list.GetType().IsGenericType;

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