Home > front end >  How can I use a `Task<string>` instance as a Task<string?> parameter to a method?
How can I use a `Task<string>` instance as a Task<string?> parameter to a method?

Time:09-17

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

Full example on SharpLab

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.

  • Related