[英]Nullability warning for Task.FromResult
我们的代码库(.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 参考参数。
为什么我们会收到此警告? 对我来说,将null
作为参数传递给Task.FromResult
看起来非常好。
重要提示:我们希望允许任务包含 null 值。 但是添加Task<T?>
会迫使我们添加我们无法做到的类型约束。
如果T
是不可为空的引用类型,则不应将null
传递给Task.FromResult<T>
。 Task.FromResult
的实现不关心 null 引用,您可以使用Task.FromResult(default(T)!)
,但是GetDefaultTask
的调用者可能会收到一个Task<string>
而它实际上应该是一个Task<string?>
. 像GetDefaultTask<string>().Result.Length
这样的代码将在没有警告的情况下编译,并在运行时导致 null 引用异常。
据我所知,目前无法在这种情况下正确注释返回类型。
不允许将该方法声明为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
, 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)
。
这个问题有几个解决方案:
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 );
struct
/value-type 的default(T)
可能是有意义的(或者它可能与null
... 一样危险),因为我们可以安全地使用default(T)
where T: struct
but not 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 );
}
(请注意,您不能仅基于泛型类型约束重载方法 - 您只能通过泛型参数计数和普通参数类型重载。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.