Using IsAssignableFrom with 'open' generic types

C#GenericsReflectionTypes

C# Problem Overview


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?

C# Solutions


Solution 1 - C#

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

Solution 2 - C#

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

Solution 3 - C#

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

Solution 4 - C#

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

Solution 5 - C#

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

Solution 6 - C#

You need to compare the contained type. See: https://stackoverflow.com/questions/557340/c-generic-list-t-how-to-get-the-type-of-t

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.

Solution 7 - C#

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

Solution 8 - C#

Creating an extension method and using link you can do this :

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

Solution 9 - C#

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

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionThatBlairGuyView Question on Stackoverflow
Solution 1 - C#Konrad RudolphView Answer on Stackoverflow
Solution 2 - C#JonView Answer on Stackoverflow
Solution 3 - C#sam shaView Answer on Stackoverflow
Solution 4 - C#HelicalView Answer on Stackoverflow
Solution 5 - C#João SequeiraView Answer on Stackoverflow
Solution 6 - C#NickView Answer on Stackoverflow
Solution 7 - C#Ken in NHView Answer on Stackoverflow
Solution 8 - C#pedrodelView Answer on Stackoverflow
Solution 9 - C#ffetechView Answer on Stackoverflow