TOC

This article is currently in the process of being translated into Chinese (~90% done).

Advanced topics:

Exception handling

程序都有出错的可能。使用C#编程时,优秀的编译器有助于阻止某些最常见的错误。当然,要预知可能出现的所有错误是不可能的,此时.NET framework会抛出一个异常,告诉用户出错了。前面关于数组的某章中讲述过往数组中加入过多的项目时会遇到异常。先看这个例子:

using System;
using System.Collections;

namespace ConsoleApplication1
{
    class Program
    {
static void Main(string[] args)
{
    int[] numbers = new int[2];

    numbers[0] = 23;
    numbers[1] = 32;
    numbers[2] = 42;

    foreach(int i in numbers)
Console.WriteLine(i);
    Console.ReadLine();
}
    }
}

好,运行一下这个例子就明白前面讲的是什么意思了。明白此处那里有问题吗?示例中定义了一个能容纳2个数字的整数数组,但却试图往里面放3个数字。这当然会导致出错,运行一下程序就会看到。如果在Visual Studio中运行,IDE会给出如何处理此异常的建议选项,但是如果只是简单地双击此程序的EXE文件来执行,只会得到一个严重错误。如果能够预见到错误发生的可能性,就应该进行处理。这就是是异常处理的用途。这是上述代码的简单修正版本:

int[] numbers = new int[2];
try
{
    numbers[0] = 23;
    numbers[1] = 32;
    numbers[2] = 42;

    foreach(int i in numbers)
Console.WriteLine(i);
}
catch
{
    Console.WriteLine("Something went wrong!");
}
Console.ReadLine();

先介绍进行错误处理的新语法:try..catch语句。运行一下上述程序,看与前面有什么区别 - 不用Visual Studio/Windows来通知严重错误的发生,程序自行进行了处理。不过如果能明确错在那里不是更好吗?没问题:

catch(Exception ex)
{
    Console.WriteLine("An error occured: " + ex.Message);
}

上述代码在catch表达式中增加了点东西。明确了要抓取什么样的异常(Exception),此例中为所有异常(的基类)。由此,可以获得导致此异常的信息,通过输出其Message属性,可以得到对此问题的简单描述。

此示例中的Exception就是最基本的异常类型。异常处理的规则是应该使用最不通用(即最具体)的异常类型,本例中代码所产生的具体异常类型是明确的。为什么呢?因为如果不处理的话Visual Studio就会给出该信息。若有疑问,相关文档通常都会描述方法可能抛出那些异常。另一种方法是直接使用Exception类来获得具体异常类型 - 把输出行改为:

Console.WriteLine("An error occured: " + ex.GetType().ToString());

输出结果就是预期的IndexOutOfRangeExpception。因此程序中处理的就应该是此异常,不过可以处理的异常类型数量是不限的。有时会需要根据抛出的不同异常而做不同的处理。只需把catch表达式作如下修改:

catch(IndexOutOfRangeException ex)
{
    Console.WriteLine("An index was out of range!");
}
catch(Exception ex)
{
    Console.WriteLine("Some sort of error occured: " + ex.Message);
}

上述代码先捕获IndexOutOfRangeException。如果使用相反的顺序,则捕获Exception类的catch语句会捕获此异常,因为所有的异常都继承了此类。因此,换言之,应该先使用最具体的异常类型。

与异常处理相关的另一个需要了解的概念是finally语句。finally语句可加在一组catch语句后面,或直接使用,视需求而定。finally语句内包含的代码总是会被执行 - 不管是否出现异常。如果有文件引用需要关闭,或有不再需要的对象要释放,此处是执行这些操作的理想位置。由于所用示例目前为止都很简单,还没有真正需要清理的东西,都可由垃圾处理机制自动处理掉。不过终究会有需要finally语句的时候,这是本示例的扩展版本:

int[] numbers = new int[2];
try
{
    numbers[0] = 23;
    numbers[1] = 32;
    numbers[2] = 42;

    foreach(int i in numbers)
Console.WriteLine(i);
}
catch(IndexOutOfRangeException ex)
{
    Console.WriteLine("An index was out of range!");
}
catch(Exception ex)
{
    Console.WriteLine("Some sort of error occured: " + ex.Message);
}
finally
{
    Console.WriteLine("It's the end of our try block. Time to clean up!");
}
Console.ReadLine();

运行一下此代码就会看到,第一个异常捕获语句和finally语句都被执行了。如果删掉往数组里加入数字42的那一行代码,会看到只有finally语句被执行。

关于异常处理的另一个重要内容是异常处理如何影响出现异常的那个方法。程序中未处理的异常并非都是致命的,但如果异常未被处理,就不要期望方法中剩下的代码会被执行。换言之,如果处理了异常,就只有try语句后面的代码会被执行。上例中,循环输出数组值的代码永远不会被执行,因为一旦异常发生,try语句会直接跳到catch/finally语句。不过,最后一行用于等待控制台输入以阻止程序立即退出的代码,仍然会被执行。构建try语句时应该时刻记住这一点。


This article has been fully translated into the following languages: Is your preferred language not on the list? Click here to help us translate this article into your language!