Jason Yu
Jason Yu

Reputation: 195

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

Upvotes: 18

Views: 6654

Answers (4)

Luke Vo
Luke Vo

Reputation: 20658

I had the exact issue (in my case I am deserializing JSON, most of the time it's not null but there is a case where it's possible to have a null value). My solution is to create two methods and decorate where T : notnull to the non-nullable one:

public static Result<T> Create<T>(T value) where T : notnull
{
   if (value is null)
      throw new ArgumentNullException(nameof(value));

   // Do something

   return value;
}

// Result may be null as well
public static Result<T?> CreateNullable<T>(T? value)
{
   // No throw here if value is null

   // Do something
   // Need to account for cases where `value` may be null

   return value;
}

In fact in my case, the non-nullable one can even reuse the nullable code:

    public async Task<T?> SendNullableAsync<T>(HttpRequestMessage req)
    {
        using var res = await SendAsync(req);
        res.EnsureSuccessStatusCode();

        var content = await res.Content.ReadFromJsonAsync<T>();
        return content;
    }

    public async Task<T> SendAsync<T>(HttpRequestMessage req)
        where T : notnull
    {
        return await SendNullableAsync<T>(req) ??
            throw new InvalidDataException();
    }

Upvotes: 0

Renegade Nemo
Renegade Nemo

Reputation: 23

The default value of reference types and nullable value types is null. https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/builtin-types/default-values

Non-nullables can never be null (obviously), so it stands to reason that you can simply do this:

public static bool IsNullable<T>() => default(T) == null;

This technique will not work if you are starting with a System.Type variable since it cannot be turned into a generic type param.

Upvotes: -1

Iliar Turdushev
Iliar Turdushev

Reputation: 5213

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

    ...
}

Upvotes: 22

John Wu
John Wu

Reputation: 52240

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

Upvotes: -1

Related Questions