Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Tags:

c#

types

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 the hierarchy of the second. It says that decimal is not assignable from int, but I'd like a method that would indicate that decimals are assignable from ints, but ints are not always assignable from decimals. The compiler knows this but I need to figure this out at runtime.

Here's a test for an extension method.

[Test]
public void DecimalsShouldReallyBeAssignableFromInts()
{
    Assert.IsTrue(typeof(decimal).IsReallyAssignableFrom(typeof(int)));
    Assert.IsFalse(typeof(int).IsReallyAssignableFrom(typeof(decimal)));
}

Is there a way to implement IsReallyAssignableFrom() that would work like IsAssignableFrom() but also passes the test case above?

Thanks!

Edit:

This is basically the way it would be used. This example does not compile for me, so I had to set Number to be 0 (instead of 0.0M).

[AttributeUsage(AttributeTargets.Property | AttributeTargets.Parameter)]
public class MyAttribute : Attribute
{
    public object Default { get; set; }
}

public class MyClass
{
    public MyClass([MyAttribute(Default= 0.0M)] decimal number)
    {
        Console.WriteLine(number);
    }
}

I get this error:

Error 4 An attribute argument must be a constant expression, typeof expression or array creation expression of an attribute parameter type

like image 603
epicsmile Avatar asked Sep 01 '10 21:09

epicsmile


2 Answers

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.

like image 194
Timwi Avatar answered Oct 15 '22 22:10

Timwi


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...

like image 35
Thomas Levesque Avatar answered Oct 15 '22 21:10

Thomas Levesque