Visual C# 2008开发技术实例详解
上QQ阅读APP看本书,新人免费读10天
设备和账号都新为新人

第7章 异常处理

在C#编程语言中,异常的表现形式是Exception类的实例。当程序发生异常时,程序创建一个Exception类实例,放在异常堆栈中,等待应用程序的处理或者程序结束。在C#编程语言中,使用throw语句将异常抛出。当异常抛出时,程序在堆栈区寻找异常处理语句,如果未能找到处理程序,就抛出到当前所在的系统来处理。

7.1 创建自定义异常

在.NET Framework中,只要从Exception类继承,用户就能够定义自己的异常类,并定义该异常类的行为和数据,在程序中生成异常类的实例,通过throw向外部程序抛出异常信息。

技术要点

本示例主要说明了如何在程序中创建自定义的异常,技术要点如下。

● 自定义异常继承于Exception类,该类具有多种形式的构造函数,用户自定义的异常类只需要简单的继承这些构造函数即可。

● 为了使自定义类能够包含更多的信息,Exception类提供了Data属性,允许用户向Data中添加键值对(Key-Value)的数据。

● 在C#中使用throw语句抛出异常,如果异常未处理,将抛出到系统中,直到异常得到处理或者结束程序。

实现步骤

(1)创建控制台应用程序项目,命名为“CustomizeException”。

(2)打开并编辑Program.cs文件,代码如下所示。

    using System;
    using System.Collections.Generic;
    using System.Text;
    namespace CustomizeException
    {
        class Program
        {
            static void Main(string[] args)
            {
                MyException myerr = new MyException();//创建一个异常类的实例
                //获取异常类实例的数据并显示在屏幕上
                Console.WriteLine("自定义异常的名称:{0}",myerr.Data["名称"].ToString());
                Console.WriteLine("自定义异常的描述:{0}" , myerr.Data["描述"].ToString());
                Console.WriteLine("自定义异常的关系:{0}" , myerr.Data["关系"].ToString());
                Console.ReadLine();
                throw myerr;//在程序中直接抛出异常,至此程序不能继续运行
            }
        }
        class MyException : Exception//继承于Exception的自定义异常类
        {
            public MyException()//默认的构造函数
            {
                AddData();//在构造类实例的同时添加异常类数据
            }
            public MyException(string message)//继承的构造函数,message表示异常的提示信息
                : base(message)
            {
                AddData();
            }
            //继承的构造函数,message表示异常的提示信息,inner表示包含的其他异常类实例
            public MyException(string message, Exception inner)
                : base(message, inner)
            {
                AddData();
            }
            private void AddData()//为异常类实例提供数据的方法
            {
                this.Data.Add("名称", "自定义的异常");//添加键值为“名称”的字符串数据
                this.Data.Add("描述", "用户根据业务规则定义的异常");
                this.Data.Add("关系", "继承于Exception类");
            }
        }
    }

(3)按F5键运行程序,运行结果如下所示。当按下回车键结束程序时,程序抛出异常,不能正常结束。

    自定义异常的名称:自定义的异常
    自定义异常的描述:用户根据业务规则定义的异常
    自定义异常的关系:继承于Exception类

源程序解读

(1)本示例创建了一个继承于Exception的异常类MyException类。在该类的构造函数中,继承Exception构造函数的同时,还向Data属性添加键值数据。

(2)在本示例的主程序入口Main方法中,输出Data数据以后,抛出MyException类的实例myerr。此时程序未作任何异常处理,程序不能正常结束。

7.2 获取异常信息

Exception类具有多种属性,根据这些属性能够获取关于异常的信息。了解这些信息,有助于查清引起异常的原因,定位异常发生的位置,为处理异常提供了充分的信息。

技术要点

本示例主要说明了如何在程序中获取异常的信息,技术要点如下。

● 使用Exception类提供的属性,可以获取描述异常的信息。这些信息包含了引起异常的来源信息和关于异常的文字描述。

● 使用Exception类的StackTrace属性能够了解异常的调用堆栈,该属性按照堆栈中自底向上的调用次序,列出当前表示的异常在哪些调用中抛出。

实现步骤

(1)创建控制台应用程序项目,命名为“ExceptionInfo”。

(2)打开并编辑Program.cs文件,代码如下所示。

    using System;
    using System.Collections.Generic;
    using System.Text;
    namespace ExceptionInfo
    {
        class Program
        {
            static void Main(string[] args)
            {
                try
                {
                    //试图将字符串赋值给整型变量时,将会引起异常,从而能输出异常信息
                    int.Parse("Error");
                }
                catch (Exception err)
                {
                    //获取并输出描述异常的文字信息
                    Console.WriteLine("当前异常的描述为:"+err.Message);
                    //获取并输出引发异常的方法名称
                    Console.WriteLine("引起异常的方法是:"+err.TargetSite.Name);
                    //获取并输出异常的来源名称,通常是应用程序的名称
                    Console.WriteLine("异常的来源名称是:"+err.Source);
                    //获取并输出异常产生时,调用堆栈上的程序位置
                    Console.WriteLine("异常在堆栈中的帧:"+err.StackTrace);
                    Console.ReadLine();
                }
            }
        }
    }

(3)按F5键运行程序,运行结果如下所示。

    当前异常的描述为:输入字符串的格式不正确。
    引起异常的方法是:StringToNumber
    异常的来源名称是:mscorlib
    异常在堆栈中的帧:  在 System.Number.StringToNumber(String str, NumberStyles op
    tions, NumberBuffer& number, NumberFormatInfo info, Boolean parseDecimal)
      在 System.Number.ParseInt32(String s, NumberStyles style, NumberFormatInfo in
    fo)
      在 System.Int32.Parse(String s)
      在 ExceptionInfo.Program.Main(String[] args) 位置
C:\Program\200\ExceptionInfo\ExceptionInfo\Program.cs:行号 13

源程序解读

(1)本示例在程序中故意引起一个异常,从该异常中分别获取异常的描述文字、方法、来源名称和堆栈中的帧。

(2)在一般的应用中,只需要查看Message属性即可了解引起异常的原因,如果是自定义异常,Message属性未描述清楚的情况,就有必要使用其他的属性,了解是何处抛出的异常,并且能够通过调用堆栈信息,明确调用的次序和位置,更好地处理异常。

7.3 使用finally关键字

在C#编程语言中,finally关键字出现在try语句中包含在finally之后“{}”符号对中的代码段,不管try语句中的执行结果如何,总是得到执行。因为finally关键字的这个特点,所以经常在finally语句中包含资源的回收功能,例如关闭打开的数据库连接,关闭打开的文件等。

技术要点

本示例主要说明了如何在程序中使用finally关键字,技术要点如下。

● finally关键字必须在try语句中使用,而不能用于其他场合。

● 不管try语句中的执行结果如何,finally关键字之后“{}”符号对所包含的代码总是得到执行。

● 在抛出异常而未处理时,程序不能正常运行。

实现步骤

(1)创建控制台应用程序项目,命名为“FinallyExample”。

(2)打开并编辑Program.cs文件,代码如下所示。

    using System;
    using System.Collections.Generic;
    using System.Text;
    namespace FinallyExample
    {
        class Program
        {
            static void Main(string[] args)
            {
                int i = 6;
                try
                {
                    MyException myerr = new MyException("自定义异常");//创建自定义异常的实例
                    throw myerr;//此句将产生异常
                }
                finally//不管try语句的执行结果,以下“{}”符号对中所包含的代码总是执行
                {
                    Console.WriteLine("变量i的值为:{0}",i);
                }
            }
        }
        class MyException : Exception//自定义的异常类
        {
            public MyException(string message)
                : base(message)//继承Exception类的构造函数
            {
            }
            public MyException(string message, Exception inner)
                : base(message, inner)//继承Exception类的构造函数
            {
            }
        }
    }

(3)按Ctrl+F5组合键运行程序,在弹出的异常信息窗口中,点击“调试”按钮,在Visual Studio实时调试器窗口中,单击“否”按钮,运行结果如下所示。

    未处理的异常:  FinallyExample.MyException: 自定义异常
      在 FinallyExample.Program.Main(String[] args) 位置 C:\Program\200\FinallyExam
    ple\FinallyExample\Program.cs:行号 14
    变量i的值为:6
    请按任意键继续 . . .

源程序解读

(1)本示例自定义了一个继承于Exception类的MyException类,在该类中继承Exception的两个构造函数。

(2)在主程序入口Main方法中,定义一个整型变量i,然后在try语句后的代码段中创建自定义异常MyException类的实例myerr,并抛出myerr异常。代码执行到此处,因为异常未得到处理,所以不能继续正常执行。只有在不调试执行的情况下带有异常信息地执行,才能看到finally中的代码段实际上已经运行。

7.4 使用try-catch语句捕获异常

try-catch语句是C#编程语言中最为常见的异常处理语句。在该语句中,一个包括“{}”中的代码段的try语句之后,允许带有多个catch语句,用于捕获多种不同的异常。

技术要点

本示例主要说明了如何在程序中使用try-catch语句捕获异常,并获取异常中所包含的信息,技术要点如下。

● 在try-catch语句中需要使用的变量,应该在try语句之前声明。

● 在程序中应该允许特定的异常向上堆栈传递,这样做是为了使异常信息能够完整地传递到调用代码,不建议在程序中排除、隐藏异常,不带参数的catch既不产生Exception类实例,也不产生继承于Exception类的实例,将排除程序产生的异常信息。当catch语句中的参数为Exception类实例时,将会隐藏特定的异常信息。这两种方法只能用于比较特殊的场合,一般的情况下不建议使用。

● 当捕捉并再次引发异常时,例如,在传递异常的时候,最好的方式是使用throw的空引发。空引发不会改变堆栈中引发异常的位置,如果不使用throw空引发,就会将堆栈中引发异常的位置指向当前的throw语句。

实现步骤

(1)创建控制台应用程序项目,命名为“Try-Catch”。

(2)打开并编辑Program.cs文件,代码如下所示。

    using System;
    using System.Collections.Generic;
    using System.Text;
    namespace Try_Catch
    {
        class Program
        {
            static void Main(string[] args)
            {
                //在try语句的外部声明变量,否则catch语句所包含的代码段无法识别i变量
                int i = 0;
                try
                {
                    i = int.Parse("Error");//此处代码在将字符串转换为整型时引起异常
                }
                  catch//catch语句之后不带任何参数,表示处理任何异常
                {
                    Console.WriteLine("不作异常捕捉的处理,变量i的值为:{0}",i);
                }
                try
                {
                    ThrowException();
                }
                //在未明错误的情况下使用所有异常的基类来获取异常信息,不建议使用
                catch (Exception currerr)
                {
                    Console.WriteLine("引起异常的方法是:{0}",currerr.TargetSite.Name);
                }
                Console.ReadLine();
            }
            static void ThrowException()//抛出异常的方法
            {
                try
                {
                    //创建一个自定义异常的实例
                    MyException err = new MyException("自定义异常的信息");
                    throw err;//抛出异常
                }
                catch (MyException e)//捕获指定类型的异常
                {
                    Console.WriteLine(e.Message);
                    //ThrowException方法的目的是为了传输异常,在此处抛出使调用程序处理
                    throw;
                }
            }
        }
        class MyException : Exception//自定义的异常类,继承于Exception类
        {
            public MyException(string message)
                : base(message)//继承Exception类的构造函数
            {
            }
        }
    }

(3)按F5键运行程序,运行结果如下所示。

    不作异常捕捉的处理,变量i的值为:0
    自定义异常的信息
    引起异常的方法是:ThrowException

源程序解读

(1)本示例创建了一个自定义的异常类MyException,并在Program类中定义了一个静态的ThrowException方法,在该方法中创建MyException类的实例err,通过捕捉异常将err表示的异常信息传递给主程序入口Main方法的调用语句。本示例的程序流程图如图7.1所示。

图7.1 try-catch示例程序流程图

(2)在try语句开始之前,应该首先声明在整个try-catch结构中需要使用的变量。在本示例程序中,i变量必须在try语句之前声明,否则catch语句所包含的代码段将无法识别i变量。

(3)在未明错误的情况下,可以使用Exception作为catch语句的参数类型,在这种情况下,程序无法判断是异常的数据类型,即捕捉的是非特定的异常,此时可能会忽略错误代码的处理,在实际编程中不建议使用。

(4)在ThrowException方法中,因为该方法的目的是抛出异常让主程序入口Main方法来处理,所以在该方法中的异常,不应该进行处理,而是通过throw语句,将该异常向上抛出到程序堆栈中。此时调用语句产生异常,并在后续的catch语句中处理。

(5)在ThrowException中,catch块的throw语句是空引发,此时抛出的异常,在堆栈中指向的位置是try语句中的throw语句,如果使用了非空引发,例如,在catch中抛出异常时使用了如下代码:

    throw e;

以上代码抛出的异常,在堆栈中指向的位置,是位于catch块中的语句。这种非空引发将改变异常抛出的位置,不利于程序的调试和异常处理,不建议使用。

7.5 使用多catch语句捕获异常

在C#编程语言中允许try语句存在多个catch语句异常捕捉,在多个catch语句并存的情况下,捕捉异常的次序和处理十分关键。在C#程序中,将首先捕获特定程度较高的异常,如果将特定程度较低的异常捕捉放在前面,就会在编译时发生错误。

技术要点

本示例主要说明了在程序中使用多catch语句捕获异常的方法,技术要点如下。

● 在使用多catch语句捕捉异常的时候,应把特定程度最高的捕捉放在最前面。

● catch语句可以不使用任何参数,不存在Exception类及其派生类的实例,此时如果要抛出异常,可以使用不带参数的空引发throw。

● 使用Exception类型作为参数的catch语句和不使用任何参数的catch语句不能在同一个try-catch结构中并存。

● 在try-catch-finally结构的代码中,建议的做法是:将变量的定义和初始化放在try语句之前,将可能出现异常的代码放在try代码段中,将各种可能产生的异常分别捕捉,并放置在各自的catch代码段中处理。在finally中的代码段主要作用是清理占用的资源,例如关闭数据库连接、释放文件或内存的占用等。

实现步骤

(1)创建控制台应用程序项目,命名为“MultiCatch”。

(2)打开并编辑Program.cs文件,代码如下所示。

    using System;
    using System.Collections.Generic;
    using System.Text;
    namespace MultiCatch
    {
        class Program
        {
            static void Main(string[] args)
            {
                MyException1 myerr1 = new MyException1();//创建MyException1异常类的实例
                MyException2 myerr2 = new MyException2();//创建MyException2异常类的实例
                MultiCatch(myerr1);
                MultiCatch(myerr2);
                MultiCatch(new Exception());//使用未知异常
                Console.ReadLine();
            }
            static void MultiCatch(Exception e)
            {
                try
                {
                    throw e;//抛出传入的异常以使用catch判断
                }
                catch (MyException1 myerr)//对于MyException1异常的处理
                {
                    Console.Write("当前异常:"+myerr.Message);
                }
                catch (MyException2 myerr)//对于MyException2异常的处理
                {
                    Console.Write("当前异常:"+myerr.Message);
                }
                catch//对未知异常的处理
                {
                    Console.Write("未知异常");
                }
                finally
                {
                    Console.WriteLine("");
                }
            }
        }
        class MyException1 : Exception//自定义的异常类
        {
            public MyException1()
                : base("自定义异常类1")
            {
            }
            public MyException1(string message)
                : base(message)
            {
            }
        }
        class MyException2 : Exception//自定义的异常类
        {
            public MyException2()
                : base("自定义异常类2")
            {
            }
        }
    }

(3)按F5键运行程序,运行结果如下所示。

    当前异常:自定义异常类1
    当前异常:自定义异常类2
    未知异常

源程序解读

(1)本示例定义了两个自定义的异常类,即MyException1类和MyException2类,这两个类的实例作为参数传递给MultiCatch方法,在MultiCatch方法中抛出异常,然后分别捕捉可能产生的异常,在处理异常时输出异常的描述信息。程序还创建一个Exception类的实例,作为不带参数的catch的输出。本示例程序的流程如图7.2所示。

图7.2 使用多catch实例程序流程图

(2)在MultiCatch方法中,捕获异常的顺序是MyException1、MyException2、不带参数的异常捕获。在捕获的过程中,首先捕获的是特定程度较高的异常。在参数e是MyException1异常或者MyException2异常时,不会执行无参数catch中的代码段。

(3)在本示例中的自定义异常类中,使用了继承带参数的构造函数来实现不带参数的构造函数。因为在构造函数中已经指定了Message属性,所以在创建MyException1类实例和MyException2类实例的时候,无须再指定异常的Message属性。