使用带有‘ open’泛型类型的 IsAssignableFrom

通过使用反射,我试图找到从给定基类继承的类型集。用不了多长时间就可以找到简单类型,但是当涉及到泛型时,我就被难住了。

对于这段代码,第一个 IsAssignableFrom 返回 true,但是第二个返回 false。然而,最终的作业编译得很好。

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

那么,如何在运行时确定一个泛型类型定义是否从另一个泛型类型定义派生?

36495 次浏览

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.

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

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

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

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