考虑以下代码:

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呢?以下是一些推荐原则:

  1. 如果希望在出现溢出时抛出异常,就应该显式使用checked。
  2. 如果即使出现了溢出,我们也不希望有异常抛出,那就应该显式使用unchecked。
  3. 对于未使用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类型,当时心里那个奇怪,原因正在于此。

posted on 2011-03-25 12:56  辛勤的代码工  阅读(600)  评论(1编辑  收藏  举报