Reputation: 195
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
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
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
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
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