While Brann's solution works for a lot of cases, it does not take into account the cascading effect on implicit castings of non-primitive types.
E.g.: Assigning a float to a Thickness (which has an implicit cast from a double) should return true.
I rewrote the code to handle cases like this.
public static class AvailableCastChecker
{
public static bool CanCast(Type from, Type to)
{
if (to.IsAssignableFrom(from))
{
return true;
}
if (HasImplicitConversion(from, from, to) || HasImplicitConversion(to, from, to))
{
return true;
}
if (ImplicitNumericConversions.TryGetValue(to, out var list) &&
(list.Contains(from) || list.Any(t => CanCast(from, t))))
{
return true;
}
if (to.IsEnum)
{
return CanCast(from, Enum.GetUnderlyingType(to));
}
return Nullable.GetUnderlyingType(to) != null && CanCast(from, Nullable.GetUnderlyingType(to));
}
// https://msdn.microsoft.com/en-us/library/y5b434w4.aspx
private static Dictionary<Type, List<Type>> _implicitNumericConversions;
private static Dictionary<Type, List<Type>> ImplicitNumericConversions => _implicitNumericConversions ?? (_implicitNumericConversions = new Dictionary<Type, List<Type>>()
{
{typeof(short), new List<Type> { typeof(sbyte), typeof(byte) }},
{typeof(ushort), new List<Type> { typeof(byte), typeof(char) }},
{typeof(int), new List<Type> { typeof(sbyte), typeof(byte), typeof(char), typeof(short), typeof(ushort) }},
{typeof(uint), new List<Type> { typeof(byte), typeof(char), typeof(ushort) }},
{typeof(long), new List<Type> { typeof(sbyte), typeof(byte), typeof(char), typeof(short), typeof(ushort), typeof(int), typeof(uint) }},
{typeof(ulong), new List<Type> { typeof(byte), typeof(char), typeof(ushort), typeof(uint) }},
{typeof(float), new List<Type> { typeof(sbyte), typeof(byte), typeof(char), typeof(short), typeof(ushort), typeof(int), typeof(uint), typeof(long), typeof(ulong) }},
{typeof(double), new List<Type> { typeof(sbyte), typeof(byte), typeof(char), typeof(short), typeof(ushort), typeof(int), typeof(uint), typeof(long), typeof(ulong), typeof(float) }}
});
private static bool HasImplicitPrimitiveConversion(Type from, Type to)
{
return ImplicitNumericConversions.TryGetValue(to, out var list) && list.Contains(from);
}
private static bool HasImplicitConversion(Type definedOn, Type from, Type to)
{
return definedOn.GetMethods(BindingFlags.Public | BindingFlags.Static)
.Where(mi => mi.Name == "op_Implicit"
&& (mi.ReturnType == to || HasImplicitPrimitiveConversion(from, to)))
.Any(mi =>
{
var pi = mi.GetParameters().FirstOrDefault();
return pi != null && (pi.ParameterType == from || HasImplicitPrimitiveConversion(from, pi.ParameterType));
});
}
}