C# Task.FromResult的可空性警告

C# Task.FromResult的可空性警告,c#,.net,.net-standard-2.0,c#-8.0,nullability,C#,.net,.net Standard 2.0,C# 8.0,Nullability,我们的代码库(.NET标准2.0库)中有以下方法: 公共任务GetDefaultTask() { 返回Task.FromResult(默认值(T)); } 我们目前正在尝试切换到C#8.0可空性,并在上面的代码中得到警告: 警告CS8604:“Task Task.FromResult(T result)”中参数“result”的引用参数可能为空 为什么我们会收到这个警告?在我看来,将null作为参数传递给Task.FromResult,这看起来非常好 重要提示:我们希望允许任务包含空值。但是添

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

公共任务GetDefaultTask() { 返回Task.FromResult(默认值(T)); } 我们目前正在尝试切换到C#8.0可空性,并在上面的代码中得到警告:

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

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


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

如果
T
是不可为空的引用类型,
null
不应传递给
Task.FromResult
Task.FromResult
的实现不关心空引用,您可以使用
Task.FromResult(默认值(t)!)
,但是
GetDefaultTask
的调用者可能会收到一个
任务,而实际上它应该是一个
Task
。类似于GetDefaultTask().Result.Length的代码将在没有警告的情况下编译,并在运行时导致空引用异常

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

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

如果
T
被约束为参考类型,则可以清楚地解决此问题:

public Task<T?> GetDefaultTask<T>() where T : class
public Task GetDefaultTask(),其中T:class
但是,根据
T
参数的来源,添加该约束可能会导致调用链上的问题

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

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

不,那是个坏主意

如果调用者为
T
指定了一个不可为null的类型,那么
default(T)
可以被认为是“未定义的”(它实际上是
null
,但这是C#8.0实现不可为null的引用类型的一个主要缺点(即它们仍然可以是
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.
因此,需要更新调用站点,如果调用方试图在运行时使用
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 );

(请注意,不能完全基于泛型类型约束重载方法-只能通过泛型参数计数和通常的参数类型重载方法。

您不是声明了不可为null的t(任务)的期望值,但仍有默认值(t)吗返回,引用类型为null?一种方法是更改为T?但是您需要限制为class.或us default!而不是默认值来拒绝可空性。无论这是一个好主意还是坏主意,您都可以通过启用/禁用命令来消除此警告,如@Dmitri:I向elabo添加了一个重要注释对这种情况进行评级。我已经用一个重要的注释调整了我的问题。底线:没有好的方法可以做到这一点?不,不幸的是没有。目前无法声明无约束的泛型参数可能是可空引用或结构。此github问题链接到一些相关讨论和未来可能的语言更改:我已经用一个重要的附加注释调整了我的问题。如果我正确阅读了你的帖子,那么到目前为止,在C#8中没有干净的方法可以做到这一点?@D.R.你为什么不能添加类型约束?
public Task<T> GetDefaultTask<T>( T defaultValue )
{
    return Task.FromResult( defaultValue );
}
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 );
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 );
}