Background
Two popular solutions to the problem of type conversion are to use System.Convert.ChangeType
, or to obtain System.ComponentModel.TypeConverter
and call its ConvertFrom
method. The first method breaks when you try converting a value type T
to System.Nullable<T>
; the second one breaks when you try converting different numeric types, for example, float
to double
. These limitations appear especially frustrating, because the CLR has built-in capabilities to perform both types of conversion.
One way of using these type casting capabilities is to build a LINQ lambda expression, compile it into a Func<object,object>
, and then use the compiled delegate every time we need to convert between two types.
public static class TypeCast {
// This is the method exposing the rest of the functionality
public static object Cast(this Type type, object obj) {
return GetConverter(type, obj)(obj);
}
private static readonly IDictionary<PairOfTypes,Func<object,object>> converters =
new Dictionary<PairOfTypes,Func<object,object>>();
private static readonly ParameterExpression convParameter =
Expression.Parameter(typeof(object), "val");
// This is the method with the "guts" of the implementation
[MethodImpl(MethodImplOptions.Synchronized)]
private static Func<object,object> GetConverter(Type targetType, object val) {
var fromType = val != null ? val.GetType() : typeof(object);
var key = new PairOfTypes(fromType, targetType);
Func<object,object> res;
if (converters.TryGetValue(key, out res)) {
return res;
}
res = (Func<object,object>)Expression.Lambda(
Expression.Convert(
Expression.Convert(
Expression.Convert(
convParameter
, fromType
)
, targetType
)
, typeof(object)
)
, convParameter
).Compile();
converters.Add(key, res);
return res;
}
// This class provides Equals and GetHashCode
// for a pair of System.Type objects.
private class PairOfTypes {
private readonly Type first;
private readonly Type second;
public PairOfTypes(Type first, Type second) {
this.first = first;
this.second = second;
}
public override int GetHashCode() {
return 31*first.GetHashCode() + second.GetHashCode();
}
public override bool Equals(object obj) {
if (obj == this) {
return true;
}
var other = obj as PairOfTypes;
if (other == null) {
return false;
}
return first.Equals(other.first)
&& second.Equals(other.second);
}
}
}
Now, you can use the Cast method like this:
double? x = typeof(double?).Cast(1.0);
int y = typeof(int).Cast(1.2345);