我们的代码库(.NET标准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<T?>
将迫使我们添加类型约束,而我们不能这样做。
如果 T
是一个不可空的引用类型。null
不应传给 Task.FromResult<T>
. 落实 Task.FromResult
不在乎空引用,你可以使用 Task.FromResult(default(T)!)
呼叫者 GetDefaultTask
可获 Task<string>
而实际上 Task<string?>
. 代码如 GetDefaultTask<string>().Result.Length
会在编译时不发出警告,并在运行时引起空引用异常。
据我所知,目前还不能在这种情况下正确注释返回类型。
将方法声明为 Task<T?> GetDefaultTask<T>()
不允许,因为 T
可以是一个结构,也可以是一个引用类型,可空结构和引用类型的表示方法不同。
如果用 T
被限制为引用类型。
public Task<T?> GetDefaultTask<T>() where T : class
但是添加这个约束可能会导致调用链上的问题, 这取决于那个 T
参数的来源。
对于类似的情况,一个通用的返回值可以是一个结构或引用(如 Enumerable.FirstOrDefault
)还有 [MaybeNull]
属性,但这只能应用于返回值本身(本例中的任务),而不是任务的通用参数。
在我看来,将null作为参数传给
Task.FromResult
.
不,这是个坏主意。
如果调用者指定了一个非空类型的 T
然后 default(T)
可以说是 "未定义 "的(其实是 null
但这是C# 8.0对不可空引用类型的实现的一个主要缺陷(即它们是 可以 仍是 null
, grrrrr)。考虑。
// 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.
避免使用 default
default(T)
在C# 8.0中,如果没有足够的类型约束,通用类型就会出现问题。
对于这个问题,有几种解决方案。
public Task<T> GetDefaultTask<T>( T defaultValue )
{
return Task.FromResult( defaultValue );
}
所以需要更新调用站点,如果调用者试图使用的是 null
而不是在运行时出现异常。
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 );
这个 default(T)
的 struct
value-type可能是有意义的(或者它可能与 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 );
}
(注意,你不能纯粹基于通用类型约束来重载方法--你只能通过通用参数数和通常的参数类型来重载。