Determine if a reflected type can be cast to another reflected type

前端 未结 5 1150
我在风中等你
我在风中等你 2021-01-21 08:07

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 tryi

5条回答
  •  借酒劲吻你
    2021-01-21 08:46

    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())
                    .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)new object[0];
        // and var x = (object[])default(IEnumerable);
        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()
    {
        // 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>.Create(binder);
        callSite.Target(callSite, default(TFrom));
    }
    

提交回复
热议问题