简体   繁体   中英

Using IsAssignableFrom with 'open' generic types

Using reflection, I'm attempting to find the set of types which inherit from a given base class. It didn't take long to figure out for simple types, but I'm stumped when it comes to generics.

For this piece of code, the first IsAssignableFrom returns true, but the second returns false. And yet, the final assignment compiles just fine.

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

So how do I determine at run time whether one generic type definition is derived from another?

From the answer to another 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);
}

The exact code you posted does not return surprising results.

This says "false":

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

This says "true":

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

The difference is that open generic types cannot have instances, so one is not "assignable" to the other.

From the docs :

Returns true if c and the current Type represent the same type, or if the current Type is in the inheritance hierarchy of c , or if the current Type is an interface that c implements, or if c is a generic type parameter and the current Type represents one of the constraints of c . false if none of these conditions are true, or if c is null .

In this case, clearly none of these conditions are true. And there's an extra note:

A generic type definition is not assignable from a closed constructed type. That is, you cannot assign the closed constructed type MyGenericList<int> ( MyGenericList(Of Integer) in Visual Basic) to a variable of type MyGenericList<T> .

In the following case use the method Konrad Rudolph provided could be wrong, like: IsAssignableToGenericType(typeof(A), typeof(A<>));// return false

I think here's a better answer

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

the test case, see Using IsAssignableFrom with C# generics for detail

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

I have a different Approach that resolves this issue, Here are my classes

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

Now if I have an instance of type OnClick but I dont know that, and I want to find out if I have an instance of anything which inherits from Signal<> of any type? I do this

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.

So this works for me, its not recursive, and it uses a trick via a designated property. It has limitations that its hard to write a function that checks assignability for all generics ever. But for a specific type it works

Obviously you need to check if() conditions better and stuff, but these are the Raw lines required to evaluate assignability of a type to its base generic, this way.

Hope this helps

My two cents. IMHO it doesn't make much sense to separate implements, derives or the original functionality of IsAssignableFrom,

Constructing from the answers previously given, this is how I do it:

public static bool ImplementsOrDerives(this Type @this, Type from)
{
    if(from is null)
    {
        return false;
    }
    else if(!from.IsGenericType)
    {
        return from.IsAssignableFrom(@this);
    }
    else if(!from.IsGenericTypeDefinition)
    {
        return from.IsAssignableFrom(@this);
    }
    else if(from.IsInterface)
    {
        foreach(Type @interface in @this.GetInterfaces())
        {
            if(@interface.IsGenericType && @interface.GetGenericTypeDefinition() == from)
            {
                return true;
            }
        }
    }

    if(@this.IsGenericType && @this.GetGenericTypeDefinition() == from)
    {
        return true;
    }

    return @this.BaseType?.ImplementsOrDerives(from) ?? false;
}

You need to compare the contained type. See: How to get the type of T from a member of a generic class or method?

In other words, I think you need to check whether the type being contained by the generic class is assignable rather than the generic class itself.

@konrad_ruldolph's answer is mostly correct, but it requires you to know the base type/interface is an open generic. I propose an improvement that combines a non-generic test with a loop to test for generic match.

    public static class Ext
    {
        public static bool IsAssignableToGeneric(
            this Type assignableFrom,
            Type assignableTo)
        {
            bool IsType(Type comparand)
                => assignableTo.IsAssignableFrom(comparand)
                    || (comparand.IsGenericType
                    && comparand.GetGenericTypeDefinition() == assignableTo);

            while (assignableFrom != null)
            {
                if (IsType(assignableFrom)
                    || assignableFrom
                    .GetInterfaces()
                    .Any(IsType))
                {
                    return true;
                }

                assignableFrom = assignableFrom.BaseType;
            }

            return false;
        }
    }

创建扩展方法并使用链接您可以这样做:

public static bool IsAssignableFromGenericInterface(this Type type, Type genericInterface) => type.GetInterfaces().Any(@interface => @interface.IsAssignableFrom(genericInterface));

I also would like to share my code with you. Here the generic arguments are checked for any compatibility and is working with interfaces.

public static bool IsAssignableToGeneric(this Type sourceType, Type targetType)
{
    bool IsAssignable(Type comperand)
    {
        if (comperand.IsAssignableTo(targetType))
            return true;

        if (comperand.IsGenericType && targetType.IsGenericType && comperand.GetGenericTypeDefinition() == targetType.GetGenericTypeDefinition())
        {
            for (int i = 0; i < targetType.GenericTypeArguments.Length; i++)
            {
                Type comperandArgument = comperand.GenericTypeArguments[i];
                Type targetArgument = targetType.GenericTypeArguments[i];

                // suggestion for improvement: forward the type check recursivley also here
                if (!comperandArgument.IsGenericTypeParameter && !targetArgument.IsGenericTypeParameter && !comperandArgument.IsAssignableTo(targetArgument))
                    return false;
            }

            return true;
        }

        return false;
    }

    if (IsAssignable(sourceType))
        return true;

    if (targetType.IsInterface && sourceType.GetInterfaces().Any(IsAssignable))
        return true;

    return false;
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM