
3.6 异常处理语句
程序中不可避免存在无法预知的反常情况,这种反常称为异常。C#为处理在程序执行期间可能出现的异常提供了内置支持,由正常控制流之外的代码处理。
本节介绍C#内置的异常处理,包括使用throw抛出异常,使用try尝试执行代码,并在失败时使用catch处理异常。
3.6.1 throw
throw语句用于发出在程序执行期间出现异常的信号。通常与try catch语句或try finally语句结合使用。
throw语句将引发异常,当异常引发时,程序查找处理此异常的catch语句。也可以用throw语句重新引发已捕获的异常。throw只是用在程序中的一条语句,在实际应用中如练习3-19所示。
【练习3-19】
定义变量name表示用户名,name是不能为空的,在使用前必须赋值,为了确保name不为空,有以下语句。
string name = null; if (name == null) { throw (new System.Exception()); } Console.Write("The name is null");
执行语句将引发异常。若将if语句中{}内的语句块注释,或直接将整个if语句注释,程序可以正常进行,输出The name is null,但有了throw语句,程序将中断并提示异常。
但这样的异常是放任的,没有处理的,需要使用catch语句处理异常,即下一节要讲解的try catch语句。
3.6.2 try catch
throw语句只是抛出异常,异常的处理需要try catch语句。try catch语句由一个try语句块后跟一个或多个catch子句构成,执行时首先尝试运行try语句块,若引发了异常则执行catch语句块并完成,若没有异常则正常完成。多个catch子句指定不同的异常处理程序。流程图如图3-23所示。

图3-23 try catch语句流程
图3-23并不是标准流程图,是形象描述的流程图。在try catch语句开始后首先执行try语句块,若无异常则结束,有异常则执行catch语句块并结束。try catch语法格式如下:
try {语句块} catch () {语句块} catch () {语句块}
格式中try与catch后的{}不需要加分号,catch子句使用时可以不带任何参数,这种情况下它捕获任何类型的异常,并被称为一般catch子句。若try后只有一个catch语句,则catch后的()可以省略,如练习3-20所示。
【练习3-20】
将练习3-19改进,添加异常的获取和处理,使用try catch语句如下。
string name = null; try { if (name == null) { throw new Exception(); } } catch { Console.WriteLine("name is null"); }
运行结果为:
name is null
catch子句还可以接受从System.Exception派生的对象参数,这种情况下它处理特定的异常,如练习3-21所示。
【练习3-21】
定义变量name表示用户名,name是不能为空的,在使用前必须赋值,若没有赋值则指出异常,使用语句如下。
string name = null; try { if (name == null) { throw new Exception(); } } catch (Exception e) { Console.WriteLine(e); }
运行结果如图3-24所示:

图3-24 指出异常
在同一个try catch语句中可以使用一个以上的特定catch子句。这种情况下catch子句的顺序很重要,因为会按顺序检查catch子句。将先捕获特定程度较高的异常,而不是特定程度较小的异常,如练习3-22所示。
【练习3-22】
将练习3-21改变,添加一个特定程度高的catch子句,再使用捕获所有类型的catch子句,使用语句如下。
string name = null; try { if (name == null) { throw new ArgumentNullException(); } } catch (ArgumentNullException e) { Console.WriteLine("first {0}",e); } catch (Exception e) { Console.WriteLine("second {0}", e); }
执行结果如图3-25所示。

图3-25 多个catch结果
除了throw语句和try语句块中抛出的异常,由catch语句可以再次引发异常,格式如下:
//参数可以省略 catch(参数) { throw(参数); }
在try语句块内部时应该只初始化其中声明的变量;否则,完成该块的执行前可能发生异常。如以下的代码示例:
int i; try { i = 0; } catch { } Console.Write(i);
变量i在try语句块外声明,而在语句块内初始化。在使用Write(i)语句输出时产生编译器错误:使用了未赋值的变量。
3.6.3 try catch finally
finally语句块用于清除try语句块中分配的所有资源,以及在try语句块结束时必须执行的代码,无论是否有异常发生。
finally语句块放在catch语句块后,控制总是传递给finally语句块,与try语句块的退出方式无关。流程图如图3-26所示。

图3-26 try catch finally流程图
图3-26并不是标准流程图,是形象化的流程图。程序从进入try语句后若没有引发异常,则进行finally语句块并结束;若引发了异常,则先进行catch语句块,接着执行finally语句块并结束。语法如下:
try { } catch { } finally { }
catch和finally一起使用的常见方式是:在try语句块中获取并使用资源;在catch语句中处理异常;在finally语句块中释放资源。catch语句块可以省略,直接使用try和finally语句块。
【练习3-23】
定义除数变量dividenum和被除数变量num,两个变量都不能为0,默认num为6,dividenum为2,求两个数的商,有以下语句。
int num=0; int dividenum = 0; try { if (num == 0 || dividenum == 0) { throw new Exception(); } } catch { num = 6; dividenum = 2; } finally { num = num / dividenum; } Console.Write(num);
执行结果为:
3
从练习3-23看得出,语句执行从try开始,在引发异常后由catch捕获并处理,之后交由finally语句。代码中的catch可以省略,如以下代码:
int num = 0; int dividenum = 0; try { if (num == 0 || dividenum == 0) { num = 6; dividenum = 2; } } finally { num = num / dividenum; } Console.Write(num);
执行结果与练习3-23一致。
与直接使用try catch不同,在finally语句块中可以初始化try语句块以外的变量,如以下代码:
int i; try { } catch { } finally {i=123;} Console.Write(i);
执行结果为:
123