考虑以下代码:
byte b = 100;
b = (byte)(b+200);
这段代码在实际运行中是否会抛出溢出异常呢?
先不管答案,我们来看看CLR是如何应对溢出的:
CLR提供的IL指令允许编译器选择自己期望的行为。CLR提供了一个名为add的指令,会直接对两个数做加法运算,而不做任何溢出检查。同时,CLR还提供了一个名为add.ovf的指令,它在对两数做加法运算时,一旦发出溢出,便会抛出一个System.OverflowException异常。除这两个加法运算指令外,CLR还提供了类似的减法(sub/sub.ovf)、乘法(mul/mul.ovf),以及数据转换(conv/conv.ovf)运算指令。
C#允许开发人员自己决定如何处理溢出。默认情况下,溢出检查是关闭的。这意味着编译器产生的IL指令中的加、减、乘及转换运行是不包含溢出检查的版本。这样的结果是代码的效率有所提高。
程序员如何对溢出进行检查呢?C#中的checked(检查溢出)和unchecked(不检查溢出)操作符为我们提供了这种灵活性。
看下面的例子:
byte b = 100;
b = checked((byte)(b + 200)); //抛出OverflowException异常
b = (byte)(b + 200); //不抛出异常
b = unchecked((byte)(b + 200)); //不抛出异常
除了checked和unchecked操作符外,C#还提供了checked和unchecked语句,它可使整个语句块中的表达式都接受或不接受溢出检查。
使用示例如下:
checked
{
byte b = 100;
b = (byte)(b + 200); //抛出异常
}
unchecked
{
byte b = 100;
b = (byte)(b + 200); //不抛出异常
}
很简单吧,溢出与否完全控制在程序员自己的手中。
再考虑以下代码:
void Method1()
{
byte b = 100;
b = (byte)(b + 200);
}
void Method2()
{
checked
{
//会抛出异常吗?
Method1();
}
}
答案是不会抛出异常。因为checked操作符和语句只影响加、减、乘以及转换IL指令产生的版本,在checked操作符或语句内调用一个方法不会对该方法产生任何影响。
怎么合理使用checked和unchecked呢?以下是一些推荐原则:
System.Decimal的溢出:
System.Decimal是一个非常特殊的类型。虽然很多编程语言(如C#和VB)都将该类型看作是一个基元类型,但CLR却不是这样。这意味着CLR没有直接操作Decimal类型的IL指令,CLR对Decimal类型的运算操作其实是通过Add、Subtract、Multiply、Divide等几个静态方法实现。因此,checked和unchecked操作符和语句对Decimal类型没有任何影响。若对Decimal值的操作没有安全执行,系统总会抛出OverflowException异常。另外,由于没有相对应的IL指令,操作Decimal值的代码效率比操作其他CLR基元类型的代码效率要低,如无特殊需要,应量避免使用Decimal类型。
题外话:
CLR只在32位和62位值上进行算术运算。所以代码:
byte b = 100;
b = (byte)(b+200);
在运算时,b和200首先转换为32位值,然后相加,得到结果亦为32位值,接着再转型为一个byte类型,然后才能将其放入变量b的存储堆栈内。回想自己以前做项目时用到了位运算符&、|、^等在byte上进行运算,但却发现运算结果总是变成了int类型,当时心里那个奇怪,原因正在于此。