试图更好地理解为什么这是一种语言功能:
我们有:
public static DateTime? Date { get; set; }
static void Main(string[] args)
{
Date = new DateTime(2017, 5, 5);
Console.WriteLine(Date.Value.Date);
Console.Read();
}
Run Code Online (Sandbox Code Playgroud)
为什么我需要使用Value从可空类型中获取值?它不像在调用Date之前检查null,如果值为null,它将抛出NullReference异常.我明白了.HasValue可以工作,
但我不确定为什么我们需要.每个nulllable类型的值?
Eri*_*ert 16
首先让我们澄清一下这个问题.
在C#1.0中,我们有两大类的类型:值类型,它们永远不为null,引用类型可以为空. *
两个值和引用类型支持成员访问运算符,.,它选择与实例相关联的值.
对于引用类型,.运算符与接收者的可为空性之间的关系是:如果接收者是空引用,则使用.运算符会产生异常.由于C#1.0值类型首先不可为空,因此无需指定.空值类型时会发生什么.他们不存在.
在C#2.0中添加了可空值类型.就其内存表示而言,可空值的类型并不神奇; 它只是一个带有值实例的结构,而bool则表示它是否为null.
有一些编译器magic(**),因为可空值的类型伴随着提升语义.通过解除,我们的意思是对可空值类型的操作具有"如果值不为null,则对值进行操作并将结果转换为可空类型;否则结果为空"的语义.(***)
也就是说,如果我们有
int? x = 2;
int? y = 3;
int? z = null;
int? r = x + y; // nullable 5
int? s = y + z; // null
Run Code Online (Sandbox Code Playgroud)
在幕后,编译器正在做各种魔术以有效地实现提升算术; 如果这个主题让你感兴趣,请参阅我关于如何编写优化器的冗长的博客系列.
但是,.操作员未被抬起.它可能是!至少有两种可能的设计是有意义的:
nullable.Whatever() 可以表现为可为空的引用类型:如果接收者为null,则抛出异常,或者nullable为null则调用将Whatever()被删除,结果为Whatever()返回任何类型的null .所以问题是:
为什么要求操作员
.Value.有合理的设计.来工作和提取基础类型的成员?
好.
注意我刚刚在那里做了什么.有两种可能性既有完美的意义,又与语言的既定的,易于理解的方面一致,并且它们相互矛盾.语言设计者发现自己在这进退两难的所有时间.我们现在处于这样一种情况:完全不明显是否可以.在可.引用值类型上执行类似于引用类型,或者是否.应该+在可空int上运行.两者都是合理的.无论选择哪一个,都会有人认为这是错误的.
语言设计团队考虑了明确的替代方案.例如,?.明确提升为可空成员访问权限的"Elvis" 运算符.这被考虑用于C#2.0但被拒绝,然后最终添加到C#6.0.还考虑了一些其他的句法解决方案,但所有这些解决方案都因历史遗失而被拒绝.
我们已经看到我们已经为.价值类型设计了一个潜在的设计雷区,但是等等,它会变得更糟.
现在让我们考虑.应用于值类型的另一个方面:如果值类型是可怕的可变值类型,并且成员是字段,那么x.y是变量 if x是变量,否则是值.也就是说,x.y = 123如果x是变量,则是合法的.但是如果x不是变量,则C#编译器不允许赋值,因为赋值将被赋予该值的副本.
这与可空值类型有何关系?如果我们有一个可空的可变值类型X?那么什么呢
x.y = 123
Run Code Online (Sandbox Code Playgroud)
做?请记住,x真的是实例不变型Nullable<X>,因此,如果这意味着x.Value.y = 123然后,我们通过突变的返回值的副本Value属性,这似乎非常,非常错误的.
那么我们该怎么办?可以为空的值类型本身是否可变?这种突变会如何起作用?它是复制拷贝出来的语义吗?这意味着这ref x.y将是非法的,因为ref需要变量,而不是财产.
这将成为一个巨大的怪人.
在C#2.0中,设计团队试图将泛型添加到语言中; 如果您曾尝试将泛型添加到现有类型系统,那么您就知道它有多少工作量.如果你还没有,那么这是很多工作.我认为设计团队可以通过决定解决所有这些问题,并且.对可空值类型没有特殊意义."如果你想要这个价值,那么你可以打电话.Value",这样做的好处就是不需要设计团队特别的工作!同样地,"如果使用可变的可空值类型会受到伤害,那么可能会停止这样做"对于设计人员来说成本很低.
如果我们生活在一个完美的世界中,那么我们在C#1.0 中将有两种正交类型:引用类型与值类型,以及可空类型与非可空类型.我们得到的是C#1.0中的可空引用类型和非可空值类型,C#2.0中的可空值类型,以及C#8.0中有类型的非可空引用类型,十年半之后.
在完美的世界里,我们会整理出所有的操作语义,起重语义,变量语义等,全部一次,使它们保持一致.
但是,嘿,我们不是生活在那个完美的世界里.我们生活在一个完美是善的敌人的世界里,你必须说.Value.而不是.在C#2.0到5.0和?.C#6.0中.
* 我故意忽略指针类型,这些指针类型是可空的,具有值类型的一些特征和引用类型的一些特征,并且它们有自己的特殊运算符用于解除引用和成员访问.
**在诸如以下内容中也存在神奇:可空值类型不满足值类型约束,可空值类型框可以为空引用或盒装底层类型,以及许多其他小特殊行为.但内存布局并不神奇; 它只是一个bool旁边的值.
*** 功能程序员当然知道这可能是monad上的绑定操作.