3

我们的代码库(.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 包含空值。但是添加Task<T?>会迫使我们添加我们无法做到的类型约束。

4

2 回答 2

2

IfT是不可为空的引用类型,null不应传递给Task.FromResult<T>. 的实现Task.FromResult不关心空引用,您可以使用Task.FromResult(default(T)!),但是当它实际上应该是a 时,调用者GetDefaultTask可能会收到a 。类似的代码会在没有警告的情况下编译,并在运行时导致空引用异常。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] 属性,但这只能应用于返回值本身(在这种情况下为任务),而不是任务的通用参数.

于 2020-04-26T23:34:48.627 回答
0

对我来说,将 null 作为参数传递给Task.FromResult.

不,这是个坏主意。

如果调用者为 then 指定了不可为空的类型,Tdefault(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:在不同的方法上添加结构与类约束:

/value-type的可能是有意义default(T)struct(或者它可能和null... 一样危险),因为我们可以安全地使用default(T)whereT : struct而不是default(T)where 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 );
}

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

于 2020-04-26T23:51:07.877 回答