How to check assignability of types at runtime in C#?

后端 未结 6 1205
后悔当初
后悔当初 2021-02-06 02:11

The Type class has a method IsAssignableFrom() that almost works. Unfortunately it only returns true if the two types are the same or the first is in

相关标签:
6条回答
  • 2021-02-06 02:15

    In order to find out if one type can be assigned to another, you have to look for implicit conversions from one to the other. You can do this with reflection.

    As Timwi said, you will also have to know some built-in rules, but those can be hard-coded.

    0 讨论(0)
  • 2021-02-06 02:16

    There are actually three ways that a type can be “assignable” to another in the sense that you are looking for.

    • Class hierarchy, interface implementation, covariance and contravariance. This is what .IsAssignableFrom already checks for. (This also includes permissible boxing operations, e.g. int to object or DateTime to ValueType.)

    • User-defined implicit conversions. This is what all the other answers are referring to. You can retrieve these via Reflection, for example the implicit conversion from int to decimal is a static method that looks like this:

      System.Decimal op_Implicit(Int32)
      

      You only need to check the two relevant types (in this case, Int32 and Decimal); if the conversion is not in those, then it doesn’t exist.

    • Built-in implicit conversions which are defined in the C# language specification. Unfortunately Reflection doesn’t show these. You will have to find them in the specification and copy the assignability rules into your code manually. This includes numeric conversions, e.g. int to long as well as float to double, pointer conversions, nullable conversions (int to int?), and lifted conversions.

    Furthermore, a user-defined implicit conversion can be chained with a built-in implicit conversion. For example, if a user-defined implicit conversion exists from int to some type T, then it also doubles as a conversion from short to T. Similarly, T to short doubles as T to int.

    0 讨论(0)
  • 2021-02-06 02:23

    What you are looking for is if there's an implicit cast from the one type to the other. I would think that's doable by reflection, though it might be tricky because the implicit cast should be defined as an operator overload which is a static method and I think it could be defined in any class, not just the one that can be implicitly converted.

    0 讨论(0)
  • 2021-02-06 02:31

    This one almost works... it's using Linq expressions:

    public static bool IsReallyAssignableFrom(this Type type, Type otherType)
    {
        if (type.IsAssignableFrom(otherType))
            return true;
    
        try
        {
            var v = Expression.Variable(otherType);
            var expr = Expression.Convert(v, type);
            return expr.Method == null || expr.Method.Name == "op_Implicit";
        }
        catch(InvalidOperationException ex)
        {
            return false;
        }
    }
    

    The only case that doesn't work is for built-in conversions for primitive types: it incorrectly returns true for conversions that should be explicit (e.g. int to short). I guess you could handle those cases manually, as there is a finite (and rather small) number of them.

    I don't really like having to catch an exception to detect invalid conversions, but I don't see any other simple way to do it...

    0 讨论(0)
  • 2021-02-06 02:33

    Timwi's answer is really complete, but I feel there's an even simpler way that would get you the same semantics (check "real" assignability), without actually defining yourself what this is.

    You can just try the assignment in question and look for an InvalidCastException (I know it's obvious). This way you avoid the hassle of checking the three possible meanings of assignability as Timwi mentioned. Here's a sample using xUnit:

    [Fact]
    public void DecimalsShouldReallyBeAssignableFromInts()
    {
        var d = default(decimal);
        var i = default(i);
    
        Assert.Throws<InvalidCastException)( () => (int)d);
        Assert.DoesNotThrow( () => (decimal)i);
    }
    
    0 讨论(0)
  • 2021-02-06 02:39

    It actually happens to be the case that the decimal type is not "assignable" to the int type, and vice versa. Problems occur when boxing/unboxing gets involved.

    Take the example below:

    int p = 0;
    decimal d = 0m;
    object o = d;
    object x = p;
    
    // ok
    int a = (int)d;
    
    // invalid cast exception
    int i = (int)o;
    
    // invalid cast exception
    decimal y = (decimal)p;
    
    // compile error
    int j = d;
    

    This code looks like it should work, but the type cast from object produces an invalid cast exception, and the last line generates a compile-time error.

    The reason the assignment to a works is because the decimal class has an explicit override on the type cast operator to int. There does not exist an implicit type cast operator from decimal to int.

    Edit: There does not exist even the implicit operator in reverse. Int32 implements IConvertible, and that is how it converts to decimal End Edit

    In other words, the types are not assignable, but convertible.

    You could scan assemblies for explicit type cast operators and IConvertible interfaces, but I get the impression that would not serve you as well as programming for the specific few cases you know you will encounter.

    Good luck!

    0 讨论(0)
提交回复
热议问题