5

In .net (C#), If you have two types discovered through reflection is it possible to determine if one can be cast to the other? (implicit and/or explicit).

What I'm trying to do is create a library that allows users to specify that a property on one type is mapped to a property on another type. Everything is fine if the two properties have matching types, but I'd like to be able to allow them to map properties where an implicit/explicit cast is available. So if they have

class from  
{
  public int IntProp{get;set;}
}

class to
{
  public long LongProp{get;set;}
  public DateTime DateTimeProp{get;set;}
}

they would be able to say that from.IntProp will be assigned to to.LongProp (as an implicity cast exists). But if they said that it mapped to DateTimeProp I'd be able to determine that there's no available cast and throw an exception.

Alex McMahon
  • 268
  • 1
  • 4
  • 11

5 Answers5

5

Here's an implementation isn't pretty but which I believe covers all cases (implicit/explicit operators, nullable boxing/unboxing, primitive type conversions, standard casts). Note that there's a difference between saying that a conversion MIGHT succeed vs. that it WILL succeed (which is almost impossible to know for sure). For more details, a comprehensive unit test, and an implicit version, check out my post here.

public static bool IsCastableTo(this Type from, Type to)
{
    // from https://web.archive.org/web/20141017005721/http://www.codeducky.org/10-utilities-c-developers-should-know-part-one/ 
    Throw.IfNull(from, "from");
    Throw.IfNull(to, "to");

    // explicit conversion always works if to : from OR if 
    // there's an implicit conversion
    if (from.IsAssignableFrom(to) || from.IsImplicitlyCastableTo(to))
    {
        return true;
    }

    // for nullable types, we can simply strip off the nullability and evaluate the underyling types
    var underlyingFrom = Nullable.GetUnderlyingType(from);
    var underlyingTo = Nullable.GetUnderlyingType(to);
    if (underlyingFrom != null || underlyingTo != null)
    {
        return (underlyingFrom ?? from).IsCastableTo(underlyingTo ?? to);
    }

    if (from.IsValueType)
    {
        try
        {
            ReflectionHelpers.GetMethod(() => AttemptExplicitCast<object, object>())
                .GetGenericMethodDefinition()
                .MakeGenericMethod(from, to)
                .Invoke(null, new object[0]);
            return true;
        }
        catch (TargetInvocationException ex)
        {
            return !(
                ex.InnerException is RuntimeBinderException
                // if the code runs in an environment where this message is localized, we could attempt a known failure first and base the regex on it's message
                && Regex.IsMatch(ex.InnerException.Message, @"^Cannot convert type '.*' to '.*'$")
            );
        }
    }
    else
    {
        // if the from type is null, the dynamic logic above won't be of any help because 
        // either both types are nullable and thus a runtime cast of null => null will 
        // succeed OR we get a runtime failure related to the inability to cast null to 
        // the desired type, which may or may not indicate an actual issue. thus, we do 
        // the work manually
        return from.IsNonValueTypeExplicitlyCastableTo(to);
    }
}

private static bool IsNonValueTypeExplicitlyCastableTo(this Type from, Type to)
{
    if ((to.IsInterface && !from.IsSealed)
        || (from.IsInterface && !to.IsSealed))
    {
        // any non-sealed type can be cast to any interface since the runtime type MIGHT implement
        // that interface. The reverse is also true; we can cast to any non-sealed type from any interface
        // since the runtime type that implements the interface might be a derived type of to.
        return true;
    }

    // arrays are complex because of array covariance 
    // (see http://msmvps.com/blogs/jon_skeet/archive/2013/06/22/array-covariance-not-just-ugly-but-slow-too.aspx).
    // Thus, we have to allow for things like var x = (IEnumerable<string>)new object[0];
    // and var x = (object[])default(IEnumerable<string>);
    var arrayType = from.IsArray && !from.GetElementType().IsValueType ? from
        : to.IsArray && !to.GetElementType().IsValueType ? to
        : null;
    if (arrayType != null)
    {
        var genericInterfaceType = from.IsInterface && from.IsGenericType ? from
            : to.IsInterface && to.IsGenericType ? to
            : null;
        if (genericInterfaceType != null)
        {
            return arrayType.GetInterfaces()
                .Any(i => i.IsGenericType
                    && i.GetGenericTypeDefinition() == genericInterfaceType.GetGenericTypeDefinition()
                    && i.GetGenericArguments().Zip(to.GetGenericArguments(), (ia, ta) => ta.IsAssignableFrom(ia) || ia.IsAssignableFrom(ta)).All(b => b));
        }
    }

    // look for conversion operators. Even though we already checked for implicit conversions, we have to look
    // for operators of both types because, for example, if a class defines an implicit conversion to int then it can be explicitly
    // cast to uint
    const BindingFlags conversionFlags = BindingFlags.Public | BindingFlags.Static | BindingFlags.FlattenHierarchy;
    var conversionMethods = from.GetMethods(conversionFlags)
        .Concat(to.GetMethods(conversionFlags))
        .Where(m => (m.Name == "op_Explicit" || m.Name == "op_Implicit")
            && m.Attributes.HasFlag(MethodAttributes.SpecialName)
            && m.GetParameters().Length == 1 
            && (
                // the from argument of the conversion function can be an indirect match to from in
                // either direction. For example, if we have A : B and Foo defines a conversion from B => Foo,
                // then C# allows A to be cast to Foo
                m.GetParameters()[0].ParameterType.IsAssignableFrom(from)
                || from.IsAssignableFrom(m.GetParameters()[0].ParameterType)
            )
        );

    if (to.IsPrimitive && typeof(IConvertible).IsAssignableFrom(to))
    {
        // as mentioned above, primitive convertible types (i. e. not IntPtr) get special 
        // treatment in the sense that if you can convert from Foo => int, you can convert
        // from Foo => double as well
        return conversionMethods.Any(m => m.ReturnType.IsCastableTo(to));
    }

    return conversionMethods.Any(m => m.ReturnType == to);
}

private static void AttemptExplicitCast<TFrom, TTo>()
{
    // based on the IL generated from
    // var x = (TTo)(dynamic)default(TFrom);

    var binder = Microsoft.CSharp.RuntimeBinder.Binder.Convert(CSharpBinderFlags.ConvertExplicit, typeof(TTo), typeof(TypeHelpers));
    var callSite = CallSite<Func<CallSite, TFrom, TTo>>.Create(binder);
    callSite.Target(callSite, default(TFrom));
}
howcheng
  • 2,211
  • 2
  • 17
  • 24
ChaseMedallion
  • 20,860
  • 17
  • 88
  • 152
5
public static bool HasConversionOperator( Type from, Type to )
        {
            Func<Expression, UnaryExpression> bodyFunction = body => Expression.Convert( body, to );
            ParameterExpression inp = Expression.Parameter( from, "inp" );
            try
            {
                // If this succeeds then we can cast 'from' type to 'to' type using implicit coercion
                Expression.Lambda( bodyFunction( inp ), inp ).Compile();
                return true;
            }
            catch( InvalidOperationException )
            {
                return false;
            }
        }

This should do the trick for implicit and explicit conversions (including numeric types, classes, etc.)

ephere
  • 147
  • 2
  • 5
  • This would perform very poorly. It compiles an expression that is never used. It uses an exception as for flow control. This is not a good solution. – Ben Oct 22 '13 at 21:45
3

To directly answer your question ...

If you have two types discovered through reflection is it possible to determine if one can be cast to the other? (implicit and/or explicit)

... you can use something similar to this :

to.GetType().IsAssignableFrom(from.GetType());

The Type.IsAssignableFrom() method can be used for exactly your purpose. This would also be considerably less verbose (even if only marginally more performant) than using TypeConverters.

Alex Marshall
  • 10,162
  • 15
  • 72
  • 117
  • 10
    According to MSDN, IsAssignableFrom only considers equality, inheritance, interfaces, and generics, not cast operators. http://msdn.microsoft.com/en-us/library/system.type.isassignablefrom.aspx – Bryan Matthews Jul 01 '10 at 19:27
  • This should be the accepted answer. Does what is asked without heavy performance penalty using try/catch. – André Boonzaaijer Jun 21 '19 at 07:58
  • This doesn't appear to handle casting (e.g. 0 as Int32 can be cast to decimal but it returns false) but I upvote since it was a feature I wasn't aware of and need currently. – mlhDev Apr 27 '23 at 19:06
1

It would be better to look into TypeConverter's.

leppie
  • 115,091
  • 17
  • 196
  • 297
0

So, probably you mean duck typing or structural typing? There are several implementations that will dynamically generate the required proxies.

For example:

http://www.deftflux.net/blog/page/Duck-Typing-Project.aspx

Bender
  • 41
  • 2