我对以下示例中的C#编译器行为感到惊讶:
int i = 1024;
uint x = 2048;
x = x+i; // A error CS0266: Cannot implicitly convert type 'long' to 'uint' ...
好像int + uint
可以溢出似乎没问题。但是,如果uint
更改为int
,则错误消失,例如int + int
无法溢出:
int i = 1024;
int x = 2048;
x = x+i; // OK, int
此外,uint + uint = uint
:
uint i = 1024;
uint x = 2048;
x = x+i; // OK, uint
似乎完全模糊不清。
为什么int + int = int
和uint + uint = uint
,但int + uint = long
?
这个决定的动机是什么?
答案 0 :(得分:14)
为什么int + int = int和uint + uint = uint,但int + uint = long?这个决定的动机是什么?
问题的表达方式意味着设计团队想要 int + uint的长期预设,并选择类型规则来实现该目标。这个预设是错误的。
相反,设计团队认为:
以及许多其他注意事项,例如设计是否适用于可调试,可维护,可版本化的程序等等。 (我注意到我没有参加这个特别的设计会议,因为它早于我在设计团队的时间。但我已经阅读了他们的笔记,并了解了在此期间设计团队所关心的事情。)
调查这些问题导致了当前的设计:算术运算定义为int + int - > int,uint + uint - > uint,long + long - > long,int可以转换为long,uint可以转换为long,依此类推。
这些决定的结果是在添加uint + int时,重载决策选择long + long作为最接近的匹配,long + long是long,因此uint + int是long。
使uint + int有一些更不同的行为,你可能认为更合理的不是团队的设计目标,因为混合有符号和无符号值是第一个,在实践中很少见,其次,几乎总是一个bug。设计团队可以为有符号和无符号的一,二,四和八字节整数的每个组合添加特殊情况,以及char,float,double和decimal,或者那些的任何子集。数百个案例,但这违背了简单的目标。
简而言之,一方面,我们有大量的设计工作来制作一个功能,我们希望没有人能够以大规模复杂的规格为代价实际使用。另一方面,我们有一个简单的规范,在我们希望没有人在实践中遇到的极少数情况下会产生异常行为。鉴于这些选择,您会选择哪个? C#设计团队选择了后者。
答案 1 :(得分:12)
这是数字类型
的重载解析的表现形式数字提升包括自动执行预定义一元和二元数字运算符的操作数的某些隐式转换。数字提升不是一种独特的机制,而是将重载决策应用于预定义运算符的效果。尽管可以实现用户定义的运算符以表现出类似的效果,但数字促销特别不会影响用户定义的运算符的评估。
http://msdn.microsoft.com/en-us/library/aa691328(v=vs.71).aspx
如果你看一下
long operator *(long x, long y);
uint operator *(uint x, uint y);
从该链接,您会看到这两个可能的重载(示例引用operator *
,但operator +
}也是如此。
uint
隐式转换为long
以进行重载解析,int
也是如此。
从uint到long,ulong,float,double或decimal。
从int到long,float,double或decimal。
http://msdn.microsoft.com/en-us/library/aa691282(v=vs.71).aspx
这个决定的动机是什么?
设计团队的成员可能会回答这个问题。 Eric Lippert,你在哪里? :-)请注意,@ Nicolas的推理下面的推理非常合理,两个操作数都被转换为"最小的"可以包含每个操作数的全部值的类型。
答案 2 :(得分:11)
简短的回答是“因为标准说它应该如此”,它见ISO 23270的信息性§14.2.5.2。规范§13.1。 2。 (隐式数字转换)说:
隐式数字转换为:
<强> ... 强>
- 从
int
到long
,float
,double
或decimal
。- 从
uint
到long
,ulong
,float
,double
或decimal
。<强> ... 强>
从
int
,uint
,long
或ulong
到float
以及long
或ulong
到{{}的转化1}} 会造成精度损失,但永远不会造成损失。 其他隐式数字转换永远不会丢失任何信息。( emph.mine )
[略]更长的答案是你要添加两种不同的类型:32位有符号整数和32位无符号整数:
因此类型不兼容,因为double
不能包含任意int
,而uint
不能包含任意uint
。它们被隐式转换(扩展转换,根据§13.1.2的要求,没有信息丢失)到下一个可以包含两者的最大类型:在这种情况下为int
,带符号的64位整数,其域为-9,223,372,036,854,775,808(0x8000000000000000) - + 9,223,372,036,854,775,807(0x7FFFFFFFFFFFFFFF)。
编辑注意:除此之外,执行此代码:
long
不会产生var x = 1024 + 2048u ;
Console.WriteLine( "'x' is an instance of `{0}`" , x.GetType().FullName ) ;
作为原始海报的示例。相反,产生的是:
long
这是因为常量折叠。表达式中的第一个元素'x' is an instance of `System.UInt32`
没有后缀,因此是1024
,而表达式int
中的第二个元素是2048u
,根据规则:
- 如果文字没有后缀,则它具有其中第一个类型的值 可以表示为:
uint
,int
,uint
,long
。- 如果文字后缀为
ulong
或U
,则其中的第一种类型可以表示其值:u
,uint
。
由于优化器知道值是什么,因此总和是预先计算并评估为ulong
。
一致性是小脑袋的大人物。
答案 3 :(得分:5)
我认为编译器的行为非常符合逻辑和预期。
在以下代码中:
int i;
int j;
var k = i + j;
此操作确实存在重载,因此k
为int
。添加两个uint
,两个byte
或者你有什么相同的逻辑适用。编译器的工作很简单,很高兴因为重载决策找到了完全匹配。编写此代码的人很可能希望k
成为int
,并且知道在某些情况下操作可能会溢出。
现在考虑一下你要问的案例:
uint i;
int j;
var k = i + j;
编译器看到了什么?那么它会看到一个没有匹配过载的操作;没有运算符+
重载,它将int
和uint
作为其两个操作数。因此,重载决策算法继续进行并尝试查找可能有效的运算符重载。这意味着它必须找到一个重载,其中涉及的类型可以“保持”原始操作数;也就是说,i
和j
都必须隐式转换为所述类型。
编译器无法将uint
隐式转换为int
,因为此类转换不会存在。它无法隐式地将int
转换为uint
,因为转换也不存在(两者都可能导致幅度发生变化)。因此,它唯一的选择是选择能够“保持”两种操作数类型的第一种更广泛的类型,在本例中为long
。一旦两个操作数隐式地转换为long
k
,long
就显而易见了。
这种行为的动机是,IMO,选择最安全的可用选项而不是第二次猜测可疑编码器的意图。编译器无法对编写此代码的人期望k
进行有根据的猜测。 int
?那么,为什么不uint
?两种选择看起来同样糟糕。编译器选择唯一的逻辑路径;安全的:long
。如果编码人希望k
为int
或unit
,则他只需要明确地投射其中一个操作数。
最后,但并非最不重要的是,C#编译器的重载决策算法在决定最佳过载时不考虑返回类型。因此,将操作结果存储在uint
中的事实与编译器完全无关,并且在重载解析过程中没有任何影响。
这是我的所有猜测,我可能完全错了。但它确实似乎是逻辑推理。
答案 4 :(得分:2)
i int i = 1024;
uint x = 2048;
// Technique #1
x = x + Convert.ToUInt32(i);
// Technique #2
x = x + checked((uint)i);
// Technique #3
x = x + unchecked((uint) i);
// Technique #4
x = x + (uint)i;
答案 5 :(得分:2)
C#的数字提升规则基于Java和C的数字提升规则,它们通过识别可以转换两个操作数的类型,然后使结果成为相同的类型来工作。我认为这种方法在20世纪80年代是合理的,但是较新的语言应该将其放在一边,而不是考虑如何使用值(例如,如果我正在设计一种语言,那么给定Int32 i1,i2,i3; Int64 l;
编译器将处理{ {1}}使用32位数学[在溢出的情况下抛出异常]将使用64位数学处理i4=i1+i2+i3;
。)但C#规则就是它们的原因并且似乎不太可能变化
应该注意的是,根据定义,C#促销规则总是选择被视为最合适的重载&#34;根据语言规范,但这并不意味着它们确实最适合任何有用的目的。例如,l=i1+i2+i3;
似乎应该产生100.0,如果两个操作数都被提升为double f=1111111100/11111111.0f;
,它将被正确计算,但编译器将把整数1111111100转换为double
产生1111111040.0f,然后执行分部产生99.999992370605469。