169 votes

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

Je voudrais effectuer un test pour savoir si un objet est d'un 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 de travers et comment dois-je effectuer ce test ?

244voto

Mehrdad Afshari Points 204872

Si vous voulez vérifier si c'est 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 exacte des types. Retourner false ne signifie pas nécessairement list is List<T> renvoie à false (c'est-à-dire que l'objet ne peut pas être assigné à une List<T> variable).

13 votes

Mais cela ne permet pas de détecter les sous-types. Voir ma réponse. C'est aussi beaucoup plus difficile pour les interfaces :(

1 votes

L'appel à GetGenericTypeDefinition sera rejeté s'il ne s'agit pas d'un type générique. Vérifiez d'abord cela.

0 votes

@JonSkeet vous pouvez détecter les sous-types en utilisant list.GetType().BaseType propriété.

97voto

Jon Skeet Points 692016

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

Ce n'est pas très simple, malheureusement. Ce n'est pas trop grave si le type générique est une classe (comme c'est le cas ici) mais c'est plus difficile pour les interfaces. Voici le code pour 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 l'impression qu'il y a peut-être des cas limites gênants autour de cela, mais je n'arrive pas à en trouver un qui échoue pour le moment.

2 votes

Je viens de découvrir un problème à ce sujet. Il ne descend que sur 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 va que dans le sens de la classe.

1 votes

@Groxx : Vrai. Je viens de remarquer que je le mentionne dans la réponse : "Ce n'est pas trop grave si le type générique est une classe (comme c'est le cas ici) mais c'est plus difficile pour les interfaces. Voici le code pour une classe".

1 votes

Et si vous n'avez aucun moyen de connaître <T> ? Par exemple, ça peut être int, ou string, mais vous ne le savez pas. Cela génère, semble-t-il, des faux négatifs... donc vous n'avez pas de T à utiliser, vous regardez simplement les propriétés d'un objet et l'une d'entre elles est une liste. Comment savez-vous que c'est une liste pour pouvoir la décortiquer ? Je veux dire par là que vous n'avez pas de T ni de type à utiliser. Vous pourriez deviner chaque type (est-ce List<int> ? est-ce List<string> ?) mais ce que vous voulez savoir est C'EST UNE LISTE ? Il semble difficile de répondre à cette question.

12voto

Zidad Points 3756

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

Fonctionne avec :

  • Interfaces multiples (génériques)
  • Classes de base multiples (génériques)
  • Possède une surcharge qui fera sortir le type générique spécifique s'il retourne vrai (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);

    }

8voto

David Desmaisons Points 396

Vous pouvez utiliser un code plus court en utilisant la 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();

0voto

CodeRush Points 165
public static string WhatIsMyType<T>()
{
    return typeof(T).NameWithGenerics();
}

public static string NameWithGenerics(this Type type)
{
    if (type == null)
        throw new ArgumentNullException(nameof(type));

    if (type.IsArray)
        return $"{type.GetElementType()?.Name}[]";

    if (!type.IsGenericType) 
        return type.Name;

    var name = type.GetGenericTypeDefinition().Name;
    var index = name.IndexOf('`');
    var newName = index == -1 ? name : name.Substring(0, index);

    var list = type.GetGenericArguments().Select(NameWithGenerics).ToList();
    return $"{newName}<{string.Join(",", list)}>";
}

Maintenant, testez avec ceci :

Console.WriteLine(WhatIsMyType<IEnumerable<string>>());
Console.WriteLine(WhatIsMyType<List<int>>());
Console.WriteLine(WhatIsMyType<IList<int>>());
Console.WriteLine(WhatIsMyType<List<ContentBlob>>());
Console.WriteLine(WhatIsMyType<int[]>());
Console.WriteLine(WhatIsMyType<ContentBlob>());
Console.WriteLine(WhatIsMyType<Dictionary<string, Dictionary<int, int>>>());

et vous obtiendrez

IEnumerable<String>
List<Int32>
IList<Int32>
List<ContentBlob>
Int32[]
ContentBlob
Dictionary<String,Dictionary<Int32,Int32>>

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X