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

时间:2019-02-08 13:55:39

标签: c# generics c#-8.0 nullablereferencetypes

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

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

现在,这将给出一个警告Possible null reference return,这是合乎逻辑的,因为default(T)将为所有引用类型提供null。起初我以为可以将其更改为以下内容:

public T? Get<T>(string key)

但这无法完成。它说我要么必须添加通用约束where T : class要么where T : struct。但这不是一个选择,因为两者都可以(我可以在缓存中存储intint?FooBar的实例或任何东西)。 我还读了一个假定的新通用约束where class?,但这似乎不起作用。

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

return wrapper.HasValue ? Deserialize<T>(wrapper) : default!;

但这感觉很不对,因为它肯定可以为null,所以我基本上在这里对编译器撒谎:-)

我该如何解决?我在这里完全看不到什么东西吗?

4 个答案:

答案 0 :(得分:7)

我认为default!是目前为止您可以做的最好的事情。

public T? Get<T>(string key)不起作用的原因是因为可为空的引用类型与可为空的值类型完全不同

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

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

换句话说,说T?就是要返回T,而T是引用类型则要返回Nullable<T>T是一个值类型。听起来不像是方法的有效返回类型,是吗?

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

答案 1 :(得分:5)

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

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

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

答案 2 :(得分:3)

您非常亲密。像这样编写您的方法:

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

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

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

有关更多信息,请参阅Microsoft文档:Specify post-conditions: MaybeNull and NotNull

答案 3 :(得分:0)

除了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