具有泛型返回类型的可空引用类型

Raz*_*zie 28 c# generics c#-8.0 nullable-reference-types

我正在使用新的C#8可空引用类型功能,并在重构我的代码时,我遇到了这个(简化)方法:

public T Get<T>(string key)
{
    var wrapper = cacheService.Get(key);
    return wrapper.HasValue ? Deserialize<T>(wrapper) : default;
}
Run Code Online (Sandbox Code Playgroud)

现在,这给出了一个Possible null reference return逻辑上的警告,因为default(T)将为所有引用类型赋予null.起初我以为我会把它改成以下内容:

public T? Get<T>(string key)

但这不可能做到.它说我要么必须添加通用约束where T : classwhere T : struct.但是,这是不是一种选择,因为这既可以是(我可以存储intint?或实例FooBar在缓存或其他).我还读到了一个假定的新泛型约束,where class?但似乎没有用.

我能想到的唯一简单的解决方案是使用null forgiving运算符更改return语句:

return wrapper.HasValue ? Deserialize<T>(wrapper) : default!;
Run Code Online (Sandbox Code Playgroud)

但这感觉不对,因为它肯定是空的,所以我基本上对这里的编译器撒谎:-)

我怎样才能解决这个问题?我错过了一些完全明显的东西吗?

Mar*_*der 28

你非常接近。只需像这样编写您的方法:

[return: MaybeNull]
public T Get<T>(string key)
{
    var wrapper = cacheService.Get(key);
    return wrapper.HasValue ? Deserialize<T>(wrapper) : default!;
}
Run Code Online (Sandbox Code Playgroud)

您必须使用default!来消除警告。但是您可以告诉编译器[return: MaybeNull]它应该检查 null,即使它是不可为 null 的类型。

在这种情况下,如果开发人员使用您的方法并且不检查 null,他可能会收到警告(取决于流分析)

有关详细信息,请参阅 Microsoft 文档:指定后置条件:MaybeNull 和 NotNull

  • 这很酷,只是 `MaybeNull` 不能与 `Task&lt;T&gt;` 一起使用(它假设任务可能为空,而不是任务返回的值)。因此,如果您有异步通用函数,您仍然会陷入困境。 (7认同)

Swe*_*per 16

我认为这default!是目前最好的方法。

之所以public T? Get<T>(string key)不起作用,是因为可为空的引用类型与可为空的值类型非常不同

可空的引用类型纯粹是编译时的事情。小问号和感叹号仅由编译器用于检查可能的null。在运行时的眼里,string?string是完全一样的。

另一方面,可空值类型是的语法糖Nullable<T>。当编译器编译您的方法时,它需要确定方法的返回类型。如果T是引用类型,则您的方法将具有返回类型T。如果T为值类型,则您的方法的返回类型为Nullable<T>。但是编译器不知道如何处理T两者。当然不能说“返回类型是Tif T是引用类型,而它是Nullable<T>if T是引用类型”。因为CLR无法理解这一点。一种方法应该只有一个返回类型。

换句话说,说要返回T?就好比说要返回Twhen T是引用类型,而要返回Nullable<T>when T是值类型。这听起来不像是方法的有效返回类型,对吗?

作为一个非常糟糕的解决方法,您可以声明两个具有不同名称的方法-一种T约束为值类型,另一种T约束为引用类型。

  • 这真的很糟糕,我很惊讶没有更好的解决方案来解决这个非常明显的问题。 (2认同)
  • 这使得可为空的引用很多用处不大。我想知道将来是否有计划添加一种将其与泛型一起使用的方法。 (2认同)
  • 它称为 null-forgiving 运算符:https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/operators/null-forgiving (2认同)

Alb*_*rtK 9

除了 Drew 关于C# 9的回答

我们仍然需要T? Get<T>(string key)在调用代码中区分可为空的引用类型和可为空的值类型:

SomeClass? c = Get<SomeClass?>("key"); // return type is SomeClass?
SomeClass? c2 = Get<SomeClass>("key"); // return type is SomeClass?

int? i = Get<int?>("key"); // return type is int?
int i2 = Get<int>("key"); // return type is int
Run Code Online (Sandbox Code Playgroud)

  • 钱币。接得好。因此,“Get()”方法的编写者现在在 C# 9 中不会收到来自编译器的警告,他可能会误以为当“T”是一个结构体时,他们可以返回一个“Nullable&lt;T&gt;”,而无需额外的操作。努力。然而,事实上,调用者将获得一个非空的默认值。对我来说,这似乎是一个非常令人讨厌的小疏忽。 (7认同)

Dre*_*kes 8

在 C# 9 中,您可以更自然地表达无约束泛型的可空性:

public T? Get<T>(string key)
{
    var wrapper = cacheService.Get(key);
    return wrapper.HasValue ? Deserialize<T>(wrapper) : default;
}
Run Code Online (Sandbox Code Playgroud)

请注意!default表达式中没有运算符。与原始示例的唯一变化是添加?T返回类型。