How to identify a nullable reference type for generic type?

In C# 8 with nullable enabled, is there a way to identify a nullable reference type for generic type?

In C# 8 there is NO way to check if a type parameter passed to a generic method is a nullable reference type or not.

The problem is that any nullable reference type T? is represented by the same type T (but with a compiler-generated attribute annotating it), as opposed to nullable value type T? that is represented by the actual .NET type Nullable<T>.

When compiler generates code that invokes a generic method F<T>, where T can be either nullable reference type or not, an information if T is nullable refence type is lost. Lets consider the next sample method:

public void F<T>(T value) { }

For the next invocations

F<string>("123");
F<string?>("456");

compiler will generate the next IL code (I simplified it a bit):

call    F<string>("123")
call    F<string>("456")

You can see that to the second method a type parameter string is passed instead of string? because the representation of the nullable reference type string? during the execution is the same type string.

Therefore during execution it is impossible to define if a type parameter passed to a generic method is a nullable reference type or not.


I think that for your case an optimal solution would be to pass a bool value that will indicate if a reference type is nullable or not. Here is a sample, how it can be implemented:

public static Result<T> Create<T>(T value, bool isNullable = false)
{
    Type t = typeof(T);

    // If type "T" is a value type then we can check if it is nullable or not.
    if (t.IsValueType) 
    {
        if (Nullable.GetUnderlyingType(t) == null && value == null)
            throw new ArgumentNullException(nameof(value));
    }
    // If type "T" is a reference type then we cannot check if it is nullable or not.
    // In this case we rely on the value of the argument "isNullable".
    else
    {
        if (!isNullable && value == null)
            throw new ArgumentNullException(nameof(value));
    }

    ...
}

You can't use nullable in the constraint, but you can use it in the method signature. This effectively constraints it to be a nullable type. Example:

static Result<Nullable<T>> Create<T>(Nullable<T> value) where T  : struct
{
    //Do something
}

Note that you can use this side by side with your existing method, as an overload, which allows you to execute a different logic path if it's nullable versus if it is not.

static Result<Nullable<T>> Create<T>(Nullable<T> value) where T  : struct
{
    Log("It's nullable!");
    Foo(value);
}

public static Result<T> Create<T>(T value)
{
    Log("It's not nullable!");
    Foo(value);
}