web-dev-qa-db-fra.com

Utiliser IsAssignableFrom avec des types génériques «ouverts»

En utilisant la réflexion, j'essaie de trouver l'ensemble des types qui héritent d'une classe de base donnée. Il n'a pas fallu longtemps pour trouver des types simples, mais je suis perplexe en ce qui concerne les génériques.

Pour ce morceau de code, le premier IsAssignableFrom retourne vrai, mais le second retourne faux. Et pourtant, l'affectation finale se compile très bien.

class class1 { }
class class2 : class1 { }
class generic1<T> { }
class generic2<T> : generic1<T> { }

class Program
{
    static void Main(string[] args)
    {
        Type c1 = typeof(class1);
        Type c2 = typeof(class2);
        Console.WriteLine("c1.IsAssignableFrom(c2): {0}", c1.IsAssignableFrom(c2));

        Type g1 = typeof(generic1<>);
        Type g2 = typeof(generic2<>);
        Console.WriteLine("g1.IsAssignableFrom(g2): {0}", g1.IsAssignableFrom(g2));

        generic1<class1> cc = new generic2<class1>();
    }
}

Alors, comment puis-je déterminer au moment de l'exécution si une définition de type générique est dérivée d'une autre?

56
ThatBlairGuy

De la réponse à une autre question :

public static bool IsAssignableToGenericType(Type givenType, Type genericType)
{
    var interfaceTypes = givenType.GetInterfaces();

    foreach (var it in interfaceTypes)
    {
        if (it.IsGenericType && it.GetGenericTypeDefinition() == genericType)
            return true;
    }

    if (givenType.IsGenericType && givenType.GetGenericTypeDefinition() == genericType)
        return true;

    Type baseType = givenType.BaseType;
    if (baseType == null) return false;

    return IsAssignableToGenericType(baseType, genericType);
}

(Si vous aimez la réponse, veuillez voter pour la réponse liée car le code n'est pas le mien.)

80
Konrad Rudolph

Le code exact que vous avez publié ne renvoie pas de résultats surprenants.

Cela dit "faux":

Type g1 = typeof(generic1<>);
Type g2 = typeof(generic2<>);
Console.WriteLine("g1.IsAssignableFrom(g2): {0}", g1.IsAssignableFrom(g2));

Cela dit "vrai":

Type g1 = typeof(generic1<class1>);
Type g2 = typeof(generic2<class1>);
Console.WriteLine("g1.IsAssignableFrom(g2): {0}", g1.IsAssignableFrom(g2));

La différence est que les types génériques ouverts ne peuvent pas avoir d'instances, donc l'un n'est pas "assignable" à l'autre.

De la docs :

Renvoie true si c et le Type courant représentent le même type, ou si le Type courant est dans la hiérarchie d'héritage de c , ou si le Type courant est une interface que c implémente, ou si c est un paramètre de type générique et que le Type courant représente une des contraintes de c. false si aucune de ces conditions n'est vraie, ou si c est null.

Dans ce cas, clairement aucune de ces conditions n'est vraie. Et il y a une note supplémentaire:

Une définition de type générique n'est pas attribuable à partir d'un type construit fermé. Autrement dit, vous ne pouvez pas affecter le type construit fermé MyGenericList<int> (MyGenericList(Of Integer) dans Visual Basic) à une variable de type MyGenericList<T>.

11
Jon

Dans le cas suivant, utilisez la méthode fournie par Konrad Rudolph, comme: IsAssignableToGenericType (typeof (A), typeof (A <>)); // return false

Je pense que voici une meilleure réponse

public static bool IsAssignableFrom(Type extendType, Type baseType)
{
    while (!baseType.IsAssignableFrom(extendType))
    {
        if (extendType.Equals(typeof(object)))
        {
            return false;
        }
        if (extendType.IsGenericType && !extendType.IsGenericTypeDefinition)
        {
            extendType = extendType.GetGenericTypeDefinition();
        }
        else
        {
            extendType = extendType.BaseType;
        }
    }
    return true;
}

le cas de test, voir tilisation d'IsAssignableFrom avec des génériques C # pour plus de détails

using System;

/**
 * Sam Sha - yCoder.com
 *
 * */
namespace Test2
{
    class MainClass
    {
        public static void Main (string[] args)
        {
            string a = "ycoder";
            Console.WriteLine(a is object);
            A aa = new A();
            //Console.WriteLine(aa is A<>);//con't write code like this
            typeof(A<>).IsAssignableFrom(aa.GetType());//return false

            Trace(typeof(object).IsAssignableFrom(typeof(string)));//true
            Trace(typeof(A<>).IsAssignableFrom(typeof(A)));//false

            AAA aaa = new AAA();
            Trace("Use IsTypeOf:");
            Trace(IsTypeOf(aaa, typeof(A<>)));
            Trace(IsTypeOf(aaa, typeof(AA)));
            Trace(IsTypeOf(aaa, typeof(AAA<>)));

            Trace("Use IsAssignableFrom from stackoverflow - not right:");
            Trace(IsAssignableFrom(typeof(A), typeof(A<>))); // error
            Trace(IsAssignableFrom(typeof(AA), typeof(A<>)));
            Trace(IsAssignableFrom(typeof(AAA), typeof(A<>)));

            Trace("Use IsAssignableToGenericType:");
            Trace(IsAssignableToGenericType(typeof(A), typeof(A<>)));
            Trace(IsAssignableToGenericType(typeof(AA), typeof(A<>)));
            Trace(IsAssignableToGenericType(typeof(AAA), typeof(A<>)));
        }

        static void Trace(object log){
                Console.WriteLine(log);
        }

        public static bool IsTypeOf(Object o, Type baseType)
        {
            if (o == null || baseType == null)
            {
                return false;
            }
            bool result = baseType.IsInstanceOfType(o);
            if (result)
            {
                return result;
            }
            return IsAssignableFrom(o.GetType(), baseType);
        }

        public static bool IsAssignableFrom(Type extendType, Type baseType)
        {
            while (!baseType.IsAssignableFrom(extendType))
            {
                if (extendType.Equals(typeof(object)))
                {
                    return false;
                }
                if (extendType.IsGenericType && !extendType.IsGenericTypeDefinition)
                {
                    extendType = extendType.GetGenericTypeDefinition();
                }
                else
                {
                    extendType = extendType.BaseType;
                }
            }
            return true;
        }

        //from stackoverflow - not good enough
        public static bool IsAssignableToGenericType(Type givenType, Type genericType) {
            var interfaceTypes = givenType.GetInterfaces();

            foreach (var it in interfaceTypes)
                if (it.IsGenericType)
                    if (it.GetGenericTypeDefinition() == genericType) return true;

            Type baseType = givenType.BaseType;
            if (baseType == null) return false;

            return baseType.IsGenericType &&
                baseType.GetGenericTypeDefinition() == genericType ||
                IsAssignableToGenericType(baseType, genericType);
        }
    }

    class A{}
    class AA : A{}
    class AAA : AA{}
}
2
sam sha

Vous devez comparer le type contenu. Voir: Comment obtenir le type de T d'un membre d'une classe ou d'une méthode générique?

En d'autres termes, je pense que vous devez vérifier si le type contenu par la classe générique est assignable plutôt que la classe générique elle-même.

0
Nick

J'ai une approche différente qui résout ce problème, voici mes cours

public class Signal<T>{
   protected string Id {get; set;} //This must be here, I use a property because MemberInfo is returned in an array via GetMember() reflection function
   //Some Data and Logic And stuff that involves T
}

public class OnClick : Signal<string>{}

Maintenant, si j'ai une instance de type OnClick mais je ne le sais pas, et je veux savoir si j'ai une instance de quelque chose qui hérite de Signal <> de tout type? je fais ça

Type type = GetTypeWhomISuspectMightBeAGenericSignal();

PropertyInfo secretProperty = type.GetProperty("Id", BindingFlags.NonPublic | BindingFlags.Instance);

Type SpecificGenericType = secretProperty.DeclaringType; //This is the trick

bool IsMyTypeInheriting = SpecificGenericType.IsGenericType && SpecificGenericType.GetGenericTypeDefinition() == typeof(Signal<>); //This way we are getting the genericTypeDefinition and comparing it to any other genericTypeDefinition of the same argument length.

Donc, cela fonctionne pour moi, ce n'est pas récursif, et il utilise une astuce via une propriété désignée. Il a des limites qu'il est difficile d'écrire une fonction qui vérifie l'assignation pour tous les génériques. Mais pour un type spécifique, cela fonctionne

De toute évidence, vous devez vérifier si les conditions () sont meilleures et autres, mais ce sont les lignes brutes requises pour évaluer l'assignation d'un type à son générique de base, de cette façon.

J'espère que cela t'aides

0
Helical