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 : class或where T : struct.但是,这是不是一种选择,因为这既可以是(我可以存储int或int?或实例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
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约束为引用类型。
除了 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)
在 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返回类型。