繁体   English   中英

Task.FromResult 的可空性警告

[英]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)

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

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 vs. class 约束:

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM