Search code examples
c#genericsc#-8.0nullable-reference-types

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?

For nullable value type, there is a section dedicated to it. https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/builtin-types/nullable-value-types#how-to-identify-a-nullable-value-type

We are trying to do optional null check according to the generic type

#nullable enable
public static Result<T> Create<T>(T value)
{
   if (!typeof(T).IsNullable() && value is null)
      throw new ArgumentNullException(nameof(value));

   // Do something
}

public static bool IsNullable(this Type type)
{
   // If type is SomeClass, return false
   // If type is SomeClass?, return true
   // If type is SomeEnum, return false
   // If type is SomeEnum?, return true
   // If type is string, return false
   // If type is string?, return true
   // If type is int, return false
   // If type is int?, return true
   // etc
}

So the following will throw ArgumentNullException when T is not nullable But allow value to be null with no exception when T is nullable, e.g.

Create<Anything>(null); // throw ArgumentNullException

Create<Anything?>(null); // No excception

Solution

  • 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));
        }
    
        ...
    }