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