可空引用类型:如何指定“T?”类型不限于类或结构
Posted
技术标签:
【中文标题】可空引用类型:如何指定“T?”类型不限于类或结构【英文标题】:Nullable reference types: How to specify "T?" type without constraining to class or struct 【发布时间】:2019-09-22 07:39:44 【问题描述】:我想创建一个具有T
类型成员的泛型类。 T
可以是类、可为空的类、结构或可为空的结构。所以基本上什么都有。这是一个显示我的问题的简化示例:
#nullable enable
class Box<T>
public T Value get;
public Box(T value)
Value = value;
public static Box<T> CreateDefault()
=> new Box<T>(default(T));
由于使用了新的#nullable enable
功能,我收到以下警告:Program.cs(11,23): warning CS8653: A default expression introduces a null value when 'T' is a non-nullable reference type.
这个警告对我来说很有意义。然后我尝试通过在属性和构造函数参数中添加?
来修复它:
#nullable enable
class Box<T>
public T? Value get;
public Box(T? value)
Value = value;
public static Box<T> CreateDefault()
=> new Box<T>(default(T));
但现在我得到了两个错误:
Program.cs(4,12): error CS8627: A nullable type parameter must be known to be a value type or non-nullable reference type. Consider adding a 'class', 'struct', or type constraint.
Program.cs(6,16): error CS8627: A nullable type parameter must be known to be a value type or non-nullable reference type. Consider adding a 'class', 'struct', or type constraint.
但是,我不想添加约束。我不在乎 T
是类还是结构。
一个明显的解决方案是将违规成员包装在#nullable disable
指令下。但是,就像#pragma warning disable
一样,除非有必要,否则我想避免这样做。是否有其他方法可以在不禁用可空性检查或 CS8653 警告的情况下编译我的代码?
$ dotnet --info
.NET Core SDK (reflecting any global.json):
Version: 3.0.100-preview4-011223
Commit: 118dd862c8
【问题讨论】:
我没有更仔细地研究这个问题,但我假设T?
值类型和引用类型之间的差异根本不由编译器处理。为什么?我可以猜它只会增加大量的复杂性,但我也猜想您需要来自实际编译器人员的输入才能确定。值类型的T?
由Nullable<T>
处理,而C# 8 中引用类型的T?
由具有属性的T
处理。基本上,我认为这根本不被支持。
嗨@Andent,欢迎来到SO。您实际上是在打开一个设置,告诉编译器 not 允许将null
分配给T
类型的变量(当T
是引用类型时),然后使用@987654341 @ which 可以为引用类型返回 null
。这里没有什么神秘之处。你必须选择一条路径,比如 Neo... :-)
您可能正在考虑同时制作Box<T> where T : class
和ValueBox<T> where T : struct
。我认为目前没有办法通过T?
和Nullable<T>
统一泛型类型/方法。
从根本上说,可空类型和泛型不能很好地混合。根据我的经验,这是设计中最棘手的部分。
我认为您在这里有一些相互矛盾的目标。您想拥有default
框的概念,但对于引用类型,还有什么是合适的default
?对于与使用可空引用类型直接冲突的引用类型,默认值为 null
。也许您需要将 T
限制为可以默认构造的类型 (new()
)。
【参考方案1】:
如果您使用的是 C# 9,该怎么办
在 C# 9 中,您可以在不受约束的类型参数上使用T?
来指示当 T 是引用类型时该类型始终可以为空。事实上,原始问题中的示例在将?
添加到属性和构造函数参数后“正常工作”。请参阅以下示例以了解您对 Box<T>
的不同类型参数的预期行为。
var box1 = Box<string>.CreateDefault();
// warning: box1.Value may be null
box1.Value.ToString();
var box2 = Box<string?>.CreateDefault();
// warning: box2.Value may be null
box2.Value.ToString();
var box3 = Box<int>.CreateDefault();
// no warning
box3.Value.ToString();
var box4 = Box<int?>.CreateDefault();
// warning: 'box4.Value' may be null
box4.Value.Value.ToString();
如果您使用 C# 8 该怎么办
在 C# 8 中,无法在不受约束的类型参数(即未知的引用类型或值类型)上放置可为空的注释。
正如 cmets 关于这个问题的讨论,您可能需要考虑具有默认值的 Box<string>
在可空上下文中是否有效,并可能相应地调整您的 API 表面。也许类型必须是 Box<string?>
才能使包含默认值的实例有效。但是,在某些情况下,您需要指定属性、方法返回或参数等仍然可以为 null,即使它们具有不可为空的引用类型。如果您属于该类别,您可能希望使用与可空性相关的属性。
MaybeNull 和 AllowNull 属性已被引入 .NET Core 3 以处理这种情况。
这些属性的一些具体行为仍在不断发展,但基本思想是:
[MaybeNull]
表示某事物的输出(读取字段或属性、方法返回等)可能是null
。
[AllowNull]
表示某事物(写入字段或属性、方法参数等)的 输入 可以是 null
。
#nullable enable
using System.Diagnostics.CodeAnalysis;
class Box<T>
// We use MaybeNull to indicate null could be returned from the property,
// and AllowNull to indicate that null is allowed to be assigned to the property.
[MaybeNull, AllowNull]
public T Value get;
// We use only AllowNull here, because the parameter only represents
// an input, unlike the property which has both input and output
public Box([AllowNull] T value)
Value = value;
public static Box<T> CreateDefault()
return new Box<T>(default);
public static void UseStringDefault()
var box = Box<string>.CreateDefault();
// Since 'box.Value' is a reference type here, [MaybeNull]
// makes us warn on dereference of it.
_ = box.Value.Length;
public static void UseIntDefault()
// Since 'box.Value' is a value type here, we don't warn on
// dereference even though the original property has [MaybeNull]
var box = Box<int>.CreateDefault();
_ = box.Value.ToString();
请参阅https://devblogs.microsoft.com/dotnet/try-out-nullable-reference-types 了解更多信息,尤其是“the issue with T?”部分。
【讨论】:
令人着迷。这是我的第一次尝试,但没有成功,我也不知道为什么。事实证明,如果我将您的示例更改为使用“default(T)”而不是“default”,我将得到相同的 CS8653,这是我完全没想到的。我应该认为它们在语义上是相同的——事实上,我认为纯默认值只有在它们存在时才是合法的! 如果我将您的示例更改为省略 Value 上的 [MaybeNull],我根本不会收到任何警告,这对我来说甚至更陌生。并调用 Boxdefault
的警告是一个错误。追踪于github.com/dotnet/roslyn/issues/38339。我更新了我的答案以更详细地介绍,并希望能更清楚地了解您所看到的行为。
C# 9 提供的解决方案仍然不能令人满意,因为它将int?
强制转换为int
而不是Nullable<int>
... :'(
当@JonSkeet 称其为“设计中最棘手的部分”时,您就会知道这确实是一个棘手的问题!就我个人而言,我认为我们在语言上走错了路,我相信不可为空的引用类型应该是完整类型。事实上,在 C#9 中,当 T 不受约束(甚至被约束为 notnull)时,语法“T?”表示“对应于 T 的可默认类型”而不是“对应于 T 的可空类型”,因为它在语言中的其他任何地方都是如此。但是,如果您将 T 限制为类或结构,那么“T?”再次开始表示“对应于T的可空类型”。【参考方案2】:
Jeff Mercado 在 cmets 中提出了一个很好的观点:
我认为您在这里有一些相互矛盾的目标。您想要默认框的概念,但对于引用类型,还有什么是合适的默认值?对于与使用可空引用类型直接冲突的引用类型,默认值为 null。也许您需要将 T 限制为可以默认构造的类型(new())。
例如,T = string
的default(T)
将是null
,因为在运行时string
和string?
之间没有区别。这是当前语言功能的限制。
我通过为每个案例创建单独的CreateDefault
方法来解决这个问题:
#nullable enable
class Box<T>
public T Value get;
public Box(T value)
Value = value;
static class CreateDefaultBox
public static Box<T> ValueTypeNotNull<T>() where T : struct
=> new Box<T>(default);
public static Box<T?> ValueTypeNullable<T>() where T : struct
=> new Box<T?>(null);
public static Box<T> ReferenceTypeNotNull<T>() where T : class, new()
=> new Box<T>(new T());
public static Box<T?> ReferenceTypeNullable<T>() where T : class
=> new Box<T?>(null);
这种似乎类型对我来说是安全的,但代价是更丑陋的呼叫站点(CreateDefaultBox.ReferenceTypeNullable<object>()
而不是Box<object?>.CreateDefault()
)。在我发布的示例类中,我将完全删除方法并直接使用Box
构造函数。哦,好吧。
【讨论】:
【参考方案3】:class Box<T>
public T! Value get;
public Box(T! value)
Value = value;
public static Box<T> CreateDefault()
=> new default!;
What does null! statement mean?
【讨论】:
这不会编译。您不能只将!
任何地方 - 它出现在表达式的末尾,而不是类型的末尾。
哇,真的是 Jon skeet!我认为你是对的,但我希望它有助于找到正确的方法
我认为这对这里没有帮助。 OP 希望 Value
可以为空,据我所知,无法在 C# 8 中表达这一点。以上是关于可空引用类型:如何指定“T?”类型不限于类或结构的主要内容,如果未能解决你的问题,请参考以下文章