【问题标题】:Nullability warning for Task.FromResultTask.FromResult 的可空性警告
【发布时间】:2020-04-27 15:03:25
【问题描述】:

我们的代码库(.NET Standard 2.0 库)中有以下方法:

public Task<T> GetDefaultTask<T>()
{
    return Task.FromResult(default(T));
}

我们目前正在尝试转换到 C# 8.0 Nullability 并在上面的代码中收到警告:

警告 CS8604:“Task Task.FromResult(T result)”中的参数“result”可能为空引用参数。

为什么我们会收到此警告?对我来说,将null 作为参数传递给Task.FromResult 看起来非常好。

重要提示:我们希望允许任务包含空值。但是添加Task&lt;T?&gt; 会迫使我们添加我们无法做到的类型约束。

【问题讨论】:

  • 你不是声明了一个不可为空的 T (Task) 的期望,但是却返回了 default(T),它对于引用类型是 null 吗?一种方法是更改​​为 T?但是你需要限制上课。或者我们默认!而不是默认拒绝可空性。
  • 无论这是一个好主意还是坏主意,您都可以通过启用/禁用命令来消除此警告,例如 stackoverflow.com/a/61436270/2946329
  • @Dmitri:我添加了一个重要说明来详细说明情况。

标签: c# .net .net-standard-2.0 c#-8.0 nullability


【解决方案1】:

如果T 是不可为空的引用类型,则不应将null 传递给Task.FromResult&lt;T&gt;Task.FromResult 的实现不关心空引用,您可以使用Task.FromResult(default(T)!),但是GetDefaultTask 的调用者可能会收到Task&lt;string&gt;,而实际上它应该是Task&lt;string?&gt;。像GetDefaultTask&lt;string&gt;().Result.Length 这样的代码会在没有警告的情况下编译,并在运行时导致空引用异常。

据我所知,目前无法在这种情况下正确注释返回类型。

不允许将方法声明为 Task&lt;T?&gt; GetDefaultTask&lt;T&gt;(),因为 T 可以是结构或引用类型,并且可空结构和引用类型的表示方式不同。

如果 T 被限制为引用类型,则可以干净地解决这个问题:

public Task<T?> GetDefaultTask<T>() where T : class

但添加该约束可能会导致调用链进一步出现问题,具体取决于 T 参数的来源。

对于通用返回值可以是结构或引用(例如Enumerable.FirstOrDefault)的类似情况,有[MaybeNull] 属性,但这只能应用于返回值本身(本例中的任务) ,而不是任务的通用参数。

【讨论】:

  • 我已经用重要说明调整了我的问题。底线:没有好办法吗?
  • 不,很遗憾没有。当前无法声明不受约束的泛型参数可以是可为空的引用或结构。此 github 问题链接到一些相关讨论和未来可能的语言更改:github.com/dotnet/roslyn/issues/29146
【解决方案2】:

在我看来,将 null 作为参数传递给 Task.FromResult 看起来非常好。

不,这是个坏主意。

如果调用者为T 指定了一个不可为空的类型,那么default(T) 可以被认为是“未定义的”(实际上是null,但这是C# 8.0 实现不可空引用的一个主要缺点-types(即它们可以仍然是null,grrrr)。考虑:

// Compiled with C# 8.0's non-nullable reference-types enabled.

Task<String> task = GetDefaultTask<String>();
String result = await task;
Console.WriteLine( result.Length ); // <-- NullReferenceException at runtime even though the C# compiler reported `result` cannot be null.

避免在 C# 8.0 中将 default/default(T) 用于没有足够类型约束的泛型类型。

这个问题有几个解决方案:

1:指定调用者提供的默认值:

public Task<T> GetDefaultTask<T>( T defaultValue )
{
    return Task.FromResult( defaultValue );
}

因此调用站点需要更新,如果调用者尝试使用 null 而不是运行时异常,C# 编译器将给出警告或错误:

Task<String> task = GetDefaultTask<String>( defaultValue: null ); // <-- compiler error or warning because `null` cannot be used here.
String result = await task;
Console.WriteLine( result.Length );

2:在不同的方法上添加结构与类约束:

struct/value 类型的 default(T) 可能是有意义的(或者它可能与 null... 一样危险),因为我们可以安全地使用 default(T) 其中 T : struct 但是不是default(T) 哪里是T : class,我们可以为这种情况添加不同的重载:

public Task<T> GetDefaultTask<T>()
    where T : struct
{
    return Task.FromResult( default(T) );
}

public Task<T> GetDefaultTask<T>( T defaultValue )
    where T : class
{
    return Task.FromResult( defaultValue );
}

(请注意,您不能仅基于泛型类型约束重载方法 - 您只能通过泛型参数计数和普通参数类型重载。

【讨论】:

  • 我已经用一个重要的附加说明调整了我的问题。如果我正确阅读了您的帖子,那么到目前为止,在 C#8 中没有干净的方法可以做到这一点?
  • @D.R.为什么不能添加类型约束?
猜你喜欢
  • 1970-01-01
  • 2017-01-16
  • 2016-09-07
  • 2018-12-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多