Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Check if types are castable / subclasses

Tags:

c#

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!

like image 707
user256974 Avatar asked Jan 22 '10 18:01

user256974


3 Answers

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);
like image 112
jason Avatar answered Sep 20 '22 20:09

jason


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)));
}
like image 21
pkyhd Avatar answered Sep 17 '22 20:09

pkyhd


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.

like image 29
JaredPar Avatar answered Sep 20 '22 20:09

JaredPar