332 votes

Vérifiez si une classe est dérivée d'une classe générique

J'ai une classe générique dans mon projet avec des classes dérivées.

public class GenericClass : GenericInterface
{
}

public class Test : GenericClass
{
}

Existe-t-il un moyen de savoir si un objet de type Type est dérivé de GenericClass ?

t.IsSubclassOf(typeof(GenericClass<>))

ne fonctionne pas.

466voto

JaredPar Points 333733

Essayez ce code

static bool IsSubclassOfRawGeneric(Type generic, Type toCheck) {
    while (toCheck != null && toCheck != typeof(object)) {
        var cur = toCheck.IsGenericType ? toCheck.GetGenericTypeDefinition() : toCheck;
        if (generic == cur) {
            return true;
        }
        toCheck = toCheck.BaseType;
    }
    return false;
}

4 votes

Il s'agit d'un morceau de code très élégant, je dois dire. L'implémentation de la boucle while évite également le surcoût de performance de la récursivité inutile. C'est une solution élégante et belle à une question méta-générique.

2 votes

J'ai ajouté cette méthode à ma classe statique ReflectionUtils dans mon framework, et je l'ai également adaptée comme une méthode d'extension pour object en définissant toCheck à l'intérieur de la méthode comme Type toCheck = obj.GetType(); étant donné que "this object obj" est le premier paramètre.

12 votes

La boucle while ne se cassera pas si le type de toCheck n'est pas une classe (c'est-à-dire, une interface). Cela provoquera une NullReferenceException.

97voto

EnocNRoll Points 2897

(Reposté en raison d'une refonte massive)

La réponse au code de JaredPar est fantastique, mais j'ai un conseil qui le rendrait inutile si vos types génériques ne sont pas basés sur des paramètres de type valeur. J'étais bloqué sur la raison pour laquelle l'opérateur "is" ne fonctionnait pas, donc j'ai également documenté les résultats de mes expérimentations pour référence future. Veuillez améliorer cette réponse pour en renforcer la clarté.

CONSEIL :

Si vous vous assurez que l'implémentation de votre classe générique hérite d'une classe de base abstraite non générique telle que GenericClassBase, vous pourriez poser la même question sans aucun problème du tout comme ceci :

typeof(Test).IsSubclassOf(typeof(GenericClassBase))

IsSubclassOf()

Mes tests indiquent que IsSubclassOf() ne fonctionne pas sur des types génériques sans paramètres tels que

typeof(GenericClass<>)

alors qu'il fonctionnera avec

typeof(GenericClass)

Par conséquent, le code suivant fonctionnera pour n'importe quelle dérivation de GenericClass<>, en supposant que vous acceptiez de tester en fonction de SomeType :

typeof(Test).IsSubclassOf(typeof(GenericClass))

Le seul moment où je peux imaginer que vous souhaiteriez tester par GenericClass<> est dans un scénario de framework de plug-in.


Réflexions sur l'opérateur "is"

À la conception, C# n'autorise pas l'utilisation de génériques sans paramètres car ils ne représentent essentiellement pas un type complet du CLR à ce stade. Par conséquent, vous devez déclarer des variables génériques avec des paramètres, et c'est pourquoi l'opérateur "is" est si puissant pour travailler avec des objets. Incidemment, l'opérateur "is" ne peut pas non plus évaluer les types génériques sans paramètres.

L'opérateur "is" testera l'ensemble de la chaîne d'héritage, y compris les interfaces.

Ainsi, étant donné une instance de n'importe quel objet, la méthode suivante fera l'affaire :

bool EstTypeDe(objet t)
{
    return (t is T);
}

C'est un peu redondant, mais j'ai pensé que je le visualiserais quand même pour tout le monde.

Étant donné

var t = new Test();

Les lignes de code suivantes renverraient true :

bool test1 = EstTypeDe>(t);

bool test2 = EstTypeDe>(t);

bool test3 = EstTypeDe(t);

En revanche, si vous voulez quelque chose de spécifique à GenericClass, vous pourriez le rendre plus spécifique, je suppose, comme ceci :

bool EstTypeDeGenericClass(objet t)
{
    return (t is GenericClass);
}

Ensuite, vous testeriez comme ceci :

bool test1 = EstTypeDeGenericClass(t);

2 votes

+1 pour l'analyse et les tests. De plus, votre réponse a été très utile dans mon cas.

3 votes

Il convient de noter que le compilateur accepte parfaitement .IsSubclassOf(typeof(GenericClass<>)), il ne fait tout simplement pas ce que vous pourriez souhaiter.

2 votes

Mais que se passe-t-il si SomeType n'est pas connu au moment de la compilation?

34voto

fir3rpho3nixx Points 191

J'ai travaillé sur certains de ces exemples et j'ai constaté qu'ils manquaient parfois. Cette version fonctionne avec tous types de génériques : types, interfaces et définitions de types associées.

public static bool InheritsOrImplements(this Type child, Type parent)
{
    parent = ResolveGenericTypeDefinition(parent);

    var currentChild = child.IsGenericType
                           ? child.GetGenericTypeDefinition()
                           : child;

    while (currentChild != typeof (object))
    {
        if (parent == currentChild || HasAnyInterfaces(parent, currentChild))
            return true;

        currentChild = currentChild.BaseType != null
                       && currentChild.BaseType.IsGenericType
                           ? currentChild.BaseType.GetGenericTypeDefinition()
                           : currentChild.BaseType;

        if (currentChild == null)
            return false;
    }
    return false;
}

private static bool HasAnyInterfaces(Type parent, Type child)
{
    return child.GetInterfaces()
        .Any(childInterface =>
        {
            var currentInterface = childInterface.IsGenericType
                ? childInterface.GetGenericTypeDefinition()
                : childInterface;

            return currentInterface == parent;
        });
}

private static Type ResolveGenericTypeDefinition(Type parent)
{
    var shouldUseGenericType = true;
    if (parent.IsGenericType && parent.GetGenericTypeDefinition() != parent)
        shouldUseGenericType = false;

    if (parent.IsGenericType && shouldUseGenericType)
        parent = parent.GetGenericTypeDefinition();
    return parent;
}

Voici également les tests unitaires :

protected interface IFooInterface
{
}

protected interface IGenericFooInterface
{
}

protected class FooBase
{
}

protected class FooImplementor
    : FooBase, IFooInterface
{
}

protected class GenericFooBase
    : FooImplementor, IGenericFooInterface
{

}

protected class GenericFooImplementor
    : FooImplementor, IGenericFooInterface
{
}

[Test]
public void Should_inherit_or_implement_non_generic_interface()
{
    Assert.That(typeof(FooImplementor)
        .InheritsOrImplements(typeof(IFooInterface)), Is.True);
}

[Test]
public void Should_inherit_or_implement_generic_interface()
{
    Assert.That(typeof(GenericFooBase)
        .InheritsOrImplements(typeof(IGenericFooInterface<>)), Is.True);
}

[Test]
public void Should_inherit_or_implement_generic_interface_by_generic_subclass()
{
    Assert.That(typeof(GenericFooImplementor<>)
        .InheritsOrImplements(typeof(IGenericFooInterface<>)), Is.True);
}

[Test]
public void Should_inherit_or_implement_generic_interface_by_generic_subclass_not_caring_about_generic_type_parameter()
{
    Assert.That(new GenericFooImplementor().GetType()
        .InheritsOrImplements(typeof(IGenericFooInterface<>)), Is.True);
}

[Test]
public void Should_not_inherit_or_implement_generic_interface_by_generic_subclass_not_caring_about_generic_type_parameter()
{
    Assert.That(new GenericFooImplementor().GetType()
        .InheritsOrImplements(typeof(IGenericFooInterface)), Is.False);
}

[Test]
public void Should_inherit_or_implement_non_generic_class()
{
    Assert.That(typeof(FooImplementor)
        .InheritsOrImplements(typeof(FooBase)), Is.True);
}

[Test]
public void Should_inherit_or_implement_any_base_type()
{
    Assert.That(typeof(GenericFooImplementor<>)
        .InheritsOrImplements(typeof(FooBase)), Is.True);
}

0 votes

C'était la réponse que je cherchais! Fonctionne pour les types concrets ET pour les interfaces, merci :-)

2 votes

Je suis perplexe au sujet de la méthode ResolveGenericTypeDefinition. La variable "shouldUseGenericType" est vraiment assignée à la valeur: !parent.IsGenericType || parent.GetGenericTypeDefinition() == parent; Donc, vous remplacez cette variable par l'expansion de l'instruction if: if (parent.IsGenericType && shouldUseGenericType) et vous obtenez if (parent.IsGenericType && (!parent.IsGenericType || parent.GetGenericTypeDefinition() == parent)) qui se réduit ensuite à if (parent.IsGenericType && parent.GetGenericTypeDefinition() == parent)) parent = parent.GetGenericTypeDefinition();

2 votes

Ce qui semblerait ne rien faire. Si ces étaient des types de valeur, ce serait semblable à int j = 0; if (j est un int && j == 0) { j=0; } Est-ce que je mélange les références égales et les égalités de valeur? Je pensais qu'il n'y avait qu'une seule instance de chaque type en mémoire, donc deux variables qui pointent vers le même type pointent en fait vers le même emplacement en mémoire.

31voto

Xav987 Points 31

Il me semble que cette implémentation fonctionne dans les cas plus (classe générique et interface avec ou sans paramètres initiés, peu importe le nombre d’enfants et de paramètres) :

Voici mon 70 cas de test :

Les classes et interfaces pour les tests :

5 votes

C'est la seule solution qui a fonctionné pour moi. Je n'ai trouvé aucune autre solution qui fonctionnait avec des classes ayant plusieurs paramètres de type. Merci.

1 votes

J'ai vraiment apprécié votre publication de tous ces cas de test. Je pense que les cas 68 et 69 devraient être faux au lieu de vrai, car vous avez ClassB, ClassA à gauche et ClassA, ClassB à droite.

0 votes

Tu as raison, @Grax. Je n'ai pas le temps de faire la correction maintenant, mais je mettrai à jour mon message dès que ce sera fait. Je pense que la correction doit être apportée dans la méthode "VerifyGenericArguments"

11voto

user53564 Points 169

Le code de JaredPar fonctionne mais seulement pour un niveau d'héritage. Pour des niveaux d'héritage illimités, utilisez le code suivant

public bool IsTypeDerivedFromGenericType(Type typeToCheck, Type genericType)
{
    if (typeToCheck == typeof(object))
    {
        return false;
    }
    else if (typeToCheck == null)
    {
        return false;
    }
    else if (typeToCheck.IsGenericType && typeToCheck.GetGenericTypeDefinition() == genericType)
    {
        return true;
    }
    else
    {
        return IsTypeDerivedFromGenericType(typeToCheck.BaseType, genericType);
    }
}

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