How to check assignability of types at runtime in C#?
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);
}
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
toobject
orDateTime
toValueType
.)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
todecimal
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
andDecimal
); 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
tolong
as well asfloat
todouble
, pointer conversions, nullable conversions (int
toint?
), 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
.
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...