Check if types are castable / subclasses
I have they type of two members as strings - and not as a Type instance. How can I check if the two types are castable? Let's say string one is "System.Windows.Forms.Label" and the other one is "System.Windows.Forms.Control". How can I check if the first one is a subclass (or implicit castable) of the second one? Is this possible by using开发者_如何学运维 reflection?
Thanks for you support!
It might seem like you should use Type.IsAssignableFrom
but note carefully the documentation:
public virtual bool IsAssignableFrom(Type c)
true
ifc
and the current [instance of]Type
represent the same type, or if the current [instance of]Type
is in the inheritance hierarchy ofc
, or if the current [instance of]Type
is an interface thatc
implements, or ifc
is a generic type parameter and the current [instance of]Type
represents one of the constraints ofc
.false
if none of these conditions aretrue
, or ifc
is anull
reference (Nothing
in Visual Basic).
In particular:
class Base { }
clase NotABase { public static implicit operator Base(NotABase o) { // } }
Console.WriteLine(typeof(Base).IsAssignableFrom(typeof(NotABase)));
will print False
on the console even though NotABase
s are implicitly castable to Base
s. So, to handle casting, we could use reflection like so:
static class TypeExtensions {
public static bool IsCastableTo(this Type from, Type to) {
if (to.IsAssignableFrom(from)) {
return true;
}
return from.GetMethods(BindingFlags.Public | BindingFlags.Static)
.Any(
m => m.ReturnType == to &&
(m.Name == "op_Implicit" ||
m.Name == "op_Explicit")
);
}
}
Usage:
Console.WriteLine(typeof(string).IsCastableTo(typeof(int))); // false
Console.WriteLine(typeof(NotABase).IsCastableTo(typeof(Base))); // true
And for your case
// from is string representing type name, e.g. "System.Windows.Forms.Label"
// to is string representing type name, e.g. "System.Windows.Forms.Control"
Type fromType = Type.GetType(from);
Type toType = Type.GetType(to);
bool castable = from.IsCastableTo(to);
I was helped by this discussion, Thanks.
I modified nawfal's code to solve problem about primitive types.
Now it returns correct results.
typeof(short).IsCastableTo(typeof(int)); // True
typeof(short).IsCastableTo(typeof(int), implicitly:true); // True
typeof(int).IsCastableTo(typeof(short)); // True
typeof(int).IsCastableTo(typeof(short), implicitly:true); // False
The code is as below.
public static bool IsCastableTo(this Type from, Type to, bool implicitly = false)
{
return to.IsAssignableFrom(from) || from.HasCastDefined(to, implicitly);
}
static bool HasCastDefined(this Type from, Type to, bool implicitly)
{
if ((from.IsPrimitive || from.IsEnum) && (to.IsPrimitive || to.IsEnum))
{
if (!implicitly)
return from==to || (from!=typeof(Boolean) && to!=typeof(Boolean));
Type[][] typeHierarchy = {
new Type[] { typeof(Byte), typeof(SByte), typeof(Char) },
new Type[] { typeof(Int16), typeof(UInt16) },
new Type[] { typeof(Int32), typeof(UInt32) },
new Type[] { typeof(Int64), typeof(UInt64) },
new Type[] { typeof(Single) },
new Type[] { typeof(Double) }
};
IEnumerable<Type> lowerTypes = Enumerable.Empty<Type>();
foreach (Type[] types in typeHierarchy)
{
if ( types.Any(t => t == to) )
return lowerTypes.Any(t => t == from);
lowerTypes = lowerTypes.Concat(types);
}
return false; // IntPtr, UIntPtr, Enum, Boolean
}
return IsCastDefined(to, m => m.GetParameters()[0].ParameterType, _ => from, implicitly, false)
|| IsCastDefined(from, _ => to, m => m.ReturnType, implicitly, true);
}
static bool IsCastDefined(Type type, Func<MethodInfo, Type> baseType,
Func<MethodInfo, Type> derivedType, bool implicitly, bool lookInBase)
{
var bindinFlags = BindingFlags.Public | BindingFlags.Static
| (lookInBase ? BindingFlags.FlattenHierarchy : BindingFlags.DeclaredOnly);
return type.GetMethods(bindinFlags).Any(
m => (m.Name=="op_Implicit" || (!implicitly && m.Name=="op_Explicit"))
&& baseType(m).IsAssignableFrom(derivedType(m)));
}
If you can convert these strings to Type
objects then your best bet is Type.IsAssignableFrom.
Beware though, this only tells you if two Type
instances are compatible at a CLR level. This will not take into account such things as user defined conversions or other C# semantics.
How about:
public bool IsCastable(String type0, String type1)
{
return Type.GetType(type1).IsAssignableFrom(Type.GetType(type0));
}
This is same as Jason's answer, but solves some problems with his solution.
public static bool IsCastableTo(this Type from, Type to)
{
return to.IsAssignableFrom(from)
|| to.GetConvertOperators().Any(m => m.GetParameters()[0].ParameterType.IsAssignableFrom(from))
|| from.GetConvertOperators(true).Any(m => to.IsAssignableFrom(m.ReturnType));
}
public static IEnumerable<MethodInfo> GetConvertOperators(this Type type, bool lookInBase = false)
{
var bindinFlags = BindingFlags.Public
| BindingFlags.Static
| (lookInBase ? BindingFlags.FlattenHierarchy : BindingFlags.DeclaredOnly);
return type.GetMethods(bindinFlags).Where(m => m.Name == "op_Implicit" || m.Name == "op_Explicit");
}
This should handle situations that arise due to inheritance as well. For instance:
class Mammal { public static implicit operator Car (Mammal o) { return null; } }
class Cow : Mammal { }
class Vehicle { }
class Car : Vehicle { }
Here the implicit relationship is between Mammal
and Car
, but since Cow
is Mammal
as well, there exist an implicit conversion from Cow
to Car
. But all Car
s are Vehicle
s; hence a Cow
would go into a Vehicle
.
Cow c = null;
Vehicle v = c; //legal
So
typeof(Cow).IsCastableTo(typeof(Vehicle)); //true
prints true, even though no direct convert operator exist between Cow
and Vehicle
.
The solution above fails for primitive types where the conversion is built directly into the language than the framework, so something like
typeof(short).IsCastableTo(typeof(int));
fails. Afaik, only manually handling will help. You will get complete list of implicit and explicit conversion for numeric types and other primitive types from msdn.
Edit:
The IsCastableTo
function could be little more "DRY" perhaps at the cost of being less readable, but I like it :)
public static bool IsCastableTo(this Type from, Type to)
{
return to.IsAssignableFrom(from)
|| IsCastDefined(to, m => m.GetParameters()[0].ParameterType, _ => from, false)
|| IsCastDefined(from, _ => to, m => m.ReturnType, true);
}
//little irrelevant DRY method
static bool IsCastDefined(Type type, Func<MethodInfo, Type> baseType, Func<MethodInfo, Type> derivedType,
bool lookInBase)
{
var bindinFlags = BindingFlags.Public
| BindingFlags.Static
| (lookInBase ? BindingFlags.FlattenHierarchy : BindingFlags.DeclaredOnly);
return type.GetMethods(bindinFlags).Any(m => (m.Name == "op_Implicit" || m.Name == "op_Explicit")
&& baseType(m).IsAssignableFrom(derivedType(m)));
}
Simplest way is value.GetType().IsSubclassOf(typeof(Control)) Basically, Type.IsSubclassOf method do what you need
精彩评论