确定一个反射类型是否可以转换为另一个反射类型。

5
在 .net (C#) 中,如果通过反射发现了两种类型,是否有可能确定其中一个类型可以被强制转换为另一个类型(隐式和/或显式)。
我试图创建一个库,允许用户指定一个类型上的属性映射到另一个类型上的属性。如果两个属性具有匹配的类型,则一切都很好,但是我想要能够允许它们映射具有可用的隐式/显式转换的属性。因此,如果他们有...
class from  
{
  public int IntProp{get;set;}
}

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

他们可以说从IntProp到LongProp将被分配(因为存在隐式转换)。但如果他们说它映射到DateTimeProp,我将能够确定没有可用的转换并抛出异常。

5个回答

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;
            }
        }

这应该可以解决隐式和显式转换的问题(包括数字类型、类等)。

这样的性能表现非常差。它编译了一个从未被使用的表达式。它使用异常来进行流程控制。这不是一个好的解决方案。 - Ben

5
这里提供的实现可能不太美观,但我相信它覆盖了所有情况(包括隐式/显式运算符、可空装箱/拆箱、基本类型转换、标准强制转换)。请注意,说一个转换“可能”成功与说它“一定”成功是有区别的(后者几乎不可能确定)。如需更多详细信息、全面单元测试和隐式版本,请查看我的帖子这里
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));
}

3
要直接回答您的问题... 如果你通过反射发现了两种类型,是否可以确定其中一种可以强制转换为另一种?(隐式和/或显式) ...你可以使用类似这样的东西:
to.GetType().IsAssignableFrom(from.GetType());

Type.IsAssignableFrom()方法可以用于您的目的。即使只是稍微更高效,这也会大大减少冗余代码量,比使用TypeConverters要简洁得多。

10
根据 MSDN,IsAssignableFrom 只考虑相等性、继承、接口和泛型,而不考虑强制转换运算符。 - Bryan Matthews
这应该是被接受的答案。使用try/catch完成所需功能而不会有太大的性能损失。 - André Boonzaaijer
这似乎没有处理强制转换(例如,将0作为Int32可以转换为十进制数,但它返回false),但我还是点赞了,因为这是一个我不知道但目前需要的功能。 - mlhDev

1
最好研究一下TypeConverter。

0

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接