Task.FromResult的虚无性警告。

问题描述 投票:0回答:1

我们的代码库(.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?> 将迫使我们添加类型约束,而我们不能这样做。

c# .net .net-standard-2.0 c#-8.0 nullability
1个回答
2
投票

如果 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] 属性,但这只能应用于返回值本身(本例中的任务),而不是任务的通用参数。


0
投票

在我看来,将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.

避免使用 defaultdefault(T) 在C# 8.0中,如果没有足够的类型约束,通用类型就会出现问题。

对于这个问题,有几种解决方案。

1:指定一个调用者提供的default-value。

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 );

2:在不同的方法上增加结构与类的约束。

这个 default(T)structvalue-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 );
}

(注意,你不能纯粹基于通用类型约束来重载方法--你只能通过通用参数数和通常的参数类型来重载。

© www.soinside.com 2019 - 2024. All rights reserved.