How can I use a Task<string>
instance as a Task<string?> parameter to a method?
If I am using nullability enabled, and I have two async methods like..
// in an ordinary class
public async static Task<string> Foo() { ... }
// in a static class
public async static Task Bar(this Task<string?> task1) { ... }
I try to call await Foo().Bar();
, but the compiler gives me:
warning CS8620: Argument of type 'Task<string>' cannot be used for parameter 'task' of type 'Task<string?>'
What can I do to the result of Foo to make it acceptable as a Task<string?>?
CodePudding user response:
There is a proposal for Task nullability covariance.
For now, you have to use the ! (null-forgiving) operator to get rid of the compiler warning:
await FooClass.Foo()!.Bar();
class Program
{
static async Task Main(string[] args)
{
await FooClass.Foo()!.Bar();
}
}
public static class Extensions
{
public async static Task Bar(this Task<string?> task)
{
System.Console.WriteLine("bar");
await Task.CompletedTask;
}
}
public class FooClass
{
public async static Task<string> Foo()
{
System.Console.WriteLine("foo");
return await Task.FromResult("");
}
}
CodePudding user response:
You can wrap the Task<string>
in a continuation that returns the result as a Task<string?>
:
await Foo().ContinueWith<string?>(task => {
try {
return task.Result;
} catch (AggregateException e) {
throw e.InnerExceptions[0]; // Propagate exceptions/cancellation.
}
}).Bar();
The lambda expression in ContinueWith
must return a string?
, which works here because task.Result
is a string
, which is implicitly convertible to string?
.
CodePudding user response:
The issue is that C#'s nullable reference type annotations are not variant for generic types.
That is evidenced by this code:
Task<object> o = Task.FromResult<object>(new object());
Task<object?> p = o;
While we know that a task which cannot return a null should be permissible in place of one which can return a null, the compiler does not know that.
Compiling this gives warning:
CS8619: Nullability of reference types in value of type 'Task' doesn't match target type 'Task<object?>'.
Note that this variance does apply for delegates and interfaces. But Task<T>
is neither of those.
For example, both of these compile without warning:
IEnumerable<object> c = new[] { new object() };
IEnumerable<object?> d = c;
Action<object?> a = _ => {};
Action<object> b = a;
There has been discussion about adding special handling to the compiler for nullability of T
in Task<T>
, but it has not yet landed.
Until that time, you must use the null forgiveness operator to suppress these warnings.