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


309

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

public class GenericClass<T> : GenericInterface<T>
{
}

public class Test : GenericClass<SomeType>
{
}

Existe-t-il un moyen de savoir si un Typeobjet est dérivé GenericClass?

t.IsSubclassOf(typeof(GenericClass<>))

ne marche pas.

Réponses:


430

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
C'est un bon morceau de code, je dois dire. L'implémentation de la boucle while évite également les performances de récursion inutiles. C'est une solution élégante et belle à une question méta-générique.
EnocNRoll - AnandaGopal Pardue

2
J'ai ajouté cette méthode à ma classe statique ReflectionUtils dans mon framework, et je l'ai également adaptée comme méthode d'extension pour l'objet en définissant toCheck dans la méthode comme Type toCheck = obj.GetType (); étant donné "cet objet obj" est le premier paramètre.
EnocNRoll - AnandaGopal Pardue

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

2
Cela retournera également vrai si toCheck est le type générique que vous recherchez.
oillio

14
Cela ne fonctionne que pour l'héritage de type concret ... Cas de test: bool attendu = vrai; bool actual = Program.IsSubclassOfRawGeneric (typeof (IEnumerable <>), typeof (List <string>)); Assert.AreEqual (attendu, réel); // échoue
Bobby

90

(Republié en raison d'une réécriture massive)

La réponse du code de JaredPar est fantastique, mais j'ai une astuce qui la rendrait inutile si vos types génériques ne sont pas basés sur des paramètres de type valeur. J'étais accroché sur la raison pour laquelle l'opérateur "is" ne fonctionnerait pas, j'ai donc également documenté les résultats de mon expérimentation pour référence future. Veuillez améliorer cette réponse pour améliorer encore sa clarté.

POINTE:

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

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

IsSubclassOf ()

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

typeof(GenericClass<>)

alors qu'il fonctionnera avec

typeof(GenericClass<SomeType>)

Par conséquent, le code suivant fonctionnera pour toute dérivation de GenericClass <>, en supposant que vous êtes prêt à tester en fonction de SomeType:

typeof(Test).IsSubclassOf(typeof(GenericClass<SomeType>))

La seule fois où je peux imaginer que vous voudriez tester par GenericClass <> est dans un scénario de cadre de plug-in.


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

Au moment de la conception, C # ne permet pas l'utilisation de génériques sans paramètre car ils ne sont pas essentiellement un type CLR complet à 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. Par ailleurs, l'opérateur "is" ne peut pas non plus évaluer les types génériques sans paramètre.

L'opérateur «is» testera toute la chaîne d'héritage, y compris les interfaces.

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

bool IsTypeof<T>(object t)
{
    return (t is T);
}

C'est en quelque sorte redondant, mais j'ai pensé que j'irais de l'avant et le visualiserais pour tout le monde.

Donné

var t = new Test();

Les lignes de code suivantes renverraient true:

bool test1 = IsTypeof<GenericInterface<SomeType>>(t);

bool test2 = IsTypeof<GenericClass<SomeType>>(t);

bool test3 = IsTypeof<Test>(t);

D'un autre côté, si vous voulez quelque chose de spécifique à GenericClass, vous pouvez le rendre plus spécifique, je suppose, comme ceci:

bool IsTypeofGenericClass<SomeType>(object t)
{
    return (t is GenericClass<SomeType>);
}

Ensuite, vous testeriez comme ceci:

bool test1 = IsTypeofGenericClass<SomeType>(t);

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

3
Il convient de noter que le compilateur est parfaitement satisfait de .IsSubclassOf (typeof (GenericClass <>)), il ne fait tout simplement pas ce que vous souhaitez.
user2880616

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

Le but de toute la discussion était de savoir quand vous n'avez qu'un Typeobjet.
Jonathan Wood

@JonathanWood c'est pourquoi ma réponse ci-dessus concerne l' typeofopérateur. Selon les documents: "L'opérateur typeof est utilisé pour obtenir l'objet System.Type pour un type."
EnocNRoll - AnandaGopal Pardue

33

J'ai travaillé sur certains de ces échantillons et j'ai constaté qu'ils manquaient dans certains cas. Cette version fonctionne avec toutes sortes de génériques: types, interfaces et définitions de type de ceux-ci.

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

protected class FooBase
{
}

protected class FooImplementor
    : FooBase, IFooInterface
{
}

protected class GenericFooBase
    : FooImplementor, IGenericFooInterface<object>
{

}

protected class GenericFooImplementor<T>
    : FooImplementor, IGenericFooInterface<T>
{
}


[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<string>().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<string>().GetType()
        .InheritsOrImplements(typeof(IGenericFooInterface<int>)), 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);
}

2
Je suis perplexe à propos de la méthode ResolveGenericTypeDefinition. La variable "shouldUseGenericType" se voit vraiment attribuer la valeur: !parent.IsGenericType || parent.GetGenericTypeDefinition() == parent; vous remplacez donc cette variable par l'expansion de l'instruction if: if (parent.IsGenericType && shouldUseGenericType) et vous obtenez if (parent.IsGenericType && (!parent.IsGenericType || parent.GetGenericTypeDefinition() == parent)) ce qui se réduit alors à if (parent.IsGenericType && parent.GetGenericTypeDefinition() == parent)) parent = parent.GetGenericTypeDefinition();
Michael Blackburn

2
Ce qui semble ne rien faire. S'il s'agissait de types de valeur qui seraient similaires à int j = 0; if (j is an int && j == 0) { j=0; } Est-ce que je reçois mes références égales et mes valeurs mélangées? 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.
Michael Blackburn

1
@MichaelBlackburn tache sur :) j'ai refactorisé cela pour être simplement: retourner parent.IsGenericType? parent.GetGenericTypeDefinition (): parent;
AaronHS

3
si c'est déjà le même que le parent, retournez-le! et il appelle trop souvent GetGenericTypeDef. Il suffit de l'appeler une fois
AaronHS

1
Désolé, je vais ajouter un nouveau commentaire ci-dessous.
Menno Deij - van Rijswijk

26

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

public static class ReflexionExtension
{
    public static bool IsSubClassOfGeneric(this Type child, Type parent)
    {
        if (child == parent)
            return false;

        if (child.IsSubclassOf(parent))
            return true;

        var parameters = parent.GetGenericArguments();
        var isParameterLessGeneric = !(parameters != null && parameters.Length > 0 &&
            ((parameters[0].Attributes & TypeAttributes.BeforeFieldInit) == TypeAttributes.BeforeFieldInit));

        while (child != null && child != typeof(object))
        {
            var cur = GetFullTypeDefinition(child);
            if (parent == cur || (isParameterLessGeneric && cur.GetInterfaces().Select(i => GetFullTypeDefinition(i)).Contains(GetFullTypeDefinition(parent))))
                return true;
            else if (!isParameterLessGeneric)
                if (GetFullTypeDefinition(parent) == cur && !cur.IsInterface)
                {
                    if (VerifyGenericArguments(GetFullTypeDefinition(parent), cur))
                        if (VerifyGenericArguments(parent, child))
                            return true;
                }
                else
                    foreach (var item in child.GetInterfaces().Where(i => GetFullTypeDefinition(parent) == GetFullTypeDefinition(i)))
                        if (VerifyGenericArguments(parent, item))
                            return true;

            child = child.BaseType;
        }

        return false;
    }

    private static Type GetFullTypeDefinition(Type type)
    {
        return type.IsGenericType ? type.GetGenericTypeDefinition() : type;
    }

    private static bool VerifyGenericArguments(Type parent, Type child)
    {
        Type[] childArguments = child.GetGenericArguments();
        Type[] parentArguments = parent.GetGenericArguments();
        if (childArguments.Length == parentArguments.Length)
            for (int i = 0; i < childArguments.Length; i++)
                if (childArguments[i].Assembly != parentArguments[i].Assembly || childArguments[i].Name != parentArguments[i].Name || childArguments[i].Namespace != parentArguments[i].Namespace)
                    if (!childArguments[i].IsSubclassOf(parentArguments[i]))
                        return false;

        return true;
    }
}

Voici mes 70 76 cas de test:

[TestMethod]
public void IsSubClassOfGenericTest()
{
    Assert.IsTrue(typeof(ChildGeneric).IsSubClassOfGeneric(typeof(BaseGeneric<>)), " 1");
    Assert.IsFalse(typeof(ChildGeneric).IsSubClassOfGeneric(typeof(WrongBaseGeneric<>)), " 2");
    Assert.IsTrue(typeof(ChildGeneric).IsSubClassOfGeneric(typeof(IBaseGeneric<>)), " 3");
    Assert.IsFalse(typeof(ChildGeneric).IsSubClassOfGeneric(typeof(IWrongBaseGeneric<>)), " 4");
    Assert.IsTrue(typeof(IChildGeneric).IsSubClassOfGeneric(typeof(IBaseGeneric<>)), " 5");
    Assert.IsFalse(typeof(IWrongBaseGeneric<>).IsSubClassOfGeneric(typeof(ChildGeneric2<>)), " 6");
    Assert.IsTrue(typeof(ChildGeneric2<>).IsSubClassOfGeneric(typeof(BaseGeneric<>)), " 7");
    Assert.IsTrue(typeof(ChildGeneric2<Class1>).IsSubClassOfGeneric(typeof(BaseGeneric<>)), " 8");
    Assert.IsTrue(typeof(ChildGeneric).IsSubClassOfGeneric(typeof(BaseGeneric<Class1>)), " 9");
    Assert.IsFalse(typeof(ChildGeneric).IsSubClassOfGeneric(typeof(WrongBaseGeneric<Class1>)), "10");
    Assert.IsTrue(typeof(ChildGeneric).IsSubClassOfGeneric(typeof(IBaseGeneric<Class1>)), "11");
    Assert.IsFalse(typeof(ChildGeneric).IsSubClassOfGeneric(typeof(IWrongBaseGeneric<Class1>)), "12");
    Assert.IsTrue(typeof(IChildGeneric).IsSubClassOfGeneric(typeof(IBaseGeneric<Class1>)), "13");
    Assert.IsFalse(typeof(BaseGeneric<Class1>).IsSubClassOfGeneric(typeof(ChildGeneric2<Class1>)), "14");
    Assert.IsTrue(typeof(ChildGeneric2<Class1>).IsSubClassOfGeneric(typeof(BaseGeneric<Class1>)), "15");
    Assert.IsFalse(typeof(ChildGeneric).IsSubClassOfGeneric(typeof(ChildGeneric)), "16");
    Assert.IsFalse(typeof(IChildGeneric).IsSubClassOfGeneric(typeof(IChildGeneric)), "17");
    Assert.IsFalse(typeof(IBaseGeneric<>).IsSubClassOfGeneric(typeof(IChildGeneric2<>)), "18");
    Assert.IsTrue(typeof(IChildGeneric2<>).IsSubClassOfGeneric(typeof(IBaseGeneric<>)), "19");
    Assert.IsTrue(typeof(IChildGeneric2<Class1>).IsSubClassOfGeneric(typeof(IBaseGeneric<>)), "20");
    Assert.IsFalse(typeof(IBaseGeneric<Class1>).IsSubClassOfGeneric(typeof(IChildGeneric2<Class1>)), "21");
    Assert.IsTrue(typeof(IChildGeneric2<Class1>).IsSubClassOfGeneric(typeof(IBaseGeneric<Class1>)), "22");
    Assert.IsFalse(typeof(IBaseGeneric<Class1>).IsSubClassOfGeneric(typeof(BaseGeneric<Class1>)), "23");
    Assert.IsTrue(typeof(BaseGeneric<Class1>).IsSubClassOfGeneric(typeof(IBaseGeneric<Class1>)), "24");
    Assert.IsFalse(typeof(IBaseGeneric<>).IsSubClassOfGeneric(typeof(BaseGeneric<>)), "25");
    Assert.IsTrue(typeof(BaseGeneric<>).IsSubClassOfGeneric(typeof(IBaseGeneric<>)), "26");
    Assert.IsTrue(typeof(BaseGeneric<Class1>).IsSubClassOfGeneric(typeof(IBaseGeneric<>)), "27");
    Assert.IsFalse(typeof(IBaseGeneric<Class1>).IsSubClassOfGeneric(typeof(IBaseGeneric<Class1>)), "28");
    Assert.IsTrue(typeof(BaseGeneric2<Class1>).IsSubClassOfGeneric(typeof(IBaseGeneric<Class1>)), "29");
    Assert.IsFalse(typeof(IBaseGeneric<>).IsSubClassOfGeneric(typeof(BaseGeneric2<>)), "30");
    Assert.IsTrue(typeof(BaseGeneric2<>).IsSubClassOfGeneric(typeof(IBaseGeneric<>)), "31");
    Assert.IsTrue(typeof(BaseGeneric2<Class1>).IsSubClassOfGeneric(typeof(IBaseGeneric<>)), "32");
    Assert.IsTrue(typeof(ChildGenericA).IsSubClassOfGeneric(typeof(BaseGenericA<,>)), "33");
    Assert.IsFalse(typeof(ChildGenericA).IsSubClassOfGeneric(typeof(WrongBaseGenericA<,>)), "34");
    Assert.IsTrue(typeof(ChildGenericA).IsSubClassOfGeneric(typeof(IBaseGenericA<,>)), "35");
    Assert.IsFalse(typeof(ChildGenericA).IsSubClassOfGeneric(typeof(IWrongBaseGenericA<,>)), "36");
    Assert.IsTrue(typeof(IChildGenericA).IsSubClassOfGeneric(typeof(IBaseGenericA<,>)), "37");
    Assert.IsFalse(typeof(IWrongBaseGenericA<,>).IsSubClassOfGeneric(typeof(ChildGenericA2<,>)), "38");
    Assert.IsTrue(typeof(ChildGenericA2<,>).IsSubClassOfGeneric(typeof(BaseGenericA<,>)), "39");
    Assert.IsTrue(typeof(ChildGenericA2<ClassA, ClassB>).IsSubClassOfGeneric(typeof(BaseGenericA<,>)), "40");
    Assert.IsTrue(typeof(ChildGenericA).IsSubClassOfGeneric(typeof(BaseGenericA<ClassA, ClassB>)), "41");
    Assert.IsFalse(typeof(ChildGenericA).IsSubClassOfGeneric(typeof(WrongBaseGenericA<ClassA, ClassB>)), "42");
    Assert.IsTrue(typeof(ChildGenericA).IsSubClassOfGeneric(typeof(IBaseGenericA<ClassA, ClassB>)), "43");
    Assert.IsFalse(typeof(ChildGenericA).IsSubClassOfGeneric(typeof(IWrongBaseGenericA<ClassA, ClassB>)), "44");
    Assert.IsTrue(typeof(IChildGenericA).IsSubClassOfGeneric(typeof(IBaseGenericA<ClassA, ClassB>)), "45");
    Assert.IsFalse(typeof(BaseGenericA<ClassA, ClassB>).IsSubClassOfGeneric(typeof(ChildGenericA2<ClassA, ClassB>)), "46");
    Assert.IsTrue(typeof(ChildGenericA2<ClassA, ClassB>).IsSubClassOfGeneric(typeof(BaseGenericA<ClassA, ClassB>)), "47");
    Assert.IsFalse(typeof(ChildGenericA).IsSubClassOfGeneric(typeof(ChildGenericA)), "48");
    Assert.IsFalse(typeof(IChildGenericA).IsSubClassOfGeneric(typeof(IChildGenericA)), "49");
    Assert.IsFalse(typeof(IBaseGenericA<,>).IsSubClassOfGeneric(typeof(IChildGenericA2<,>)), "50");
    Assert.IsTrue(typeof(IChildGenericA2<,>).IsSubClassOfGeneric(typeof(IBaseGenericA<,>)), "51");
    Assert.IsTrue(typeof(IChildGenericA2<ClassA, ClassB>).IsSubClassOfGeneric(typeof(IBaseGenericA<,>)), "52");
    Assert.IsFalse(typeof(IBaseGenericA<ClassA, ClassB>).IsSubClassOfGeneric(typeof(IChildGenericA2<ClassA, ClassB>)), "53");
    Assert.IsTrue(typeof(IChildGenericA2<ClassA, ClassB>).IsSubClassOfGeneric(typeof(IBaseGenericA<ClassA, ClassB>)), "54");
    Assert.IsFalse(typeof(IBaseGenericA<ClassA, ClassB>).IsSubClassOfGeneric(typeof(BaseGenericA<ClassA, ClassB>)), "55");
    Assert.IsTrue(typeof(BaseGenericA<ClassA, ClassB>).IsSubClassOfGeneric(typeof(IBaseGenericA<ClassA, ClassB>)), "56");
    Assert.IsFalse(typeof(IBaseGenericA<,>).IsSubClassOfGeneric(typeof(BaseGenericA<,>)), "57");
    Assert.IsTrue(typeof(BaseGenericA<,>).IsSubClassOfGeneric(typeof(IBaseGenericA<,>)), "58");
    Assert.IsTrue(typeof(BaseGenericA<ClassA, ClassB>).IsSubClassOfGeneric(typeof(IBaseGenericA<,>)), "59");
    Assert.IsFalse(typeof(IBaseGenericA<ClassA, ClassB>).IsSubClassOfGeneric(typeof(IBaseGenericA<ClassA, ClassB>)), "60");
    Assert.IsTrue(typeof(BaseGenericA2<ClassA, ClassB>).IsSubClassOfGeneric(typeof(IBaseGenericA<ClassA, ClassB>)), "61");
    Assert.IsFalse(typeof(IBaseGenericA<,>).IsSubClassOfGeneric(typeof(BaseGenericA2<,>)), "62");
    Assert.IsTrue(typeof(BaseGenericA2<,>).IsSubClassOfGeneric(typeof(IBaseGenericA<,>)), "63");
    Assert.IsTrue(typeof(BaseGenericA2<ClassA, ClassB>).IsSubClassOfGeneric(typeof(IBaseGenericA<,>)), "64");
    Assert.IsFalse(typeof(BaseGenericA2<ClassB, ClassA>).IsSubClassOfGeneric(typeof(IBaseGenericA<ClassA, ClassB>)), "65");
    Assert.IsFalse(typeof(BaseGenericA<ClassB, ClassA>).IsSubClassOfGeneric(typeof(ChildGenericA2<ClassA, ClassB>)), "66");
    Assert.IsFalse(typeof(BaseGenericA2<ClassB, ClassA>).IsSubClassOfGeneric(typeof(BaseGenericA<ClassA, ClassB>)), "67");
    Assert.IsTrue(typeof(ChildGenericA3<ClassA, ClassB>).IsSubClassOfGeneric(typeof(BaseGenericB<ClassA, ClassB, ClassC>)), "68");
    Assert.IsTrue(typeof(ChildGenericA4<ClassA, ClassB>).IsSubClassOfGeneric(typeof(IBaseGenericB<ClassA, ClassB, ClassC>)), "69");
    Assert.IsFalse(typeof(ChildGenericA3<ClassB, ClassA>).IsSubClassOfGeneric(typeof(BaseGenericB<ClassA, ClassB, ClassC>)), "68-2");
    Assert.IsTrue(typeof(ChildGenericA3<ClassA, ClassB2>).IsSubClassOfGeneric(typeof(BaseGenericB<ClassA, ClassB, ClassC>)), "68-3");
    Assert.IsFalse(typeof(ChildGenericA3<ClassB2, ClassA>).IsSubClassOfGeneric(typeof(BaseGenericB<ClassA, ClassB, ClassC>)), "68-4");
    Assert.IsFalse(typeof(ChildGenericA4<ClassB, ClassA>).IsSubClassOfGeneric(typeof(IBaseGenericB<ClassA, ClassB, ClassC>)), "69-2");
    Assert.IsTrue(typeof(ChildGenericA4<ClassA, ClassB2>).IsSubClassOfGeneric(typeof(IBaseGenericB<ClassA, ClassB, ClassC>)), "69-3");
    Assert.IsFalse(typeof(ChildGenericA4<ClassB2, ClassA>).IsSubClassOfGeneric(typeof(IBaseGenericB<ClassA, ClassB, ClassC>)), "69-4");
    Assert.IsFalse(typeof(bool).IsSubClassOfGeneric(typeof(IBaseGenericB<ClassA, ClassB, ClassC>)), "70");
}

Classes et interfaces de test:

public class Class1 { }
public class BaseGeneric<T> : IBaseGeneric<T> { }
public class BaseGeneric2<T> : IBaseGeneric<T>, IInterfaceBidon { }
public interface IBaseGeneric<T> { }
public class ChildGeneric : BaseGeneric<Class1> { }
public interface IChildGeneric : IBaseGeneric<Class1> { }
public class ChildGeneric2<Class1> : BaseGeneric<Class1> { }
public interface IChildGeneric2<Class1> : IBaseGeneric<Class1> { }

public class WrongBaseGeneric<T> { }
public interface IWrongBaseGeneric<T> { }

public interface IInterfaceBidon { }

public class ClassA { }
public class ClassB { }
public class ClassC { }
public class ClassB2 : ClassB { }
public class BaseGenericA<T, U> : IBaseGenericA<T, U> { }
public class BaseGenericB<T, U, V> { }
public interface IBaseGenericB<ClassA, ClassB, ClassC> { }
public class BaseGenericA2<T, U> : IBaseGenericA<T, U>, IInterfaceBidonA { }
public interface IBaseGenericA<T, U> { }
public class ChildGenericA : BaseGenericA<ClassA, ClassB> { }
public interface IChildGenericA : IBaseGenericA<ClassA, ClassB> { }
public class ChildGenericA2<ClassA, ClassB> : BaseGenericA<ClassA, ClassB> { }
public class ChildGenericA3<ClassA, ClassB> : BaseGenericB<ClassA, ClassB, ClassC> { }
public class ChildGenericA4<ClassA, ClassB> : IBaseGenericB<ClassA, ClassB, ClassC> { }
public interface IChildGenericA2<ClassA, ClassB> : IBaseGenericA<ClassA, ClassB> { }

public class WrongBaseGenericA<T, U> { }
public interface IWrongBaseGenericA<T, U> { }

public interface IInterfaceBidonA { }

4
C'est la seule solution qui a fonctionné pour moi. Je n'ai trouvé aucune autre solution fonctionnant avec des classes ayant plusieurs paramètres de type. Je vous remercie.
Connor Clark

1
J'ai vraiment apprécié que vous ayez publié tous ces cas de test. Je pense que les cas 68 et 69 devraient être faux au lieu d'être vrais, car vous avez ClassB, ClassA à gauche et ClassA, ClassB à droite.
Grax32

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

1
@Grax: J'ai trouvé du temps pour faire la correction. J'ai ajouté la classe ClassB2, j'ai changé VerifyGenericArguments et j'ai ajouté un contrôle à l'appel de VerifyGenericArguments. J'ai également modifié les cas 68 et 69 et ajouté 68-2, 68-3, 68-4, 69-2, 69-3 et 69-4.
Xav987

1
Merci Monsieur. +1 pour la solution de travail ET la quantité énorme de cas de test (énorme pour moi, au moins).
Eldoïr

10

Le code de JaredPar fonctionne mais uniquement 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);
    }
}

4
Le whilecode de JaredPar couvre des niveaux illimités.
Jay

@jay ... et évite la récursivité.
Marc L.

1
@MarcL. Cela utilise la récursivité de queue, donc il devrait être trivial pour le compilateur d'optimiser la récursivité.
Darhuuk

10

Voici une petite méthode que j'ai créée pour vérifier qu'un objet est dérivé d'un type spécifique. Fonctionne très bien pour moi!

internal static bool IsDerivativeOf(this Type t, Type typeToCompare)
{
    if (t == null) throw new NullReferenceException();
    if (t.BaseType == null) return false;

    if (t.BaseType == typeToCompare) return true;
    else return t.BaseType.IsDerivativeOf(typeToCompare);
}

7

Cela peut être exagéré, mais j'utilise des méthodes d'extension comme les suivantes. Ils vérifient les interfaces ainsi que les sous-classes. Il peut également renvoyer le type qui a la définition générique spécifiée.

Par exemple pour l'exemple dans la question, il peut tester aussi bien l'interface générique que la classe générique. Le type renvoyé peut être utilisé avec GetGenericArgumentspour déterminer que le type d'argument générique est "SomeType".

/// <summary>
/// Checks whether this type has the specified definition in its ancestry.
/// </summary>   
public static bool HasGenericDefinition(this Type type, Type definition)
{
    return GetTypeWithGenericDefinition(type, definition) != null;
}

/// <summary>
/// Returns the actual type implementing the specified definition from the
/// ancestry of the type, if available. Else, null.
/// </summary>
public static Type GetTypeWithGenericDefinition(this Type type, Type definition)
{
    if (type == null)
        throw new ArgumentNullException("type");
    if (definition == null)
        throw new ArgumentNullException("definition");
    if (!definition.IsGenericTypeDefinition)
        throw new ArgumentException(
            "The definition needs to be a GenericTypeDefinition", "definition");

    if (definition.IsInterface)
        foreach (var interfaceType in type.GetInterfaces())
            if (interfaceType.IsGenericType
                && interfaceType.GetGenericTypeDefinition() == definition)
                return interfaceType;

    for (Type t = type; t != null; t = t.BaseType)
        if (t.IsGenericType && t.GetGenericTypeDefinition() == definition)
            return t;

    return null;
}

Bon post! Agréable de diviser les préoccupations en deux méthodes.
Wiebe Tijsma

4

En s'appuyant sur l'excellente réponse ci-dessus de fir3rpho3nixx et David Schmitt, j'ai modifié leur code et ajouté le test ShouldInheritOrImplementTypedGenericInterface (le dernier).

    /// <summary>
    /// Find out if a child type implements or inherits from the parent type.
    /// The parent type can be an interface or a concrete class, generic or non-generic.
    /// </summary>
    /// <param name="child"></param>
    /// <param name="parent"></param>
    /// <returns></returns>
    public static bool InheritsOrImplements(this Type child, Type parent)
    {
        var currentChild = parent.IsGenericTypeDefinition && child.IsGenericType ? child.GetGenericTypeDefinition() : child;

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

            currentChild = currentChild.BaseType != null && parent.IsGenericTypeDefinition && 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 = parent.IsGenericTypeDefinition && childInterface.IsGenericType
                    ? childInterface.GetGenericTypeDefinition()
                    : childInterface;

                return currentInterface == parent;
            });

    }

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

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

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

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

    [Test]
    public void ShouldNotInheritOrImplementGenericInterfaceByGenericSubclassNotCaringAboutGenericTypeParameter()
    {
        Assert.That(new GenericFooImplementor<string>().GetType()
            .InheritsOrImplements(typeof(IGenericFooInterface<int>)), Is.False);
    }

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

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

    [Test]
    public void ShouldInheritOrImplementTypedGenericInterface()
    {
        GenericFooImplementor<int> obj = new GenericFooImplementor<int>();
        Type t = obj.GetType();

        Assert.IsTrue(t.InheritsOrImplements(typeof(IGenericFooInterface<int>)));
        Assert.IsFalse(t.InheritsOrImplements(typeof(IGenericFooInterface<String>)));
    } 

4

Tout cela peut être fait facilement avec linq. Cela trouvera tous les types qui sont une sous-classe de la classe de base générique GenericBaseType.

    IEnumerable<Type> allTypes = Assembly.GetExecutingAssembly().GetTypes();

    IEnumerable<Type> mySubclasses = allTypes.Where(t => t.BaseType != null 
                                                            && t.BaseType.IsGenericType
                                                            && t.BaseType.GetGenericTypeDefinition() == typeof(GenericBaseType<,>));

C'était la seule solution qui fonctionnait pour moi. Simple et élégant. Je vous remercie.
silkfire

4

Solution simple: il suffit de créer et d'ajouter une deuxième interface non générique à la classe générique:

public interface IGenericClass
{
}

public class GenericClass<T> : GenericInterface<T>, IGenericClass
{
}

Ensuite , il suffit de vérifier pour cela de quelque façon que vous aimez utiliser is, as, IsAssignableFrom, etc.

if (thing is IGenericClass)
{
    // Do work
{

Évidemment, cela n'est possible que si vous avez la possibilité d'éditer la classe générique (que l'OP semble avoir), mais c'est un peu plus élégant et lisible que d'utiliser une méthode d'extension cryptique.


1
cependant, le simple fait de vérifier si quelque chose est de type IGenericClassne vous garantira pas GenericClassou GenericInterfaceest réellement étendu ou implémenté. Cela signifie que votre compilateur ne vous permettra pas non plus d'accéder aux membres de la classe générique.
B12Toaster

4

Ajouté à la réponse de @ jaredpar, voici ce que j'utilise pour vérifier les interfaces:

public static bool IsImplementerOfRawGeneric(this Type type, Type toCheck)
{
    if (toCheck.GetTypeInfo().IsClass)
    {
        return false;
    }

    return type.GetInterfaces().Any(interfaceType =>
    {
        var current = interfaceType.GetTypeInfo().IsGenericType ?
                    interfaceType.GetGenericTypeDefinition() : interfaceType;
        return current == toCheck;
    });
}

public static bool IsSubTypeOfRawGeneric(this Type type, Type toCheck)
{
    return type.IsInterface ?
          IsImplementerOfRawGeneric(type, toCheck)
        : IsSubclassOfRawGeneric(type, toCheck);
}

Ex:

Console.WriteLine(typeof(IList<>).IsSubTypeOfRawGeneric(typeof(IList<int>))); // true

Bon ajout. Vous a donné à vous et à Jaredpar une note positive. Mon seul commentaire est que vous avez inversé la position des types (d'après la réponse de jaredpar) à cause de la méthode d'extension. Je l'ai supprimé comme méthode d'extension et cela m'a un peu dérouté. Pas votre problème mais le mien. Je voulais juste donner la tête à la prochaine personne. Merci encore.
Tony

@Tony, merci pour le conseil mais la prochaine fois Mettez à jour la réponse.
johnny 5

@vexe, les tests sont importants, votre réponse originale est cassée, cela ne fonctionne que parce que vous l'avez testée sur une interface. Deuxièmement, vous gaspillez la puissance de traitement en exécutant cette fonction quel que soit le type.
johnny 5

2

JaredPar,

Cela n'a pas fonctionné pour moi si je passe typeof (type <>) à toCheck. Voici ce que j'ai changé.

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

La solution de JaredPar fonctionne en effet pour typeof(type<>)as toCheck. De plus, vous avez vraiment besoin de la vérification nulle comme dans la solution de JaredPar. De plus, je ne sais pas quoi faire d'autre en remplaçant cur == genericsa solution par cur.IsGenericType && generic.GetGenericTypeDefinition() == cur.GetGenericTypeDefinition()autre chose que de restreindre votre méthode pour qu'elle ne fonctionne que pour les types génériques. En d'autres termes, quelque chose comme ça échoue avec une exception:IsSubclassOfRawGeneric(typeof(MyClass), typeof(MyClass<>))
nawfal

2

@EnocNRoll - La réponse d'Ananda Gopal est intéressante, mais dans le cas où une instance n'est pas instanciée à l'avance ou si vous souhaitez vérifier avec une définition de type générique, je suggère cette méthode:

public static bool TypeIs(this Type x, Type d) {
    if(null==d) {
        return false;
    }

    for(var c = x; null!=c; c=c.BaseType) {
        var a = c.GetInterfaces();

        for(var i = a.Length; i-->=0;) {
            var t = i<0 ? c : a[i];

            if(t==d||t.IsGenericType&&t.GetGenericTypeDefinition()==d) {
                return true;
            }
        }
    }

    return false;
}

et l'utiliser comme:

var b = typeof(char[]).TypeIs(typeof(IList<>)); // true

Il y a quatre cas conditionnels lorsque les deux t(à tester) et dsont des types génériques et deux cas sont couverts par t==dqui sont (1) ni , tni dune définition générique ou (2) les deux sont des définitions génériques . Les autres cas sont l'un d'eux est une définition générique, c'est seulement quand dest déjà une définition générique que nous avons la chance de dire que a test ad mais pas l'inverse.

Il devrait fonctionner avec des classes ou des interfaces arbitraires que vous souhaitez tester, et renvoie ce que si vous testez une instance de ce type avec l' isopérateur.


0
Type _type = myclass.GetType();
PropertyInfo[] _propertyInfos = _type.GetProperties();
Boolean _test = _propertyInfos[0].PropertyType.GetGenericTypeDefinition() 
== typeof(List<>);

0

Vous pouvez essayer cette extension

    public static bool IsSubClassOfGenericClass(this Type type, Type genericClass,Type t)
    {
        return type.IsSubclassOf(genericClass.MakeGenericType(new[] { t }));
    }

0

tard dans le jeu à ce sujet ... moi aussi j'ai encore une autre permutation de la réponse de JarodPar.

voici Type.IsSubClassOf (Type) avec la permission du réflecteur:

    public virtual bool IsSubclassOf(Type c)
    {
        Type baseType = this;
        if (!(baseType == c))
        {
            while (baseType != null)
            {
                if (baseType == c)
                {
                    return true;
                }
                baseType = baseType.BaseType;
            }
            return false;
        }
        return false;
    }

à partir de cela, nous voyons qu'il ne fait rien de trop cray cray et est similaire à l'approche itérative de JaredPar. jusqu'ici tout va bien. voici ma version (avis de non-responsabilité: pas complètement testé, alors laissez-moi savoir si vous rencontrez des problèmes)

    public static bool IsExtension(this Type thisType, Type potentialSuperType)
    {
        //
        // protect ya neck
        //
        if (thisType == null || potentialSuperType == null || thisType == potentialSuperType) return false;

        //
        // don't need to traverse inheritance for interface extension, so check/do these first
        //
        if (potentialSuperType.IsInterface)
        {
            foreach (var interfaceType in thisType.GetInterfaces())
            {
                var tempType = interfaceType.IsGenericType ? interfaceType.GetGenericTypeDefinition() : interfaceType;

                if (tempType == potentialSuperType)
                {
                    return true;
                }
            }
        }

        //
        // do the concrete type checks, iterating up the inheritance chain, as in orignal
        //
        while (thisType != null && thisType != typeof(object))
        {
            var cur = thisType.IsGenericType ? thisType.GetGenericTypeDefinition() : thisType;

            if (potentialSuperType == cur)
            {
                return true;
            }

            thisType = thisType.BaseType;
        }
        return false;
    }

Fondamentalement, il s'agit simplement d'une méthode d'extension à System.Type - je l'ai fait pour limiter intentionnellement le type "thisType" aux types concrets, car mon utilisation immédiate est la requête LINQ "où" les prédicats contre les objets Type. Je suis sûr que tous les gens intelligents pourraient le transformer en une méthode statique efficace et polyvalente si vous en avez besoin :) le code fait quelques choses que le code de la réponse ne fait pas

  1. open's it jusqu'à "extension" générale - je considère l'héritage (think classes) ainsi que l'implémentation (interfaces); les noms de méthode et de paramètre sont modifiés pour mieux refléter cela
  2. entrée null-validation (ouais)
  3. entrée de même type (une classe ne peut pas s'étendre)
  4. exécution en court-circuit si le type en question est une interface; parce que GetInterfaces () renvoie toutes les interfaces implémentées (même celles implémentées dans les super-classes), vous pouvez simplement parcourir cette collection sans avoir à grimper dans l'arbre d'héritage

le reste est fondamentalement le même que le code de JaredPar

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.