Prueba si el objeto es de tipo genérico en C #

134

Me gustaría realizar una prueba si un objeto es de tipo genérico. He intentado lo siguiente sin éxito:

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

¿Qué estoy haciendo mal y cómo realizo esta prueba?

Richbits
fuente

Respuestas:

201

Si desea verificar si se trata de una instancia de tipo genérico:

return list.GetType().IsGenericType;

Si quieres comprobar si es un genérico List<T>:

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

Como señala Jon, esto verifica la equivalencia de tipo exacta. Devolver falseno necesariamente significa list is List<T>devoluciones false(es decir, el objeto no se puede asignar a una List<T>variable).

Mehrdad Afshari
fuente
9
Sin embargo, eso no detectará subtipos. Mira mi respuesta. También es mucho más difícil para las interfaces :(
Jon Skeet
1
La llamada a GetGenericTypeDefinition se lanzará si no es un tipo genérico. Asegúrate de comprobar eso primero.
Kilhoffer
85

Supongo que no solo quiere saber si el tipo es genérico, sino si un objeto es una instancia de un tipo genérico particular, sin conocer los argumentos de tipo.

No es terriblemente simple, desafortunadamente. No es tan malo si el tipo genérico es una clase (como lo es en este caso) pero es más difícil para las interfaces. Aquí está el código para una clase:

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

EDITAR: Como se señaló en los comentarios, esto puede funcionar para las interfaces:

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

Tengo la sospecha de que puede haber algunos casos incómodos al respecto, pero no puedo encontrar uno por el que falle en este momento.

Jon Skeet
fuente
2
Acabo de descubrir un problema con esto. Solo baja una sola línea de herencia. Si, en el camino, tiene una base con una clase base y la interfaz que está buscando, esto solo va por la ruta de clase.
Groxx
1
@Groxx: cierto. Sin embargo, acabo de ver que sí menciono eso en la respuesta: "No es tan malo si el tipo genérico es una clase (como lo es en este caso) pero es más difícil para las interfaces. Aquí está el código para una clase"
Jon Skeet
1
¿Qué pasa si no tienes una manera de saber <T>? Como, podría ser int o string, pero no lo sabes. Esto genera, al parecer, falsos negativos ... por lo que no tiene una T para usar, solo está mirando las propiedades de algún objeto y una es una lista. ¿Cómo sabes que es una lista para que puedas despegarla? Con esto quiero decir, no tienes una T en ninguna parte ni un tipo para usar. Podrías adivinar cada tipo (¿es Lista <int>? ¿Es Lista <cadena>?) Pero lo que quieres saber es ¿ ES ESTA LISTA AA? Esa pregunta parece difícil de responder.
@RiverC: Sí, tienes razón, es bastante difícil de responder, por varias razones. Si solo estás hablando de una clase, no es tan malo ... puedes seguir caminando por el árbol de la herencia y ver si golpeas List<T>de una forma u otra. Si incluye interfaces, es realmente complicado.
Jon Skeet
3
¿no podría reemplazar el bucle IsInstanceOfGenericTypecon una llamada a en IsAssignableFromlugar del operador de igualdad ( ==)?
slawekwin
7

Puede usar código más corto usando dinámico aunque esto puede ser más lento que la reflexión pura:

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

Estos son mis dos métodos de extensión favoritos que cubren la mayoría de los casos extremos de verificación de tipos genéricos:

Funciona con:

  • Múltiples interfaces (genéricas)
  • Clases base múltiples (genéricas)
  • Tiene una sobrecarga que 'eliminará' el tipo genérico específico si devuelve verdadero (consulte la prueba unitaria para obtener muestras):

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

Aquí hay una prueba para demostrar la funcionalidad (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
fuente
0
return list.GetType().IsGenericType;
Stan R.
fuente
3
Es correcto para una pregunta diferente. Para esta pregunta, es incorrecta, ya que solo aborda (significativamente menos que) la mitad del problema.
Groxx
1
De hecho, la respuesta de Stan R responde a la pregunta planteada, pero lo que realmente significaba el OP era "Probar si el objeto es de un tipo genérico particular en C #", para lo cual esta respuesta es realmente incompleta.
yoyo
la gente me baja el voto porque respondí la pregunta en el contexto de "es un" tipo genérico en lugar de "es de un" tipo genérico. El inglés es mi segundo idioma y tales matices de idioma me pasan por alto a menudo, en mi defensa, el OP no solicitó específicamente una prueba contra un tipo específico y en el título pregunta "es de" tipo genérico ... no estoy seguro de por qué merezco votos negativos para Una pregunta ambigua.
Stan R.
2
Ahora lo sabe y puede mejorar su respuesta para ser más específico y correcto.
Peter Ivan