06 管理错误和异常

1.处理错误

生活并非总是一帆风顺。轮胎可能扎破,电池可能耗尽,螺丝起子并非总在老地方,应用程序的用户可能进行了出乎意料的操作。在计算机世界里,磁盘可能出故障,编写不当的程序可能影响机器上运行的其他应用程序(比如由于程序bug造成耗尽所有内存),无线网络可能在最不恰当的时刻断开连接,甚至一些自然现象(比如附近的一次闪电)也会造成电源或者网络故障。错误可能在程序运行的任何阶段发生,其中许多都不是程序本身的问题。

人们多年来为此研发了大量机制。早期系统(如UNIX)采用的典型方案要求在每次方法出错时都由操作系统设置一个特殊全局变量。每次调用方法后都检查全局变量,判断方法是否成功。和大多数面向对象编程语言一样,C#没有使用这种痛苦的、折磨人的方式处理错误。相反,它使用异常,为了写健壮的C#应用程序,必须很好地掌握异常。

 

2.尝试执行代码和捕捉异常

C#中利用异常和异常处理程序,可以很容易地区分实现程序主逻辑地代码与处理错误地代码。为了写支持异常处理地应用程序,要做下面两件事情。

(1)代码放到try块中(try是C#关键字)。代码运行时,会尝试执行try块内的所有语句。如果没有任何语句产生异常,就会正常全部运行完成。但一旦出现异常,就跳出try块,进入一个catch处理程序中执行。

(2)紧接着try块写一个或多个catch处理程序(catch也是C#关键字)来处理可能发生的错误。每个catch处理程序都捕捉并处理特定类型的异常,可在try块后面写多个catch处理程序。try块中的任何语句造成错误,“运行时”都会生成并抛出异常。然后,“运行时”检查try块之后的catch处理程序,将控制权移交给匹配的处理程序。

 

举例:下面将字符串转换成整型,如果字符串包含了无效字符,int.Prase方法抛出FormatException异常,并将控制权移交给对应的catch处理程序。catch处理程序结束后,程序从整个try/catch块之后的第一个语句继续。注意,如果没有和异常对应的处理程序,就说明异常未处理(稍后会讨论这种情况)

void parseInt(string str)

{

    try{

        int number = int.Parse(str);

    }

    catch(FormatException fEx)

    {

        //处理异常

        ……

    }

}

catch处理程序采用与方法参数相似的语法指定要捕捉的异常。在前例中,一旦抛出FormatException异常,fEx变量就会被填充一个对象,其中包含了异常的细节。FormatException类型提供大量属性供检查造成异常的确切原因。不少属性是所有异常通用的。例如,Message属性包含错误的文本描述。

 

2.1未处理的异常

如果try块抛出异常,但没有对应的catch处理程序,那么会发生什么?在上面的例子中,str可能是一个整数,但如果该整数超出了C#允许的整数范围(例如2147483648)。在这种情况下,int,Parse语句会抛出OverflowWxception异常,而catch处理程序目前只能捕捉FormatException异常。如果try块是某个方法的一部分,那个方法将立即退出,并返回它的调用方法。如果它的调用方法有try块,“运行时”会尝试定位try块之后的一个匹配catch处理程序并执行。如果调用方法没有try块,或者没有找到匹配的catch处理程序,调用方法退出,返回它的更上一层的调用方法……以此类推。如果最后找到了匹配的catch处理程序,就运行它,然后从捕获(到异常的)方法的catch处理程序之后的第一个语句继续执行。

由内向外遍历了所有调用方法之后,如果还是找不到匹配的catch处理程序,整个程序终止,报告发生了未处理的异常。

 

2.2使用多个catch处理异常

void parseInt(string str)

{

    try{

        int number = int.Parse(str);

    }

    catch(FormatException fEx)

    {

        //处理异常

        ……

    }

    catch(OverflowException oEx)

    {

        //处理异常

        ……

    }

}

2.3捕捉多个异常

异常用继承层次结构进行组织。这个继承层次结构由多个“家族”构成(后面将会详细讨论继承)。FormatException和OverflowException异常都属于SystemException家族。该家族还包含其他许多异常。SystemException本身又是Exception家族的成员,而Exception是所有异常的“老祖宗”。捕捉Exception相当于捕捉所有可能发生的异常。

 

以下代码演示如何捕捉所有可能的异常:

void parseInt(string str)

{

    try{

        int number = int.Parse(str);

    }

    catch(Exception fEx)

    {

        //处理异常

        ……

    }

}

2.4异常过滤器

允许指定catch处理程序的额外使用条件。这些条件采用的形式是when关键字加布尔表达式,如下例所示:

catch(Exception ex)when(ex.GetType() != typeof(System.OutOfMemoryException))

{

    //处理之前未捕捉的除OutOfMemoryException之外的所有异常

}

3.使用checked和unchecked整数运算

问题:在当前值已经是2147483647的一个int上加1会发生什么?

C#编译器默认允许悄悄溢出。换言之,将得到一个错误答案(结果是:-2147483648)

 

3.1编写checked语句

checked语句是以checked关键字开头的代码块。checked语句中的任何整数运算溢出都抛出OverflowException异常,如下例所示:

使用checked(checked语句中的任何整数运算溢出都会抛出OverflowException异常)

int number = int.MaxValue;

checked

{

      int willThrow = number ++;

      Console.WriteLine("永远都执行不到这里");

}

使用unchecked语句(不抛异常)

int number = int.MaxValue;

unchecked

{

      int willThrow = number ++;

      Console.WriteLine("会执行到这里");

}

3.2编写checked表达式

还可以使用checked和unchecked关键字控制单独整数表达式的溢出检查

int wontThrow = unchecked(int.MaxValue + 1);   //不抛出异常

int willThrow = checked(int.MaxValue + 1);   //抛出异常

4.抛出异常&声明异常

我们可以不在当前方法处理异常,而是将产生的异常往外抛,让调用当前方法的处理该异常,以此类推

比如:

void parseInt(string str)throws FormatException,OverflowException

{

    try{

        int number = int.Parse(str);

    }

    catch(FormatException fEx)

    {

        throw new FormatException("格式异常");

    }

    catch(OverflowException oEx)

    {

        throw new OverflowException("越界异常");

    }


}

throw是抛出异常,throws是方法声明可能产生的异常

 

5.使用finally块

之前说过,当catch处理程序运行完毕,会从整个try/catch块之后的语句继续,而不是从抛出异常的语句之后继续。

如果程序运行到一半,出了异常,一些之前语句获取的资源得不到释放,就有可能出现问题,所以我们可以写一个finally块,放到其中的语句总是执行(无论是否抛出异常)。finally块要么紧接在try块之后,要么紧接最后一个catch块之后。

所以可用以下方案确保reader.Dispose总是得到调用:

TextReader  reader = …;

…

try

{

    string line = reader.ReadLine();

    while(line != null)

    {

        …

        line = reader.ReadLine();

    }

}

finally

{

    if(reader != null)

    {

        reader.Dispose();

    }

}

即使读取文件时发生异常,finally块也保证reader.Dispose语句得到执行。之后将会介绍解决该问题的另一个方案(使用using语句)。

 

参考书籍:《Visual C#从入门到精通》

发布了46 篇原创文章 · 获赞 53 · 访问量 3716

猜你喜欢

转载自blog.csdn.net/qq_38992372/article/details/104982581
06