简单而正确的答案是“因为C#语言规范是这样规定的”。
显然,你对这个答案不满意,想知道“为什么会这样”。你在寻找“可信和/或官方来源”,这可能有点困难。这些设计决策是很久以前做出的,13年在软件工程中是很长的时间。它们是由“老手”做出的,正如Eric Lippert所称,他们已经转向了更大更好的事情,不会在这里发布答案提供官方来源。
然而,可以推断出一些东西,但存在风险,仅仅是可信的。任何托管编译器,如C#的编译器,都有一个约束,即它需要为.NET虚拟机生成代码。关于这个的规则在CLI规范中精心(也相当易读)地描述。这是Ecma-335规范,你可以从
这里免费下载。
打开第III部分,第3.1和3.2章。它们描述了执行加法的两个IL指令,add和add.ovf。点击链接到表格2,“二进制数值操作”,它描述了哪些操作数适用于这些IL指令。注意那里只列出了一些类型。byte和short以及所有无符号类型都不存在。只允许int、long、IntPtr和浮点数(float和double)。还有附加的限制标记为x,例如你不能将int与long相加。这些约束并不是完全人为的,它们基于可以在可用硬件上合理有效地执行的事情。
任何托管编译器都必须处理这个问题,以生成有效的IL。这并不困难,只需将ushort转换为表中的一个较大值类型,这种转换始终有效。C#编译器选择int,表中出现的下一个更大的类型。或者通常情况下,将任一操作数转换为下一个最大的值类型,使它们都具有相同的类型并满足表中的约束条件。现在有一个新的问题,这个问题让C#程序员感到很恼火。加法的结果是提升类型的结果。在您的情况下,将是int类型。因此,例如添加两个ushort值0x9000和0x9000将具有完全有效的int结果:0x12000。问题是:这是一个无法装入ushort的值。该值
溢出了。但是,在IL计算中它
没有溢出,只有当编译器试图将其压缩回ushort时才会溢出。0x12000被截断为0x2000。这是一个令人困惑的不同值,只有在用2或16个手指进行计数时才有一定意义,而不是10个手指。
需要注意的是,add.ovf指令无法解决这个问题。它是用于自动生成溢出异常的指令。但事实并非如此,转换后的整数实际计算没有溢出。
这就是真正的设计决策所在。老一辈的程序员显然认为,简单地截断int结果为ushort类型将产生错误的代码。当然是这样。他们认为你必须承认你
知道加法可能会溢出,并且如果发生溢出也可以接受。他们把它变成了
您的问题,主要是因为他们不知道如何解决这个问题并且仍然生成高效的代码。你需要进行强制转换。是的,这很让人生气,我敢肯定你也不想遇到这个问题。
有趣的是VB.NET设计者采用了不同的方法来解决这个问题。他们实际上让它成为
他们的问题,而不是推卸责任。您可以将两个UShort相加并将其分配给UShort而无需进行转换。区别在于VB.NET编译器实际上生成了额外的IL以检查溢出条件。这不是廉价的代码,使每个短添加变慢了3倍。但否则,这就是解释微软为什么维护了两种具有非常类似功能的语言的原因。简而言之:您因为使用了与现代CPU架构不是很匹配的类型而支付了代价。这本身就是使用uint而不是ushort的一个非常好的理由。从ushort中获得动力是困难的,您需要很多ushort,才能抵消内存节省的成本。不仅仅是由于受限的CLI规范,x86核心还需要额外的CPU周期来加载16位值,因为机器码中有操作数前缀字节。我不确定今天是否仍然是这种情况,在我还关注计算周期的时候,这是一年前的事情。
请注意,您可以通过让C#编译器生成与VB.NET编译器生成相同的代码来使这些丑陋且危险的强制转换更加安全。因此,当转换被证明是不明智的时候,您会得到OverflowException。在调试版本中使用项目>属性>构建选项卡>高级按钮>选中“检查算术溢出/下溢”复选框。顺便说一下,为什么项目模板没有自动启用此复选框是另一个非常神秘的问题,这是一个太久以前做出的决定。