【问题标题】:How to tell if Type A is implicitly convertible to Type B如何判断 A 型是否可隐式转换为 B 型
【发布时间】:2010-02-08 19:30:23
【问题描述】:

给定类型 a 和类型 b,我如何在运行时确定是否存在从 a 到 b 的隐式转换?

如果这没有意义,请考虑以下方法:

public PropertyInfo GetCompatibleProperty<T>(object instance, string propertyName)
{
   var property = instance.GetType().GetProperty(propertyName);

   bool isCompatibleProperty = !property.PropertyType.IsAssignableFrom(typeof(T));
   if (!isCompatibleProperty) throw new Exception("OH NOES!!!");

   return property;   
}

这是我想要工作的调用代码:

// Since string.Length is an int property, and ints are convertible
// to double, this should work, but it doesn't. :-(
var property = GetCompatibleProperty<double>("someStringHere", "Length");

【问题讨论】:

    标签: .net reflection type-conversion implicit-conversion


    【解决方案1】:

    请注意,IsAssignableFrom 不能解决您的问题。你必须像这样使用反射。注意处理原始类型的明确需要;这些列表符合规范的第 6.1.2 节(隐式数字转换)。

    static class TypeExtensions { 
        static Dictionary<Type, List<Type>> dict = new Dictionary<Type, List<Type>>() {
            { typeof(decimal), new List<Type> { typeof(sbyte), typeof(byte), typeof(short), typeof(ushort), typeof(int), typeof(uint), typeof(long), typeof(ulong), typeof(char) } },
            { typeof(double), new List<Type> { typeof(sbyte), typeof(byte), typeof(short), typeof(ushort), typeof(int), typeof(uint), typeof(long), typeof(ulong), typeof(char), typeof(float) } },
            { typeof(float), new List<Type> { typeof(sbyte), typeof(byte), typeof(short), typeof(ushort), typeof(int), typeof(uint), typeof(long), typeof(ulong), typeof(char), typeof(float) } },
            { typeof(ulong), new List<Type> { typeof(byte), typeof(ushort), typeof(uint), typeof(char) } },
            { typeof(long), new List<Type> { typeof(sbyte), typeof(byte), typeof(short), typeof(ushort), typeof(int), typeof(uint), typeof(char) } },
            { typeof(uint), new List<Type> { typeof(byte), typeof(ushort), typeof(char) } },
            { typeof(int), new List<Type> { typeof(sbyte), typeof(byte), typeof(short), typeof(ushort), typeof(char) } },
            { typeof(ushort), new List<Type> { typeof(byte), typeof(char) } },
            { typeof(short), new List<Type> { typeof(byte) } }
        };
        public static bool IsCastableTo(this Type from, Type to) { 
            if (to.IsAssignableFrom(from)) { 
                return true; 
            }
            if (dict.ContainsKey(to) && dict[to].Contains(from)) {
                return true;
            }
            bool castable = from.GetMethods(BindingFlags.Public | BindingFlags.Static) 
                            .Any( 
                                m => m.ReturnType == to &&  
                                (m.Name == "op_Implicit" ||  
                                m.Name == "op_Explicit")
                            ); 
            return castable; 
        } 
    } 
    

    用法:

    bool b = typeof(A).IsCastableTo(typeof(B));
    

    【讨论】:

    • 这会告诉我们它有一个隐式或显式的转换方法。不是是否可以在特定类型之间进行隐式转换。
    • 为什么不使用可枚举的 Any 扩展?
    • @Jason:您应该将其命名为IsCastableFrom,并反转参数以匹配类似框架方法的命名。
    • @280Z28:你是对的;事后看来IsCastableFrom 会更好。
    • 隐式/显式运算符不仅可以在from 类型上声明,还可以在to 类型上声明。然后需要检查tofrom类型上算子的met​​hodinfo的返回类型和参数类型。
    【解决方案2】:

    您需要考虑的隐式转换:

    • 身份
    • sbyte 到 short、int、long、float、double 或 decimal
    • byte 到 short、ushort、int、uint、long、ulong、float、double 或 decimal
    • 短于 int、long、float、double 或 decimal
    • ushort 到 int、uint、long、ulong、float、double 或 decimal
    • int 到 long、float、double 或 decimal
    • uint 到 long、ulong、float、double 或 decimal
    • 长浮点数、双精度数或十进制数
    • ulong 为浮点数、双精度数或十进制数
    • char 到 ushort、int、uint、long、ulong、float、double 或 decimal
    • 浮点数加倍
    • 可空类型转换
    • 对象的引用类型
    • 派生类到基类
    • 实现接口的类
    • 基本接口的接口
    • 当数组具有相同的维数时,从源元素类型到目标元素类型存在隐式转换,并且源元素类型和目标元素类型是引用类型时,数组到数组
    • System.Array 的数组类型
    • IList 及其基接口的数组类型
    • 将类型委托给 System.Delegate
    • 拳击转换
    • 枚举类型为 System.Enum
    • 用户定义的转换 (op_implicit)

    我假设您正在寻找后者。您需要编写类似于编译器的东西来涵盖所有这些。值得注意的是 System.Linq.Expressions.Expression 并没有尝试这一壮举。

    【讨论】:

    • 嘿。有趣的。我真的很惊讶没有固定的方式说“这种类型可以转换为另一种类型”。
    • "当数组长度相同且元素具有隐式转换时,数组到数组" 你确定吗?我不这么认为。事实上,我认为没有明确的转换。至于其余的,我认为我的方法涵盖了所有这些。因此,我一定误解了您所说的“您需要编写类似于编译器的东西来涵盖所有这些”的意思。
    • 是的,我确定。 Derived[] 可以隐式转换为 Base[]。
    • 好的,我同意,但这与您最初的陈述略有不同。存在从intdouble 的隐式转换,但没有从int[]double[] 的隐式转换。
    • 除 op_Implicit 和“数字”转换之外的所有上述内容都包含在 Type.IsAssignableFrom
    【解决方案3】:

    这个问题的公认答案可以处理很多情况,但不是全部。例如,这里只是一些没有正确处理的有效转换/转换:

    // explicit
    var a = (byte)2;
    var b = (decimal?)2M;
    
    // implicit
    double? c = (byte)2;
    decimal? d = 4L;
    

    下面,我发布了这个函数的另一个版本,它专门回答了隐式转换和转换的问题。更多细节,我用来验证它的测试套件,以及显式转换版本,请查看my post on the subject

    public static bool IsImplicitlyCastableTo(this Type from, Type to)
    {
        if (from == null) { throw new ArgumentNullException(nameof(from)); }
        if (to == null) { throw new ArgumentNullException(nameof(to)); }
    
        // not strictly necessary, but speeds things up
        if (to.IsAssignableFrom(from))
        {
            return true;
        }
    
        try
        {
            // overload of GetMethod() from http://www.codeducky.org/10-utilities-c-developers-should-know-part-two/ 
            // that takes Expression<Action>
            ReflectionHelpers.GetMethod(() => AttemptImplicitCast<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, @"^The best overloaded method match for 'System.Collections.Generic.List<.*>.Add(.*)' has some invalid arguments$")
            );
        }
    }
    
    private static void AttemptImplicitCast<TFrom, TTo>()
    {
        // based on the IL produced by:
        // dynamic list = new List<TTo>();
        // list.Add(default(TFrom));
        // We can't use the above code because it will mimic a cast in a generic method
        // which doesn't have the same semantics as a cast in a non-generic method
    
        var list = new List<TTo>(capacity: 1);
        var binder = Microsoft.CSharp.RuntimeBinder.Binder.InvokeMember(
            flags: CSharpBinderFlags.ResultDiscarded, 
            name: "Add", 
            typeArguments: null, 
            context: typeof(TypeHelpers), // the current type
            argumentInfo: new[] 
            { 
                CSharpArgumentInfo.Create(flags: CSharpArgumentInfoFlags.None, name: null), 
                CSharpArgumentInfo.Create(
                    flags: CSharpArgumentInfoFlags.UseCompileTimeType, 
                    name: null
                ),
            }
        );
        var callSite = CallSite<Action<CallSite, object, TFrom>>.Create(binder);
        callSite.Target.Invoke(callSite, list, default(TFrom));
    }
    

    【讨论】:

    • 唉,您在该主题上的帖子已不再存在于该链接中
    • 帖子Conversions.md 仍然存在,但描述ReflectionHelpers.GetMethod 的文章显然已被抢注者接管。
    • 你可以从here找到ReflectionHelpers。
    【解决方案4】:

    这是一个通过以下所有测试的方法:

    [Test] public void TestImplicitlyCastable()
    {
        Assert.That( typeof(byte)    .IsImplicitlyCastableTo(typeof(short)));
        Assert.That( typeof(byte)    .IsImplicitlyCastableTo(typeof(byte?)));
        Assert.That( typeof(byte)    .IsImplicitlyCastableTo(typeof(long?)));
        Assert.That(!typeof(short)   .IsImplicitlyCastableTo(typeof(uint)));
        Assert.That( typeof(long)    .IsImplicitlyCastableTo(typeof(float)));
        Assert.That( typeof(long)    .IsImplicitlyCastableTo(typeof(decimal)));
        Assert.That(!typeof(double)  .IsImplicitlyCastableTo(typeof(decimal)));
        Assert.That(!typeof(decimal) .IsImplicitlyCastableTo(typeof(double)));
        Assert.That( typeof(List<int>).IsImplicitlyCastableTo(typeof(object)));
        Assert.That( typeof(float)   .IsImplicitlyCastableTo(typeof(IComparable<float>)));
        Assert.That( typeof(long?)   .IsImplicitlyCastableTo(typeof(IComparable<long>)));
        Assert.That(!typeof(object)  .IsImplicitlyCastableTo(typeof(string)));
        Assert.That( typeof(string[]).IsImplicitlyCastableTo(typeof(object[])));
        Assert.That( typeof(Foo)     .IsImplicitlyCastableTo(typeof(int)));
        Assert.That(!typeof(Foo)     .IsImplicitlyCastableTo(typeof(uint)));
        Assert.That( typeof(Foo)     .IsImplicitlyCastableTo(typeof(long)));
        Assert.That( typeof(Foo)     .IsImplicitlyCastableTo(typeof(long?)));
    }
    class Foo
    {
        public static implicit operator int(Foo f) => 42;
    }
    

    它基于 dynamic 的一个技巧,灵感来自 ChaseMedallion 的回答:

    using System;
    using System.Collections.Generic;
    using System.Linq.Expressions;
    using System.Reflection;
    using Microsoft.CSharp.RuntimeBinder;
    
    public static class ReflectionHelpers
    {
        [ThreadStatic]
        static readonly Dictionary<KeyValuePair<Type, Type>, bool> ImplicitCastCache;
    
        /// <summary>Returns true iff casting between values of the specified 
        /// types is possible based on the rules of C#.</summary>
        public static bool IsImplicitlyCastableTo(this Type from, Type to)
        {
            if (from == to)
                return true;
    
            var key = new KeyValuePair<Type, Type>(from, to);
            ImplicitCastCache ??= new Dictionary<KeyValuePair<Type, Type>, bool>();
            if (ImplicitCastCache.TryGetValue(key, out bool result))
                return result;
    
            if (to.IsAssignableFrom(from))
                return ImplicitCastCache[key] = true;
    
            var method = GetMethodInfo(() => IsImplicitlyCastableCore<int, int>())
                .GetGenericMethodDefinition().MakeGenericMethod(from, to);
            return ImplicitCastCache[key] = (bool)method.Invoke(null, Array.Empty<object>());
        }
    
        static bool IsImplicitlyCastableCore<TFrom,TTo>()
        {
            var testObject = new LinkedListNode<TTo>(default(TTo));
            try {
                ((dynamic)testObject).Value = default(TFrom);
                return true;
            } catch (Exception e) {
                // e.g. "Cannot implicitly convert type 'A' to 'B'. An explicit conversion exists (are you missing a cast?)"
                // The exception may be caused either because no conversion is available,
                // OR because it IS available but the conversion method threw something.
                // Assume RuntimeBinderException means the conversion does not exist.
                return !(e is RuntimeBinderException); 
            }
        }
    
        /// <summary><c>GetMethodInfo(() => M(args))</c> gets the MethodInfo object corresponding to M.</summary>
        public static MethodInfo GetMethodInfo(Expression<Action> shape) => ((MethodCallExpression)shape.Body).Method;
    }
    

    【讨论】:

      猜你喜欢
      • 2014-12-12
      • 1970-01-01
      • 2022-11-07
      • 1970-01-01
      • 2021-02-07
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多