If you actually want to unbox the value and not simply convert it you can use the Expression API to build a function that does exactly that, in pseudo-code:
Func<object, TResult> = boxedValue => (TResult)(typeof(boxedValue))boxedValue;
To build the function you'll need a helper class to construct the delegate with the correct types:
static class UnboxerCache<TResult>
{
private static readonly ConcurrentDictionary<Type, Func<object, TResult>> UnboxerLookup = new ConcurrentDictionary<Type, Func<object, TResult>>();
public static Func<object, TResult> GetUnboxer(Type actualType)
{
return UnboxerLookup.GetOrAdd(actualType, CreateUnboxer);
}
private static Func<object, TResult> CreateUnboxer(Type actualType)
{
var boxedParameter = Expression.Parameter(typeof(object), "boxedValue");
var unboxedValue = Expression.Unbox(boxedParameter, actualType);
var convertedValue = Expression.Convert(unboxedValue, typeof(TResult));
var unboxer = Expression.Lambda<Func<object, TResult>>(convertedValue, boxedParameter);
return unboxer.Compile();
}
}
Here we have a mapping from the actual type of the object to a delegate that can unbox and cast it to the result type. The construction of the delegate is pretty simple, convert the boxedValue
to the actual type and then to the result type.
I would make the actual unboxer a separate class with that sole resposibility:
public static class Unbox
{
public static TResult? To<TResult>(object boxedValue)
where TResult : struct
{
if (boxedValue == null)
{
return null;
}
var actualType = boxedValue.GetType();
var resultType = typeof(TResult);
if (actualType == resultType)
{
return (TResult)boxedValue;
}
var unboxer = UnboxerCache<TResult>.GetUnboxer(actualType);
return unboxer(boxedValue);
}
private static class UnboxerCache<TResult>
{
private static readonly ConcurrentDictionary<Type, Func<object, TResult>> UnboxerLookup = new ConcurrentDictionary<Type, Func<object, TResult>>();
public static Func<object, TResult> GetUnboxer(Type actualType)
{
return UnboxerLookup.GetOrAdd(actualType, CreateUnboxer);
}
private static Func<object, TResult> CreateUnboxer(Type actualType)
{
var boxedParameter = Expression.Parameter(typeof(object), "boxedValue");
var unboxedValue = Expression.Unbox(boxedParameter, actualType);
var convertedValue = Expression.Convert(unboxedValue, typeof(TResult));
var unboxer = Expression.Lambda<Func<object, TResult>>(convertedValue, boxedParameter);
return unboxer.Compile();
}
}
}
Finally you should consider verifying that the type of the boxed value is a value type (boxedValue.GetType().IsValueType
) and maybe raise an error if that isn't the case.