C#实践教程(第2版)
上QQ阅读APP看书,第一时间看更新

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