简单而正确的答案是“因为 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 中获得牵引力是很困难的,在操纵它们的成本超过内存节省之前,你需要很多它们。不仅仅是因为有限的 CLI 规范,x86 内核需要额外的 cpu 周期来加载 16 位值,因为机器代码中的操作数前缀字节。实际上不确定今天是否仍然如此,当我仍然注意计算周期时,它曾经回来过。一年前的狗。
请注意,通过让 C# 编译器生成与 VB.NET 编译器生成的代码相同的代码,您可以更好地了解这些丑陋和危险的强制转换。所以当演员被证明是不明智的时,你会得到一个 OverflowException。使用项目>属性>构建选项卡>高级按钮>勾选“检查算术上溢/下溢”复选框。仅用于调试版本。为什么项目模板没有自动打开此复选框是另一个非常神秘的问题,顺便说一句,这是很久以前做出的决定。