Nullable<T>与类似的自定义C#结构体有何不同?

4
可空微优化,第一部分中,Eric提到Nullable<T>有一种奇怪的装箱行为,这是一个类似的用户定义类型所无法实现的。
C#语言授予预定义的Nullable<T>类型的特殊功能是什么?特别是那些不能在MyNullable类型上工作的功能。
当然,Nullable<T>有特殊的语法糖T?,但我的问题更多地涉及语义。

1
我是偶然发现了你在这里的问题。考虑将这样的问题留在博客文章本身上。如果您对文章有疑问,也可以随时给我发送电子邮件;博客的“关于”页面上有联系信息。 - Eric Lippert
我确实考虑过,但我也想了解除了装箱之外的特殊支持,所以我转向了 Stack Overflow。 - Eldritch Conundrum
3个回答

11
我所想表达的是: 没有所谓的可空值类型(Boxed Nullable)。当你对一个int进行装箱操作时,你会得到一个引用到一个装箱后的int。而当你对一个int?进行装箱操作时,你要么得到一个空引用,要么得到一个装箱后的int的引用,你从来不会得到一个装箱后的int?

你可以轻松地创建自己的 Optional<T> 结构体,但是你无法实现具有这种装箱行为的结构体。Nullable<T> 的特殊行为在运行时内核中被固定。

这个事实导致了许多奇怪的问题。例如:

另外,你需要知道的是,Nullable<T> 类型还有其他一些“神奇”的地方。例如,尽管它是一个结构体类型,但它不满足结构体约束。你无法创建具有这个属性的自定义结构体。


我明白了...所以如果我理解正确的话,优化是导致这些奇怪行为的原因。感谢您的解释! - Eldritch Conundrum
2
@EldritchConundrum:不完全是这样。相反,这些奇怪的事情是可空值语义和可空引用语义之间不匹配的结果。故事的寓意是:在v1.0中向类型系统添加可空性,而不是在v2.0中。如果可空引用类型和可空值类型同时发明,它们会更加相似。 - Eric Lippert
@EricLippert:你认为事情会有什么不同的做法吗?我对Nullable<T>的设计最大的反对意见是,至少按照我的理解,在大多数情况下,当代码获得一个Nullable<T>时,它想要的是一个带有有效性指示的T;而Nullable<T>周围的许多额外东西会妨碍这个基本任务。你是否有不同的看法? - supercat

2

C#提供了对可空类型的运算符支持。例如:

int? SumNullableInts(int? a, int? b)
{
    return a + b;
}

如果要支持这个功能,你需要在MyNullable<T>中进行大量的反射工作,然后下面的代码将会编译通过,但实际上它不应该通过:

MyNullable<List<string>.Enumerator> SumNullableEnumerators(MyNullable<List<string>.Enumerator> a, MyNullable<List<string>.Enumerator> b)
{
    return a + b;
}

2
我在C#规范中找到了以下两个内容:
  • is运算符对T?的操作与对T的操作相同,as运算符可以转换为可空类型。
  • 对于非空值类型的预定义和用户定义运算符,会将其提升到这些类型的可空形式。

现在,以下是我认为不仅适用于Nullable<T>的特性:

  • switch中的值可以是可空类型。我不认为这算,因为switch也接受在MyNullable类型上定义的用户定义隐式转换。
  • 支持可空IDisposable类型,在调用Dispose()生成的代码之前插入了一个空检查。我不认为这算,因为我可以将MyNullable定义为类,然后它就是相同的了。

以下是我不确定的:

  • 规范提到了装箱/拆箱和隐式/显式转换,但我不明白是否可以使用MyNullable来实现相同的结果。

无法指定除 Nullable 之外的值类型不应转换为 Object,或者转换结果不应该是具有与结构体相同字段并初始化为相同值的对象。 - supercat

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接