• 第十四章 异常处理


    1 错误与异常

      程序中的错误有很多种,最典型的一种是语法错误,另一种是代码中的逻辑错误,代码本身没有语法错误,可能在运行过程中出现。

    2 C#中的异常处理结构

      C#语言通过try语句提供的控制结构来检测代码中的异常并作出相应的处理。try语句有4种使用方式。

    2.1 try-catch语句

      正常情况下try代码段中语句依次执行,而catch代码段不会被执行;一是出现异常,程序控制权就从try语句转到catch语句,并在catch代码段中进行异常处理。

      catch语句可以捕获指定的异常。可以在catch语句上加一对括号,在括号中指定希望捕获的异常类型。

        class ExtractExceptionSample
        {
            static void Main()
            {
                try
                {
                    Console.Write("请输入整数x:");
                    int x = int.Parse(Console.ReadLine());
                    Console.Write("请输入整数y:");
                    int y = int.Parse(Console.ReadLine());
                    int result = 24 / (6 - x) / (x - y) / (y - 2);
                    Console.WriteLine("24 / (6 - x) / (x - y) / (y -2) = {0}", result);
                }
                catch (FormatException)
                {
                    Console.WriteLine("输入的格式不正确");
                }
                catch (DivideByZeroException)
                {
                    Console.WriteLine("分母不能为零");
                }
                catch (Exception)
                {
                    Console.WriteLine("其它错误");
                }
             }
        }

      同时使用多个catch语句时,如果其中两个cathc语句所捕获的异常类存在继承关系,那么要保证捕获派生类的catch语句在前,而捕获基类的catch语句在后。上例中,Exception是所有异常类的基类。

    2.2 try-catch-finally语句

      其中同样可以使用多个catch语句,但finally语句要在所有catch语句的后面,而且只能出现一次。不论程序在执行过程中是否发生异常,finally语句中的代码段总是被执行。

    2.3 try-finally语句

      由于没有catch语句,实际不并不能处理异常。如果发生异常,该异常将在执行完finally代码段之后被抛出。

    2.4 throw语句

      前面介绍的3种语句是用于防止异常出现时程序中止,而throw语句相反,它主动引发一个异常,如果该异常不被捕获将导致程序中止。throw语句的使用格式是在关键字throw之后跟一个异常类型的对象,当程序执行到throw语句时就引发相应的异常,之后的语句不会被执行。throw语句的主要用途是对发生的异常进行描述。

        class ThrowSample
        {
            static void Main()
            {
                ConsoleRW c1 = new ConsoleRW();
                c1.Validate("2004", 5);
            }
        }
        public class ConsoleRW
        {
            public string Read(string sPrompt)
            {
                Console.WriteLine("请输入{0}:", sPrompt);
                return Console.ReadLine();
            }
    
            public void Write(string sPrompt, string sContent)
            {
                Console.WriteLine("{0}: {1} ", sPrompt, sContent);
            }
    
            public void Validate(string sPwd, int iCount)
            {
                int i = 0;
                while (Read("密码") != sPwd)
                {
                    Console.WriteLine("密码错误!");
                    i++;
                    if (i > iCount)
                        throw (new Exception("密码错误次数超过限制,您没有系统的访问权限。"));
                }
                Console.WriteLine("通过验证");
            }
        }

      如果发生多次输入错误,程序的输出将如下:

    请输入密码:
    20045
    密码错误!
    请输入密码:
    251
    密码错误!
    请输入密码:
    455
    密码错误!
    请输入密码:
    126
    密码错误!
    请输入密码:
    12
    密码错误!
    请输入密码:
    154
    密码错误!
    
    未经处理的异常:  System.Exception: 密码错误次数超过限制,您没有系统的访问权限。
       在 P16_7.ConsoleRW.Validate(String sPwd, Int32 iCount) 位置 F:编程学习C#2.0
    csharp2P16_7ConsoleRW.cs:行号 29
       在 P16_7.ThrowSample.Main() 位置 F:编程学习C#2.0csharp2P16_7ThrowSample.
    cs:行号 13
    请按任意键继续. . .

      如果在try-catch语句或try-catch-finally语句的try代码段中使用了throw语句,而throw语句产生的异常又被之后的catch语句捕获,那么就在该catch代码段中进行相应的异常处理。而在其它情况下,throw语句产生的异常都会导致代码中止,这也包括在catch代码段中使用的throw语句。

    3 异常的层次结构

    3.1 异常传播

      当异常在try代码段中被引发时,程序控制权将在异常处理结构中转移,直到找到一个能够处理该异常的catch语句,否则中止程序,这个过程叫做异常传播。异常传播的步骤为:

      (1)如果当前的异常处理结构中上包含能够处理该异常的catch语句,那么程序控制权就转移给第一个这样的catch语句,异常传播结束。

      (2)如果没有找到能够处理该异常的catch语句,则程序通过当前的异常处理结构(如果存在finally代码段则执行它)。

      (3)如果程序到达更外层的一个异常处理结构,则转到第(1)步;

      (4)如果异常在当前的成员方法中没有得到处理,则当前方法的执行代码被中止;若当前方法是程序所在的进程或线程的主方法,则整个程序结束运行;

      (5)程序控制权转移给调用当前方法的代码,重复第(1)步。

        class ExceptionPropagateSample
        {
            static void Main()
            {
                try
                {
                    OutterMethod(0);
                    OutterMethod(1);
                    OutterMethod(2);
                }
                catch (Exception)
                {
                    Console.WriteLine("发生一般异常");
                }
            }
    
            public static void OutterMethod(int x)
            {
                if (x == 2)
                    throw new Exception();
                try
                {
                    MiddleMethod(x);
                }
                catch (ArithmeticException)
                {
                    Console.WriteLine("发生算术异常");
                }
            }
    
            public static void MiddleMethod(int x)
            {
                if (x == 1)
                    throw new ArithmeticException();
                try
                {
                    InnerMethod(x);
                }
                catch (DivideByZeroException)
                {
                    Console.WriteLine("发生除法异常");
                }
            }
    
            public static void InnerMethod(int x)
            {
                if (x == 0)
                    throw new DivideByZeroException();
            }
        }

      输出结果:

    发生除法异常
    发生算术异常
    发生一般异常
    请按任意键继续. . .

    3.2 Exception类

      Exception类是.NET类库中所有其它异常类的基类,即是对所有异常的一般抽象。其构造函数可以不带参数,也可以指定一个字符类型的参数作为描述异常的信息。还可以指定另一个异常对象作为参数构造Exception对象,这表示作为参数的异常对象引发了正在构造的异常对象。

      (参见Exception类的公用属性)。

      Exception类还提供了一个公有方法GetBaseException,用于返回异常的根源。因为一个异常可能引发另一个异常,使用该方法可以到异常链中第一个异常对象。如果异常链中只有当前异常对象,那么调用该方法得到的总是对象本身。

      在catch语句中,除了可以指明要捕获的异常,还可以声明捕获到的异常对象。通过这个对象的属性或方法,就可以得到关于当前异常对象的详细描述。看下面的程序:

        class ExceptionDetailSample
        {
            static void Main()
            {
                try
                {
                    OutterMethod(0);
                }
                catch (Exception exp)
                {
                    Console.WriteLine("程序运行过程中发生异常。");
                    Console.WriteLine("
    错误信息:
    " + exp.Message);
                    Console.WriteLine("
    引发对象:
    " + exp.Source);
                    Console.WriteLine("
    帮助文件:
    " + exp.HelpLink);
                    Console.WriteLine("
    内部异常:
    " + exp.InnerException);
                    Console.WriteLine("
    堆栈表示:
    " + exp.StackTrace);
                    Console.WriteLine("
    引发方法:
    " + exp.TargetSite.Name);
                    Console.WriteLine("
    基础异常:
    " + exp.GetBaseException());
                }
            }
    
            public static void OutterMethod(int x)
            {
                if (x == 1)
                    throw new Exception("发生一般异常:x不能为1");
                InnerMethod(x);
            }
    
            public static void InnerMethod(int x)
            {
                if (x == 0)
                    throw new ArithmeticException("发生算术异常:x不能为0");
            }
        }
    程序运行过程中发生异常。
    
    错误信息:
    发生算术异常:x不能为0
    
    引发对象:
    P16_9
    
    帮助文件:
    
    
    内部异常:
    
    
    堆栈表示:
       在 P16_9.ExceptionDetailSample.InnerMethod(Int32 x) 位置 F:编程学习C#2.0cs
    harp2P16_9ExceptionDetailSample.cs:行号 39
       在 P16_9.ExceptionDetailSample.OutterMethod(Int32 x) 位置 F:编程学习C#2.0c
    sharp2P16_9ExceptionDetailSample.cs:行号 33
       在 P16_9.ExceptionDetailSample.Main() 位置 F:编程学习C#2.0csharp2P16_9Ex
    ceptionDetailSample.cs:行号 14
    
    引发方法:
    InnerMethod
    
    基础异常:
    System.ArithmeticException: 发生算术异常:x不能为0
       在 P16_9.ExceptionDetailSample.InnerMethod(Int32 x) 位置 F:编程学习C#2.0cs
    harp2P16_9ExceptionDetailSample.cs:行号 39
       在 P16_9.ExceptionDetailSample.OutterMethod(Int32 x) 位置 F:编程学习C#2.0c
    sharp2P16_9ExceptionDetailSample.cs:行号 33
       在 P16_9.ExceptionDetailSample.Main() 位置 F:编程学习C#2.0csharp2P16_9Ex
    ceptionDetailSample.cs:行号 14
    请按任意键继续. . .

    3.3 其它一些常见的异常类

    3.3.1 SystemException类和ApplicationException类

      它们是Exception的直接派生类中最常用的两个。SystemException类是System命名空间中所有其它异常类的基类,ApplicationException类则表示应用程序发生非致命性错误所引发的异常。和Exception类相比,这两个类并没有提供新的属性或方法。由公共语言运行时引发的异常,通常使用SystemException;而由应用程序自身引发的异常,则通常使用ApplicationException。这只是.NET框架中处理异常的一个建议性的规则,并没有强制性。

    3.3.2 与参数有关的异常类

      ArgumentException类和FormatException类都表示由于传递给方法成员的参数发生错误引发的异常,它们都是SystemException的直接派生类。FormatException类在前面已经出现过,表示参数格式错误;而ArgumentException类则表示参数无效。除了继承的属性之外,ArgumentException类还提供了一个string类型的属性ParamName,表示引发异常的参数名称。

      ArgumentException还有两个常用的派生类,其中ArgumentNullException表示将空值null作为参数传递给了方法,而ArgumentOutOfRangeException则表示传递给方法的参数值超出了可接受的范围。

      下面的程序由用户输入年、月、日来构造一个DateTime对象。如果输入的年月日不是整数,则捕获FormatException异常;如果输入超出可接受的范围,则捕获ArgumentOutOfRangeException异常:

        class ArgumentExceptionSample
        {
            static void Main()
            {
                try
                {
                    Console.Write("请输入年份:");
                    int year = int.Parse(Console.ReadLine());
                    Console.Write("请输入月份:");
                    int month = int.Parse(Console.ReadLine());
                    Console.Write("请输入日期:");
                    int day = int.Parse(Console.ReadLine());
                    DateTime dt1 = new DateTime(year, month, day);
                    Console.WriteLine("输入时间为:" + dt1.ToLongDateString());
                }
                catch (FormatException)
                {
                    Console.WriteLine("输入错误:年月日应当是整数");
                }
                catch (ArgumentOutOfRangeException)
                {
                    Console.WriteLine("输入错误:不是有效的时间格式");
                }
            }
        }

    3.3.3 与成员访问有关的异常类

      MemberAccessException类表示访问类的成员失败所引发的异常。失败的原因可能是没有足够的访问权限,也可能是要访问的成员根本不存在。例如,在调用代表类Delegate的DynamicInvoke方法时,如果无法访问代表所封装的方法,就会引发MemberAccessException异常。

      MemberAccessException类的直接派生类有:

      • FieldAccessException,表示访问字段成员失败所引发的异常;

      • MethodAccessException,表示访问方法成员失败所引发的异常;

      • MissingMemberException,表示访问的成员不存在时所引发的异常。

    3.3.4 与数组有关的异常

      当访问的下标超过了数组的长度时,将引发IndexOutOfRangeException异常;如果试图在数组中存储不正确的元素,将引发ArrayTypeMismatchException异常;而如果使用了维数错误的数组,将引发RankException异常。这3个类也都是SystemException的直接派生类。

    3.3.5 与内存和磁盘操作有关的异常

      涉及到内存和磁盘操作时,引发异常的原因就复杂了:既可以是软件本身的错误,也可能是系统硬件的问题。

      如果程序的运行得不到足够的内存,将引发OutOfMemoryException异常。如果程序引用了内存中的空对象,将引发NullReferenceException异常,该异常类需要和前面介绍的ArgumentNullException类相区别。例如一个对象为空值null,试图调用该对象的字段或方法成员就会引发NullReferenceException异常;而如果将该对象作为一个参数传递给某个方法,而该方法不支持空对象,那么引发的是ArgumentNullException异常。

      IOException类表示在进行文件输入输出操作时所引发的异常,它的5个直接派生类分别是:

      • DirectoryNotFoundException,表示没有找到指定的目录而引发的异常;

      • FileNotFoundException,表示没有找到指定的文件而引发的异常;

      • EndOfStreamException,表示已经到达流的末尾而引发的异常;

      • FileLoadException,表示不能加载文件而引发的异常;

      • PathToolLongException,表示文件或目录的路径名超出规定的长度而引发的异常。

    3.3.6 与算术运算有关的异常

      ArithmeticException类表示与算术运算有达的所有异常类的基类。其派生类有:

      • DivideByZeroException,表示整数或十进制运算中试图除以零时所引发的异常;

      • NotFiniteNumberException,表示浮点数运算中出现正负无穷大或非数值时所引发的异常;

      • OverflowException,表示运算溢出所引发的异常。

  • 相关阅读:
    pydbg系列[1]
    内核参与方式
    Debugging with GDB阅读[6]
    宏技巧解读
    右键-发送到-邮件接收者没有了的解决方法
    获得文件版本信息
    解决动态生成的SQL中特殊字符的问题 QuotedStr function
    CreateFileMapping的MSDN翻译和使用心得
    关闭Windows自动播放功能
    清凉明目茶
  • 原文地址:https://www.cnblogs.com/boywg/p/4149970.html
Copyright © 2020-2023  润新知