using System; public class Tester { public static void Main() { const uint x=1u; const int y=-1; Console.WriteLine((x+y).GetType()); // Let's refactor and inline y... oops! Console.WriteLine((x-1).GetType()); } }
想象一下上面的代码在以下情况中使用:
public long Foo(uint x) { const int y = -1; var ptr = anIntPtr.ToInt64() + (x + y) * 4096; return ptr; }
内联y看起来非常安全,但事实并非如此.语言本身的这种不一致是违反直觉的,而且非常危险.大多数程序员只是内联y,但实际上你最终会出现整数溢出错误.事实上,如果你编写如上所述的代码,你很容易让下一个人在同一条代码内联工作,甚至没有考虑过两次.
我认为这是C#的一个非常适得其反的语言设计问题.
第一个问题,在C#规范中定义了这种行为,为什么这样设计?
第二个问题,1.GetType()/( – 1).GetType()给出了System.Int32.那么为什么它与const int y = -1的行为不同?
第三个问题,如果隐式转换为uint,那么我们如何明确地告诉编译器它是一个有符号整数(1i不是有效的语法!)?
最后一个问题,这不可能是语言设计团队所期望的行为(Eric Lippert要编钟吗?),可以吗?
解决方法
• A constant-expression (§7.19) of type int can be converted to type sbyte,byte,short,ushort,uint,or ulong,provided the value of the constant-expression is within the range of the destination type.
所以你有常数x = 1u;和常量表达式(x – 1).
根据规范,x-1的结果通常是int,但由于常量表达式(即0)的值在uint的范围内,因此将其视为uint.
请注意,这里编译器将1视为无符号.
如果将表达式更改为(x -1),则将-1视为signed,并将结果更改为int. (在这种情况下,– in -1是一个“一元运算符”,它将-1的结果类型转换为int,因此编译器不能再像普通1那样将它转换为uint.
规范的这一部分暗示如果我们将常量表达式更改为x – 2,那么结果将不再是uint,而是转换为int.但是,如果进行了更改,则会收到编译错误,指出结果将溢出uint.
这是因为C#规范的另一部分,在7.19节常量表达式中指出:
The compile-time evaluation of constant expressions uses the same rules as run-time evaluation of non-constant expressions,except that where run-time evaluation would have thrown an exception,compile-time evaluation causes a compile-time error to occur.
在这种情况下,如果进行检查计算会出现溢出,因此编译器会发生故障.
关于这个:
const uint x = 1u; const int y = -1; Console.WriteLine((x + y).GetType()); // Long
这与此相同:
Console.WriteLine((1u + -1).GetType()); // Long
这是因为-1的类型为int,而1u的类型为uint.
第7.3.6.2节二进制数字促销描述了这个:
•否则,如果任一操作数的类型为uint而另一个操作数的类型为sbyte,short或int,则两个操作数都将转换为long类型.
(我省略了与此特定表达式无关的部分.)
附录:我只是想指出常数和非常数值之间的一元减号(又称“否定”)运算符的细微差别.
根据标准:
If the operand of the negation operator is of type uint,it is converted to type long,and the type of the result is long.
变量也是如此:
var p = -1; Console.WriteLine(p.GetType()); // int var q = -1u; Console.WriteLine(q.GetType()); // long var r = 1u; Console.WriteLine(r.GetType()); // uint
虽然对于编译时常量,如果涉及uint的表达式使用它,则将值1转换为uint,为了将整个表达式保持为uint,-1实际上被视为int.
我同意OP – 这是非常微妙的东西,导致各种惊喜.