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
ifc
and the currentType
represent the same type, or if the currentType
is in the inheritance hierarchy ofc
, or if the currentType
is an interface thatc
implements, or ifc
is a generic type parameter and the currentType
represents one of the constraints ofc
.false
if none of these conditions are true, or ifc
isnull
.
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 typeMyGenericList<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;
}
}
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;
}
精彩评论