开发者

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 if c and the current [instance of] Type represent the same type, or if the current [instance of] Type is in the inheritance hierarchy of c, or if the current [instance of] Type is an interface that c implements, or if c is a generic type parameter and the current [instance of] Type represents one of the constraints of c. false if none of these conditions are true, or if c is a null 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 NotABases are implicitly castable to Bases. 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 Cars are Vehicles; 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

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜