可能返回default(T)的异步泛型方法的适当可空注释

8
我正在将代码库转换为支持可空引用类型的 C#8。我发现了一个类似于此问题中的方法,但它是异步的。
public async Task<T> GetAsync<T>()
{
    // sometimes returns default(T); => warning CS8603 Possible null reference return
}

T可以是任何类型,包括可空引用类型或可空值类型。

明确一点,我知道为什么这个方法会触发一个警告。我想知道的是有哪些注释可以用来解决它。

  • 我知道我可以使用 #nullable disable 或 default(T)!,但我希望有一些不那么"强硬"的东西。
  • 我知道我不能使用[return: MaybNull],因为那将应用于Task本身,而不是T

还有其他的属性/注释可以使编译器满意吗,还是default(T)!是我的唯一选择?


请详细解释一下为什么您所提到的可能重复的问题不能解决您的问题。您是否尝试在返回语句上使用null-forgiving运算符?鉴于其他问题已经指出这是解决警告的方法,为什么您认为您会在这里得到不同的答案呢?您是否理解MaybeNull注释和null-forgiving运算符正在执行两个不同的操作?前者是修复警告的方法,后者确保调用者知道可能存在的情况。 - Peter Duniho
@PeterDuniho,重复返回T,因此他们可以使用return:MaybeNull。在我的情况下,我返回Task<T>,因此该属性将适用于任务,而不是T。正如我在问题中提到的那样,我知道在这里使用default(T)!作为解决方案,但它感觉像一个hack;我想知道这是否是唯一的解决方案。 - ChaseMedallion
“重复返回 T,这样他们就可以使用 return: MaybeNull” — 是的,但这是一个与你在这里提出的警告完全不同的问题。你是想同时问两个问题吗?如果是这样,你需要通过这里只问一个问题并针对第二个问题发布第二个问题来修复问题。无论是否破解,使用 null-forgiving 都是确切的答案,就像你已经查看的可能重复项中所提供的那样。 - Peter Duniho
@PeterDuniho 我不是在询问警告;我了解为什么会省略警告。我想知道是否有“干净”的选项来解决这种情况。我已更新措辞,希望减少混淆。正如你所说,在这种情况下,似乎!是唯一的选择,不像其他问题,那里有更好的选择。 - ChaseMedallion
@RikkiGibson 看起来C#9的答案解决了这个问题,当然,在C#9发布之前我就问过这个问题了! - ChaseMedallion
3个回答

1

根据我的经验,你可以使用 Task<T?> GetAsync<T>() where T: class 来解决你的问题。


3
可以,但这将改变函数的语义,要求T是引用类型。 - ChaseMedallion

1
在 C# 9 中,我们可以通过添加 "?" 来解决问题。保留了 HTML 格式。
public async Task<T?> GetAsync<T>()
{
    return default;
}

但是在调用代码中,您需要区分可空值类型和可空引用类型。要获取可空引用类型作为返回值,您可以使用 <T><T?> 调用该方法:

SomeClass? c = await GetAsync<SomeClass>(); // return type is SomeClass?
SomeClass? c2 = await GetAsync<SomeClass?>(); // return type is SomeClass?

要获取可空值类型,您需要使用 <T?> 进行调用:
int? i = await GetAsync<int?>(); // return type is int?
int i2 = await GetAsync<int>(); // return type is int

附言:我想知道微软如何解释为什么他们不能允许无限制的 T?,然后在下一个 C# 版本中却这样做了 :)

另一种选择是使用 C# 8 的答案中的代码。

C# 8 的答案

我们不能使用 async Task<T?> GetAsync<T>(),因为SomeClass?SomeStruct?非常不同。此外,default! 不是最佳选项,因为通过调用 GetAsync<SomeClass>() 在调用代码中可以获取可空引用类型上的非可空引用。

更好的选择是有两个不同的方法使用相同的私有方法:

public class Storage
{
    ...
    public Task<T?> GetClassAsync<T>() where T : class
    {
        return GetAsync<T?>();
    }
    public Task<T?> GetStructAsync<T>() where T : struct
    {
        return GetAsync<T?>();
    }
    private async Task<T> GetAsync<T>()
    {
        if (condition)
            return default!;
        string json = await GetJsonAsync();
        T result = JsonSerializer.Deserialize<T>(json);
        return result;
    }
}

并使用:

// return type is SomeClass?
SomeClass? classResult = await storage.GetClassAsync<SomeClass>();

// return type is int?
int? structResult = await storage.GetStructAsync<int>();


// warning: Nullability of type argument doesn't match 'class' constraint
SomeClass? classResult = await storage.GetClassAsync<SomeClass?>();
// error: The type 'int?' must be a non-nullable value type
int? structResult2 = await storage.GetStructAsync<int?>();

很高兴了解到C#9对此问题的解决方案! - ChaseMedallion
我必须说,我认为.NET正在变得越来越混乱。仅此功能的名称“可空引用类型”就显示了他们在创建简单、清晰概念方面遇到了多大的困难(因为所有引用类型一直都是可空的,不像值类型,“可空”至少是System.Nullable结构体的半合理缩写,而这个结构体作为值类型,当然是不可空的)。我归咎于JavaScript新手的涌入! :D - Dojo

0

从搜索和更多的研究中发现,在这种情况下抑制警告的首选方法似乎是在default(T)上使用!运算符。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接