【问题标题】:How can I use a `Task<string>` instance as a Task<string?> parameter to a method?如何将 `Task<string>` 实例用作方法的 Task<string?> 参数?
【发布时间】:2021-09-02 20:29:17
【问题描述】:

如何将Task&lt;string&gt; 实例用作方法的Task 参数?

如果我启用了可空性,并且我有两个异步方法,例如..

// in an ordinary class
public async static Task<string> Foo() { ... }

// in a static class
public async static Task Bar(this Task<string?> task1) { ... }

我尝试调用await Foo().Bar();,但编译器给了我:

warning CS8620: Argument of type 'Task<string>' cannot be used for parameter 'task' of type 'Task<string?>'

我可以对 Foo 的结果做些什么来使它可以作为一个 Task 被接受?

【问题讨论】:

  • 我想你正在寻找! (null-forgiving) operator
  • @patrick-szalapski 你可以这样使用它:await FooClass.Foo()!.Bar();
  • 谢谢,但我不明白。 Foo() 不返回可空任务(它返回Task&lt;string&gt;,而不是Task&lt;string&gt;?);为什么我需要在不可为空的东西上原谅 null?

标签: c# generics nullable nullable-reference-types


【解决方案1】:

有一个Task nullability covariance的提案。

现在,您必须使用! (null-forgiving) operator 来消除编译器警告:
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

【讨论】:

【解决方案2】:

问题在于 C# 的可为空引用类型注释不是泛型类型的变体。

这段代码证明了这一点:

Task<object> o = Task.FromResult<object>(new object());
Task<object?> p = o;

虽然我们知道应该允许不能返回 null 的任务代替可以返回 null 的任务,但编译器不知道这一点。

编译这个gives warning:

CS8619:“Task”类型的值中引用类型的可空性与目标类型“Task”不匹配。

请注意,这种差异确实适用于委托和接口。但Task&lt;T&gt; 两者都不是。

例如这两个compile without warning:

IEnumerable<object> c = new[] { new object() };
IEnumerable<object?> d = c;

Action<object?> a = _ => {};
Action<object> b = a;

已经讨论过在Task&lt;T&gt; 中为T 的可空性向编译器添加特殊处理,但尚未落地。

在此之前,您必须使用 null 宽恕运算符来抑制这些警告。

【讨论】:

    【解决方案3】:

    您可以将Task&lt;string&gt; 包装在一个延续中,将结果作为Task&lt;string?&gt; 返回:

    await Foo().ContinueWith<string?>(task => {
        try {
            return task.Result;
        } catch (AggregateException e) {
            throw e.InnerExceptions[0]; // Propagate exceptions/cancellation.
        }
    }).Bar();
    

    ContinueWith 中的 lambda 表达式必须返回一个 string?,这在这里有效,因为 task.Result 是一个 string,它可以隐式转换为 string?。

    【讨论】:

    • 这不会保持任务的正确错误/取消状态。
    • @Servy 这超出了这个问题的范围。
    • 这超出了问题的范围吗?这个问题并不是说任务永远不会被取消或出错,或者他们不关心任务出错或被取消时的行为。
    • @Servy 这个答案只是一个说明,而不是一个全面的解决方案。我会添加一个警告。
    • 我很欣赏 Spencer 的努力和意图,但我必须同意 Servy 的观点——这里的解决方案应该足够健壮,以允许捕获异常在几乎任何情况下都有用。我已经编辑了这个问题以明确这一点。但无论如何,你的回答很有帮助和启发性。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-12-03
    • 1970-01-01
    • 2016-10-28
    • 2013-06-19
    相关资源
    最近更新 更多