How to identify a nullable reference type for generic type?
Asked Answered
O

4

16

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
Openhanded answered 3/6, 2020 at 4:31 Comment(3)
Does this answer your question? How to use .NET reflection to check for nullable reference type You can also have a look at nullable-metadta specShipment
@JasonYu Do you need to define if reference type is nullable or not?Salami
@Iliar Turdushev that's what I would like to do. But constraint only work like where T : notnull. I want the Create<T> method to throw ArgumentNullException according to if T is nullableOpenhanded
S
21

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

    ...
}
Salami answered 4/6, 2020 at 3:45 Comment(1)
If a generic type is constrained to "struct" it is constrained to non-nullable value types. Consequently T and T? can be used as expected for non-nullable and nullable types.Strang
H
0

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();
    }
Hecate answered 15/7, 2024 at 16:40 Comment(0)
C
-1

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);
}
Contrecoup answered 3/6, 2020 at 4:38 Comment(3)
There is notnull constraint like Result<T> Create<T>(T value) where T : notnull, and I am trying to use the new nullable reference type instead of Nullable<T>Openhanded
And this only considered struct. In our case, the generic type can be any class or struct. T may be SomeClass or SomeClass? or SomeEnum or SomeEnum? or int?, etcOpenhanded
But how would this handle a nullable reference type, like a string that may be null?Rehearsal
M
-1

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.

Musetta answered 27/10, 2023 at 10:18 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.