具有泛型 return 类型的可空引用类型
Nullable reference types with generic return type
我正在尝试使用新的 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
。但这不是一个选项,因为它可以是两者(我可以存储 int
或 int?
或 FooBar
的实例或缓存中的任何内容)。
我还读到了一个假定的新通用约束 where class?
,但这似乎不起作用。
我能想到的唯一简单解决方案是使用 null 宽容运算符 :
更改 return 语句
return wrapper.HasValue ? Deserialize<T>(wrapper) : default!;
但这感觉不对,因为它肯定可以为空,所以我基本上是在欺骗编译器..
我该如何解决这个问题?我在这里错过了一些非常明显的东西吗?
我认为 default!
是你目前能做的最好的了。
public T? Get<T>(string key)
不起作用的原因是可空引用类型与可空值类型非常不同。
可为空的引用类型纯粹是编译时的事情。小问号和感叹号仅供编译器用于检查可能的空值。在运行时看来,string?
和 string
完全相同。
另一方面,可空值类型是 Nullable<T>
的语法糖。当编译器编译你的方法时,它需要决定你的方法的 return 类型。如果 T
是引用类型,您的方法将具有 return 类型 T
。如果 T
是值类型,则您的方法将具有 Nullable<T>
的 return 类型。但是当 T
可以是两者时,编译器不知道如何处理它。它当然不能说“如果T
是引用类型,return类型就是T
,如果T
是引用类型,它就是Nullable<T>
。 “因为 CLR 不会理解这一点。一个方法应该只有 one return 类型。
换句话说,当 T
是参考时,说你想要 return T?
就像说你想要 return T
类型,当 T
是值类型时,return Nullable<T>
。这听起来不像是方法的有效 return 类型,是吗?
作为一个非常糟糕的解决方法,您可以声明两个具有不同名称的方法 - 一个 T
限制为值类型,另一个 T
限制为引用类型:
public T? Get<T>(string key) where T : class
{
var wrapper = cacheService.Get(key);
return wrapper.HasValue ? Deserialize<T>(wrapper) : null;
}
public T? GetStruct<T>(string key) where T : struct
{
var wrapper = cacheService.Get(key);
return wrapper.HasValue ? (T?)Deserialize<T>(wrapper) : null;
}
你们非常亲密。像这样写你的方法:
[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
在 C# 9 中,您可以更自然地表达无约束泛型的可空性:
public T? Get<T>(string key)
{
var wrapper = cacheService.Get(key);
return wrapper.HasValue ? Deserialize<T>(wrapper) : default;
}
请注意 default
表达式中没有 !
运算符。与原始示例的唯一变化是 ?
添加到 T
return 类型。
除了 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
我正在尝试使用新的 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
。但这不是一个选项,因为它可以是两者(我可以存储 int
或 int?
或 FooBar
的实例或缓存中的任何内容)。
我还读到了一个假定的新通用约束 where class?
,但这似乎不起作用。
我能想到的唯一简单解决方案是使用 null 宽容运算符 :
更改 return 语句return wrapper.HasValue ? Deserialize<T>(wrapper) : default!;
但这感觉不对,因为它肯定可以为空,所以我基本上是在欺骗编译器..
我该如何解决这个问题?我在这里错过了一些非常明显的东西吗?
我认为 default!
是你目前能做的最好的了。
public T? Get<T>(string key)
不起作用的原因是可空引用类型与可空值类型非常不同。
可为空的引用类型纯粹是编译时的事情。小问号和感叹号仅供编译器用于检查可能的空值。在运行时看来,string?
和 string
完全相同。
另一方面,可空值类型是 Nullable<T>
的语法糖。当编译器编译你的方法时,它需要决定你的方法的 return 类型。如果 T
是引用类型,您的方法将具有 return 类型 T
。如果 T
是值类型,则您的方法将具有 Nullable<T>
的 return 类型。但是当 T
可以是两者时,编译器不知道如何处理它。它当然不能说“如果T
是引用类型,return类型就是T
,如果T
是引用类型,它就是Nullable<T>
。 “因为 CLR 不会理解这一点。一个方法应该只有 one return 类型。
换句话说,当 T
是参考时,说你想要 return T?
就像说你想要 return T
类型,当 T
是值类型时,return Nullable<T>
。这听起来不像是方法的有效 return 类型,是吗?
作为一个非常糟糕的解决方法,您可以声明两个具有不同名称的方法 - 一个 T
限制为值类型,另一个 T
限制为引用类型:
public T? Get<T>(string key) where T : class
{
var wrapper = cacheService.Get(key);
return wrapper.HasValue ? Deserialize<T>(wrapper) : null;
}
public T? GetStruct<T>(string key) where T : struct
{
var wrapper = cacheService.Get(key);
return wrapper.HasValue ? (T?)Deserialize<T>(wrapper) : null;
}
你们非常亲密。像这样写你的方法:
[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
在 C# 9 中,您可以更自然地表达无约束泛型的可空性:
public T? Get<T>(string key)
{
var wrapper = cacheService.Get(key);
return wrapper.HasValue ? Deserialize<T>(wrapper) : default;
}
请注意 default
表达式中没有 !
运算符。与原始示例的唯一变化是 ?
添加到 T
return 类型。
除了 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