I am converting a codebase to C#8 with nullable reference types. I came across the a method similar to the one in this question but async.
public async Task<T> GetAsync<T>()
{
// sometimes returns default(T); => warning CS8603 Possible null reference return
}
T may be any type, including nullable reference types or nullable value types.
To be clear, I understand WHY this method triggers a warning. What I'd like to know is what annotations can be used to resolve it.
default(T)!
, but I was hoping for something that's less of a "hammer".[return: MaybNull]
because that would apply to the Task
itself, not the T
.Is there any other attribute/annotation I can apply to make the compiler happy, or is default(T)!
my only option?
Form my experience, you can use Task<T?> GetAsync<T>() where T: class
to resolve your problem.
In C# 9 we can solve just by adding ?
public async Task<T?> GetAsync<T>()
{
return default;
}
But you need to distinguish nullable value types and nullable ref types in the calling code. To get a nullable ref type as a return value you can call the method with either <T>
or <T?>
:
SomeClass? c = await GetAsync<SomeClass>(); // return type is SomeClass?
SomeClass? c2 = await GetAsync<SomeClass?>(); // return type is SomeClass?
To get a nullable value type you need to call it with <T?>
:
int? i = await GetAsync<int?>(); // return type is int?
int i2 = await GetAsync<int>(); // return type is int
P.S. I wonder how Microsoft explains us why they can't allow unconstrained T? and then just does this in the next C# version :)
Another option is to use a code from the answer for C# 8.
We can't have async Task<T?> GetAsync<T>()
since SomeClass?
and SomeStruct?
are very different. Also default!
is not the best option since we can get nullable reference on non-nullable reference type by calling GetAsync<SomeClass>()
in the calling code.
Better option is to have two different methods that use the same private method:
public class Storage
{
...
public Task<T?> GetClassAsync<T>() where T : class
{
return GetAsync<T?>();
}
public Task<T?> GetStructAsync<T>() where T : struct
{
return GetAsync<T?>();
}
private async Task<T> GetAsync<T>()
{
if (condition)
return default!;
string json = await GetJsonAsync();
T result = JsonSerializer.Deserialize<T>(json);
return result;
}
}
And usage:
// return type is SomeClass?
SomeClass? classResult = await storage.GetClassAsync<SomeClass>();
// return type is int?
int? structResult = await storage.GetStructAsync<int>();
// warning: Nullability of type argument doesn't match 'class' constraint
SomeClass? classResult = await storage.GetClassAsync<SomeClass?>();
// error: The type 'int?' must be a non-nullable value type
int? structResult2 = await storage.GetStructAsync<int?>();
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With