Search code examples
c#genericsnullablenullable-reference-types

Generically convert Object? to T?


I have some code which works when I have a generic constraint where T : struct:

public readonly struct AnnotatedValue<T> where T : struct
{
    public AnnotatedValue(Object? value, ...)
    {
        Value = ConvertValue(value);
        ...
    }

    public T? Value { get; init; }
    ... more properties ...

    private static T? ConvertValue(Object? value)
    {
        return value switch
        {
            null => null,
            T castValue => castValue,
            _ => (T?)Convert.ChangeType(value, typeof(T))
        };
    }
}

The general goal here is to convert the non-generic Object? into a requested type (most likely either a number, a struct, or a string); if the requested type is a value type, the result should be wrapped in a Nullable<T> to handle null inputs, and if the requested type is a reference type, the result is allowed to be null.

If I remove the where T : struct constraint, then the line null => null results in compiler error CS0403 Cannot convert null to type parameter 'T' because it could be a non-nullable value type. Consider using 'default(T)' instead.

I don't want to use default(T) because that will likely return a 0 value rather than a Nullable with null value.

The context is that I'm calling a non-generic method in a data source API which returns Object?, where the returned value might be null, a boxed value type (numeric or struct), or a String. I have access to metadata which indicates what the actual type will be, but this specific API returns it as Object?; my goal is to flexibly return it to a strongly-typed representation.

If the requested type is a value type, I'd like to convert the returned value into a Nullable<foo> corresponding to the requested value type. But if the requested type is a String, I'd like to return a String? (which is a nullable reference type, and therefore is not compatible with a where T : struct generic constraint).

Is there a way to achieve this kind of flexible conversion without requiring the caller to call a different method depending on whether the desired type is a value or reference type?

If I have to implement different generic methods for value where T : struct and reference where T : class, is there a way to have a wrapping method that checks run-time type info and calls into one method or the other to do the conversion?


Solution

  • It all comes down to what T is being passed in the value-type case. If it will be int, then: you can't do anything. Without a T : struct constraint, T? doesn't mean Nullable<T>; it means "T, and by the way, the compiler should consider this nullable from a NRT perspective". If the T will be int?, then it already is Nullable<T>, and default(T) already means "null".

    Ultimately T? expresses two competing things, and you can't get the exact combination you want here. In particular, you can't tell the compiler (or more specifically, the JIT) "if the T happens to be a value-type, swap T for T?".