今天早些时候,我试图添加两个ushort,我注意到我必须将结果返回给ushort。我以为它可能变成了一个uint (为了防止可能的意外溢出?),但令我惊讶的是它是一个int (System.Int32)。
这是不是有一些聪明的原因,或者可能是因为int被视为‘基本’整数类型?
示例:
ushort a = 1;
ushort b = 2;
ushort c = a + b; // <- "Cannot implicitly convert type 'int' to 'ushort'. An explicit conversion exists (are you missing a cast?)"
uint d = a + b; // <- "Cannot implicitly convert type 'int' to 'uint'. An explicit conversion exists (are you missing a cast?)"
int e = a + b; // <- Works!编辑:就像GregS的答案说的那样,C#规范说两个操作数(在本例中是'a‘和'b')都应该转换为整数。我感兴趣的是为什么这是规范的一部分的潜在原因:为什么C#规范不允许直接对ushort值进行操作?
发布于 2012-04-15 05:28:53
简单而正确的答案是“因为C#语言规范这么说”。
显然,你对这个答案并不满意,并想知道“为什么会这样说”。你正在寻找“可信的和/或官方的消息来源”,这将会有点困难。这些设计决策是很久以前就做出的,在软件工程领域13年是非常艰难的。它们是由Eric Lippert所称的“旧时代的人”制作的,他们已经转向更大更好的事情,不会在这里发布答案来提供官方来源。
然而,它可以被推断出来,只是有可能是可信的。任何托管编译器,像C#一样,都有为.NET虚拟机生成代码所需的约束。它的规则在命令行界面规范中有详细的描述(并且很容易阅读)。它是Ecma-335规范,你可以从免费的from here下载它。
转到Partition III,第3.1章和3.2章。它们描述了可用于执行加法的两个IL指令:add和add.ovf。单击表2 "Binary Numeric“的链接,它描述了这些IL指令允许哪些操作数。请注意,这里只列出了几种类型。缺少byte和short以及所有无符号类型。仅允许int、long、IntPtr和浮点数(浮点数和双精度)。使用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是一个bug工厂。当然是这样。他们决定,你必须承认,你知道添加可能会溢出,如果它发生了,也是可以的。他们让它成为你的问题,主要是因为他们不知道如何让它成为他们自己的,同时仍然生成高效的代码。你得去选角。是的,这是令人抓狂的,我相信你也不想要这个问题。
值得注意的是,VB.NET设计者对这个问题采取了不同的解决方案。他们实际上是把它当作自己的问题,而不是推卸责任。您可以添加两个UShorts并将其分配给没有强制转换的UShort。不同之处在于,VB.NET编译器实际上会生成额外的IL来检查溢出条件。这可不是什么便宜的代码,这使得每个简短的加法都慢了3倍。但除此之外,这也解释了为什么微软维护着两种在其他方面功能非常相似的语言。
长话短说:您正在付出代价,因为您使用的类型与现代cpu架构不是很好匹配。这本身就是使用uint而不是ushort的一个很好的理由。从ushort中获得牵引力是困难的,在操作它们的成本超过内存节省之前,你将需要大量的ushort。不仅仅是因为有限的CLI规范,由于机器代码中的操作数前缀字节,x86内核还需要额外的cpu周期来加载16位的值。不确定今天是否还是这样,当我还在关注计数周期的时候,它又回来了。一年前的一条狗。
请注意,通过让C#编译器生成与VB.NET编译器生成的代码相同的代码,您可以更好地处理这些丑陋和危险的类型转换。因此,当演员阵容被证明是不明智的时,你会得到一个OverflowException。使用Project > Properties > Build选项卡> Advanced按钮>勾选"Check for /underflow“复选框。仅用于Debug构建。为什么这个复选框没有被项目模板自动打开,这是另一个非常令人费解的问题,这是一个很久以前就做出的决定。
发布于 2012-04-09 02:36:57
ushort x = 5, y = 12;下面的赋值语句将产生编译错误,因为在默认情况下,赋值运算符右侧的算术表达式的计算结果为int。
ushort z = x + y; // Error: conversion from int to ushorthttp://msdn.microsoft.com/en-us/library/cbf1574z(v=vs.71).aspx
编辑:
在ushort上进行算术运算的情况下,操作数被转换为可以容纳所有值的类型。因此可以避免溢出。操作数可以按照int、uint、long和ulong的顺序进行更改。请参阅本文档中的C# Language Specification转到4.1.5整数类型一节( word文档中的第80页左右)。您将在此处找到:
对于二进制+、-、*、/、%、&、^、|、==、!=、>、<、>=和<=运算符,操作数被转换为类型T,其中T是int、uint、long和ulong中的第一个,可以完全表示两个操作数的所有可能值。然后使用类型T的精度执行操作,结果的类型为T(对于关系运算符,则为bool )。使用二元运算符时,不允许一个操作数属于long类型,而另一个操作数属于ulong类型。
埃里克·利珀在一次question中表示
在C#中,
运算永远不会在短时间内完成。算术可以在in,uint,long和ulong中完成,但算术永远不会在short中完成。Short提升为int,并且算术是在int中完成的,因为就像我前面说过的,绝大多数算术计算都适合int。绝大多数人都不适合做空头。短算术在为整数优化的现代硬件上可能会更慢,并且短算术不会占用更少的空间;它将在芯片上以整型或长整型完成。
发布于 2012-04-09 02:42:11
来自C#语言规范:
7.3.6.2二进制数值升级对于预定义的+、-、*、/、%、&、|、^、==、!=、>、<、>=和<=二元运算符的操作数进行二进制数值升级。二进制数值提升隐式地将两个操作数转换为公共类型,对于非关系运算符,该类型也成为操作的结果类型。二进制数字升级包括按以下规则在此处出现的顺序应用这些规则:
·如果其中一个操作数是decimal类型,则另一个操作数转换为decimal类型,或者如果另一个操作数是float或double类型,则会发生绑定时错误。
·否则,如果其中一个操作数属于双精度类型,则另一个操作数将转换为双精度类型。
·否则,如果其中一个操作数的类型为float,则另一个操作数将转换为float类型。
·否则,如果其中一个操作数的类型为ulong,则另一个操作数将转换为ulong类型,否则,如果另一个操作数的类型为sbyte、short、int或long,则会出现绑定时间错误。
·否则,如果其中一个操作数的类型为long,则另一个操作数将转换为long类型。
·否则,如果其中一个操作数的类型为uint,而另一个操作数的类型为sbyte、short或int,则两个操作数都将转换为long类型。
·否则,如果其中一个操作数属于uint类型,则另一个操作数将转换为uint类型。
·否则,两个操作数都转换为int类型。
https://stackoverflow.com/questions/10065287
复制相似问题