Tester si l'objet est de type générique en C #

134

Je souhaite effectuer un test si un objet est de type générique. J'ai essayé ce qui suit sans succès:

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

Qu'est-ce que je fais mal et comment effectuer ce test?

Richbits
la source

Réponses:

201

Si vous voulez vérifier s'il s'agit d'une instance d'un type générique:

return list.GetType().IsGenericType;

Si vous voulez vérifier s'il s'agit d'un générique List<T>:

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

Comme le souligne Jon, cela vérifie l'équivalence de type exacte. Un retour falsene signifie pas nécessairement un list is List<T>retour false(c'est-à-dire que l'objet ne peut pas être affecté à une List<T>variable).

Mehrdad Afshari
la source
9
Cela ne détectera cependant pas les sous-types. Voyez ma réponse. C'est aussi beaucoup plus difficile pour les interfaces :(
Jon Skeet
1
L'appel à GetGenericTypeDefinition sera lancé s'il ne s'agit pas d'un type générique. Assurez-vous de vérifier cela en premier.
Kilhoffer
85

Je suppose que vous ne voulez pas seulement savoir si le type est générique, mais si un objet est une instance d'un type générique particulier, sans connaître les arguments de type.

Ce n'est malheureusement pas très simple. Ce n'est pas trop mal si le type générique est une classe (comme c'est le cas dans ce cas) mais c'est plus difficile pour les interfaces. Voici le code d'une 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: Comme indiqué dans les commentaires, cela peut fonctionner pour les interfaces:

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

J'ai un soupçon sournois qu'il peut y avoir des cas délicats à ce sujet, mais je ne peux pas en trouver un pour lequel il échoue pour le moment.

Jon Skeet
la source
2
Je viens de découvrir un problème avec ça. Il ne descend qu'une seule ligne d'héritage. Si, en cours de route, vous avez une base avec à la fois une classe de base et l'interface que vous recherchez, cela ne suit que le chemin de la classe.
Groxx
1
@Groxx: Vrai. Je viens de remarquer que je le mentionne dans la réponse: "Ce n'est pas trop mal si le type générique est une classe (comme c'est le cas dans ce cas) mais c'est plus difficile pour les interfaces. Voici le code d'une classe"
Jon Skeet
1
Et si vous n'avez pas moyen de connaître <T>? Comme, il peut s'agir d'un entier ou d'une chaîne, mais vous ne le savez pas. Cela génère, semble-t-il, de faux négatifs ... donc vous n'avez pas de T à utiliser, vous regardez juste à travers les propriétés d'un objet et l'un est une liste. Comment savez-vous qu'il s'agit d'une liste pour pouvoir la décoller? Je veux dire par là que vous n'avez pas de T nulle part ni de type à utiliser. Vous pouvez deviner chaque type (est-ce List <int>? Est-ce List <string>?) Mais ce que vous voulez savoir c'est EST CETTE LISTE AA? Cette question semble difficile à répondre.
@RiverC: Oui, vous avez raison - il est assez difficile de répondre, pour diverses raisons. Si vous ne parlez que d'une classe, ce n'est pas trop mal ... vous pouvez continuer à marcher dans l'arbre d'héritage et voir si vous frappez List<T>sous une forme ou une autre. Si vous incluez des interfaces, c'est vraiment délicat.
Jon Skeet
3
ne pourriez-vous pas remplacer la boucle IsInstanceOfGenericTypepar un appel à IsAssignableFromau lieu de l'opérateur d'égalité ( ==)?
slawekwin
7

Vous pouvez utiliser du code plus court en utilisant dynamique bien que cela puisse être plus lent que la réflexion pure:

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
la source
7

Ce sont mes deux méthodes d'extension préférées qui couvrent la plupart des cas marginaux de vérification de type générique:

Marche avec:

  • Plusieurs interfaces (génériques)
  • Classes de base multiples (génériques)
  • A une surcharge qui `` sortira '' le type générique spécifique s'il renvoie true (voir le test unitaire pour des exemples):

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

Voici un test pour démontrer la fonctionnalité (de 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);


    }
Wiebe Tijsma
la source
0
return list.GetType().IsGenericType;
Stan R.
la source
3
C'est correct pour une question différente. Pour cette question, c'est incorrect, car elle ne résout (nettement moins que) la moitié du problème.
Groxx
1
La réponse de Stan R répond en fait à la question posée, mais ce que l'OP signifiait vraiment était "Tester si l'objet est d'un type générique particulier en C #", pour lequel cette réponse est en effet incomplète.
yoyo
les gens me votent à la baisse parce que j'ai répondu à la question dans le contexte de "est un" type générique plutôt que "est d'un" type générique. L'anglais est ma deuxième langue et de telles nuances de langue me passent souvent, à ma défense, l'OP n'a pas spécifiquement demandé de tester contre un type spécifique et dans le titre demande "est de" type générique ... je ne sais pas pourquoi je mérite des votes négatifs pour une question ambiguë.
Stan R.
2
Maintenant vous le savez et vous pouvez améliorer votre réponse pour être plus précise et correcte.
Peter Ivan