Verifica se l'oggetto è di tipo generico in C #


134

Vorrei eseguire un test se un oggetto è di tipo generico. Ho provato quanto segue senza successo:

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

Cosa sto sbagliando e come posso eseguire questo test?

Risposte:


201

Se vuoi verificare se si tratta di un'istanza di tipo generico:

return list.GetType().IsGenericType;

Se vuoi verificare se è un generico List<T>:

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

Come sottolinea Jon, questo controlla l'esatta equivalenza del tipo. Restituire falsenon significa necessariamente list is List<T>restituire false(cioè l'oggetto non può essere assegnato a una List<T>variabile).


9
Tuttavia, ciò non rileverà i sottotipi. Vedi la mia risposta È anche molto più difficile per le interfacce :(
Jon Skeet,

1
La chiamata a GetGenericTypeDefinition verrà lanciata se non è un tipo generico. Assicurati di controllare prima quello.
Kilhoffer,

85

Presumo che tu non voglia solo sapere se il tipo è generico, ma se un oggetto è un'istanza di un particolare tipo generico, senza conoscere gli argomenti del tipo.

Purtroppo non è terribilmente semplice. Non è male se il tipo generico è una classe (come in questo caso) ma è più difficile per le interfacce. Ecco il codice per una 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: come notato nei commenti, questo potrebbe funzionare per le interfacce:

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

Ho un sospetto furbo che ci possano essere alcuni casi imbarazzanti attorno a questo, ma non riesco a trovarne uno per cui non riesca ora.


2
Ho appena scoperto un problema con questo. Scende solo una singola linea di eredità. Se, lungo la strada, hai una base sia con una classe base che con l'interfaccia che stai cercando, questo scende solo nel percorso della classe.
Groxx,

1
@Groxx: True. Ho appena notato che lo menziono nella risposta: "Non è un male se il tipo generico è una classe (come in questo caso) ma è più difficile per le interfacce. Ecco il codice per una classe"
Jon Skeet

1
E se non avessi modo di conoscere <T>? Ad esempio, potrebbe essere int o stringa, ma non lo sai. Questo genera, sembrerebbe, falsi negativi ... quindi non hai una T da usare, stai solo guardando le proprietà di alcuni oggetti e uno è un elenco. Come fai a sapere che è un elenco in modo da poterlo deselezionare? Con questo voglio dire, non hai una T da nessuna parte né un tipo da usare. Potresti indovinare ogni tipo (è List <int>? È List <string>?) Ma quello che vuoi sapere è QUESTO ELENCO AA? Sembra difficile rispondere a questa domanda.

@RiverC: Sì, hai ragione - è abbastanza difficile rispondere, per vari motivi. Se stai parlando solo di una classe, non è poi così male ... puoi continuare a camminare sull'albero dell'eredità e vedere se colpisci List<T>in un modo o nell'altro. Se includi interfacce, è davvero complicato.
Jon Skeet,

3
non è possibile sostituire il loop in IsInstanceOfGenericTypecon una chiamata a IsAssignableFromanziché l'operatore di uguaglianza ( ==)?
slawekwin,

7

Puoi usare un codice più breve usando l'althougth dinamico, questo potrebbe essere più lento della pura riflessione:

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

Questi sono i miei due metodi di estensione preferiti che coprono la maggior parte dei casi limite di controllo del tipo generico:

Lavora con:

  • Interfacce multiple (generiche)
  • Classi di base multiple (generiche)
  • Presenta un sovraccarico che "esclude" il tipo generico specifico se restituisce true (vedere test unitario per i campioni):

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

Ecco un test per dimostrare la funzionalità (di base):

 [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
È corretto per una domanda diversa. Per questa domanda, non è corretto, poiché risolve solo (significativamente meno) la metà del problema.
Groxx,

1
La risposta di Stan R in effetti risponde alla domanda come posta, ma ciò che realmente significava OP era "Testare se l'oggetto è di un particolare tipo generico in C #", per il quale questa risposta è effettivamente incompleta.
yoyo

la gente mi sta votando male perché ho risposto alla domanda nel contesto di "è un" tipo generico piuttosto che "è di" tipo generico. L'inglese è la mia seconda lingua e tali sfumature linguistiche mi passano spesso, a mia difesa l'OP non ha specificamente chiesto di testare contro un tipo specifico e nel titolo chiede "è di" tipo generico ... non sono sicuro del motivo per cui merito i voti negativi per una domanda ambigua.
Stan R.,

2
Ora lo sai e puoi migliorare la tua risposta per essere più specifici e corretti.
Peter Ivan,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.