面向对象——错误与异常

对于.NET类一般的异常类System.Exception派生自System.Object,通常不在代码中抛出System.Exception泛型对象,因为他们无法确定错误情况的本质。


在该层次结构中有两个重要的类,他们派生自System.Exception。


System.SystemException-该类用于通常由.NET运行库抛出的异常,或者有着非常一般的本质并且可以由几乎所有的应用程序抛出的异常。例如,如果.Net运行库检测到调用方法时参数不正确,就可以在自己的代码中抛出ArgumentException异常或其子异常。System.SystemException异常的子类,包括表示致命错误和非致命错误的异常。

System.Application-这个类非常重要,因为它是第三发定义的异常基类。如果自己定义的任何异常覆盖了应用程序独有的错误情况,就应使它们直接或间接派生自System.Application异常。其他可能用到的异常:

*Exception 如果分配给栈的内存区域已满,就会抛出这个异常。如果一个方法连续地递归调用自己,就可能发生栈溢出。这一般是一个致命错误,因为它禁止应用程序执行除了中断以外的其他任务。在这种情况下,甚至也不可能执行finally块,通常用户自己不能处理像这样的错误,而应退出应用程序。

EndOfStreamException 这个异常通常是因为读到文件末尾而抛出的。

OverflowException 如果要在checked环境下把包含-40的int类型数据强制转换为Unit数据,就会抛出异常。

捕获异常

为了在C#代码中处理可能的错误情况,一般要把程序的相关部分分成3种不同的代码块:


Try块 包含的代码组成了程序的正常操作部分,但这部分程序可能遇到某些严重的错误。

Catch块 包含的代码处理各种错误情况,这些错误执行try块中的代码时遇到的。这个快还可以用于记录错误。

Finally块 包含的代码清理资源或执行通常要在try块或catch块末尾执行得操作。无论是否抛出异常,都会执行finally块,理解这一点,非常重要。因为finally块包含了应总是执行得清理代码,如果在finally块是,乐意关闭在try块中打开的连接。Finally块是完全可选的。如果不需要清理代码(如删除对象或者关闭已打开的对象就不需要包含此块)。

使用步骤:


执行的程序流进try块。

如果try块中没有错误发生,在块中正常执行。当程序流到try块末尾后,如果存在一个finally块,程序就会自动进入finally块。但如果在try块中程序流检测到一个错误,程序流就会跳转到catch块。

在catch中处理错误。

在catch块执行完成后,如果存在finally块,程序流就会自动进入finally块。

执行finally块。

例 :try


                 {

                          //Code for normalExecution


}


                           Catch


                                    {

                                              //error handling


}


                           Finally


{

//chean up


}


实际上,上面的代码还有几种变体:


可以省略finally,因为它是可选的。

可以提供任意多个catch块,处理不同类型的错误。但不应包含过多的catch块,以防降低应用程序的性能。

可以省略catch块,此时该语法应不是标识异常,而是一种确保程序流在离开try块后执行finally块中的代码的方式。如果try块中有几个出口点,这很有用。如果运行try块中的代码,则程序流如何在错误发生时切换到catch块? 如果检测到错误,代码就执行一定的操作,成为抛出一个异常;实例化一个异常对象类,并抛出异常 throw new OverflowException();只要计算机在try块中遇到throw 语句,就会立即查到与这个try块对应的catch块。如果有多个与try块对应catch块,计算机就会查找与catch块对应的异常类,确定正确的catch块。例如抛出一个OverflowException异常对象时,执行的程序流就会跳转到下面的Catch块;

Catch(OverflowException ex)


{

        //Exception handling here


}


假定可能在try块中发生两个严重错误:溢出和数组超出范围。假定代码包含两个布尔变量Overflow和OutofBounds,它们分别表示这两种错误的存在,如下所示:


Try


{

        If(overflow==true){throw new OverflowException();}


        If (outOfBounds==true){throw new IndexOutOfException();}


}


Catch(OverflowException ex)


{

        //error handling for the overflow error condition


}


Catch( IndexOutofRangeException ex)


{

        //error handling for the index  out of range error condition


}


Finally


{

        //clean up


}


Throw 语句可以嵌套在try块几个方法调用中,甚至在程序流进入其他方法时,也会继续执行同一个try块。如果计算机遇到一条throw 语句,就会立即退出栈上所有的方法调用,查找try块的结尾和合适的catch块的开头,此时,中间方法调用的所有局部变量超出作用域。Try ….catch结构最适合于本届开头描述的场合;错误发生在一个方法调用中,而该方法调用可能嵌套了15级或者20级,这些处理操作会立即停止。


Try块在控制执行得程序流上有重要的作用。但是,异常时用于处理异常情况的,不应该用异常来控制退出 do….. while循环时间。


例:


using System;


namespace Wrox.ProCSharp.AdvancedCSharp


{

  public class MainEntryPoint


  {

     public static void Main()


     {

         while ( true )


        {

           try


           {

              Console.Write("Input a number between 0 and 5 " +


                 "(or just hit return to exit)> ");


              string userInput = Console.ReadLine();


              if (userInput == "")


                 break;


              int index = Convert.ToInt32(userInput);


              if (index < 0 || index > 5)


                 throw new IndexOutOfRangeException(


                    "You typed in " + userInput);


               Console.WriteLine("Your number was " + index);


           }


           catch (IndexOutOfRangeException e)


           {

              Console.WriteLine("Exception: " +


                 "Number should be between 0 and 5. " + e.Message);


           }          


           catch (Exception e)


           {

              Console.WriteLine(


                 "An exception was thrown. Message was: " + e.Message);


           }


           catch


           {

              Console.WriteLine("Some other exception has occurred");


           }


           finally


           {

              Console.WriteLine("Thank you");


           }


        }


     }


  }


}


注:1、传递给catch块的参数只能用于该catch块。这就是为什么在上面的代码中能在后续的catch块中使用相同的参数ex的原因。


2、这里用break语句退出try块和while循环。这是有效的,当程序流退出try块时,会执行finally块的语句。


下面的异常:


if (index<0||index>5)


{

throw new IndexOutOfRangeException("You typed in " + userInput);


}


在抛出一个异常时,需要选择抛出异常的类型。可以使用System.Exception异常类,但这个类是一个基类,最好不要把这个类的实例当做一个异常抛出,因为他没有包含错误的任何信息。而.NETFramework包含了许多派生自System.Exception异常类的其他异常类,每个都对应于一种特定类型的异常情况,也可以定义异常类。在抛出一个匹配特定错误情况的类的实例时,应提供尽可能多的信息。在本例中,System.IndexOutofRangeException异常类是最佳选择。


   假定用户这次输入了不在0-5范围内的数字,if语句就会检测到一个错误,并实例化和抛出一个IndexOutOfRangeException异常对象。计算机会立即退出try块,并查找处理IndexOutOfRangeException异常的Catch块。


 catch (IndexOutOfRangeException e)


           {

              Console.WriteLine("Exception: " +


                 "Number should be between 0 and 5. " + e.Message);


           }          


由于这个Catch块带适合的一个参数,因此它就会传递给异常实例,并执行。在本例是显示错误信息和Exception.Message属性。执行了这个Catch块后,控制权就切换到finally块,就好像没有发生过任何异常。


  catch (Exception e)


           {

              Console.WriteLine(


                 "An exception was thrown. Message was: " + e.Message);


           }


没有前面的Catch块中捕获异常,则这个Catch块也能处理IndexOutOfRangeException异常。基类的一个引用也可以指派生自它的类的所欲实例,所有的异常都派生自System.Exception异常类。那么为什么不执行这个Catch块?答案是计算机指执行它在可用的Catch块列表中找到的第一个适合的Catch块。但为什么这要编写第二个Catch块?不仅try块包含这段代码,这有另外3个方法去调用Console.ReadLine(),Console.Write(),Convert.ToInt32()也包含这段代码,他们是System命名空间的方法。这个方法也会抛出异常。


        如果输入a或者Hello,Convert.ToInt32()方法就会抛出System.FormatException类的一个异常,表达传递给ToInt32()方法的字符串对应的格式不能转换为int。此时,计算机会跟踪这个方法调用,查找可以处理该异常的处理程序。第一个Catch块不能处理这种异常,因为FormatException派生与Exception异常类,所以把FormatException异常类的实例作为参数传递给它。


        该实例的这种结构是非常典型的多Catch结构。最先编写的Catch用子处理非常特殊的错误情况,接着是比较一般的块,他们可以处理任何错误。


        Catch块的顺序非常重要,如果这两个Catch块顺序相反的话,代码不会编译。因此最上面的Catch应用于最特殊的异常情况,最后是一般的Catch块。


System.Exception属性


属性


说明


Data


这个属性可以给异常添加键/值语句,以提供关于异常的额外信息


HelpLink


链接到一个帮助文件上,以提供关于该异常的更多信息


InnerException


如果此异常时在Catch块中抛出的,它就会包含代码发送到Catch中的异常对象。


Message


描述错误情况的文本


Source


导致异常的应用程序名或对象名


StackTrace


栈上方法调用的详细信息,它有助于跟踪抛出异常的方法。


Targetsite


描述抛出异常的方法的.NET反射对象


在这些属性中,如果可以进行栈跟踪,StackTrace和TargetSite属性由.NET运行库自动提供。Source属性总是有.NET运行库填充为抛出异常的程序集的名称(但在代码中可以修改,以提供更多的信息),Data,Message,HelpLink和InnerException属性必须在抛出异常的代码中填充,方法时在抛出异常前设置这些属性。例:


If (ErrorCondition==true)


{

                  Exception myException=new ClassMyException(“Help!!!”);


                  myException.Source=”My Application Name”;


                  myException.HelpLink=“MyHelpFile.txt”;


   myException.Data[“ErrorDate“]=DateTime.Now;


                  myException.Data.Add(“AdditionalInfo”,”Contact Bill from the Blue Team”);


                  throw myException;


}


没有处理的异常时,所发生的情况:

如果抛出异常,代码中没有Catch块能处理这类异常,则有.NET运行库捕获它。


嵌套的try块

异常的一个特性是try块可以嵌套,如下:


try


{

          //PointA


          try


          {

                   //PointB


}


Catch


{

        //PointC


}


Finally


{

        //Clean up


}


//Point D


}


Catch{//error Handling}


Finally{//Clean up}


如果抛出的异常在外层的 try 块中,在标记为 A点 和 D点 的代码块:异常由外层的 Catch块 捕获,并执行外层的 finally块 ,或者执行 finally块,由 .NET运 行库处理异常。

如果异常是在内层try块标记为 B点 的代码块中抛出的,且有一个合适的内层 catch块处理该异常,在内层处理异常,执行内层的 finally块 ,之后执行标记为 D点的代码块。

在内层抛出异常,且内层没有找到合适的处理程序。这是会执行内层finally块,但接着.NET运行库退出内层的try块,到外层的Catch块中寻找合适的Catch,如果找到后执行finally块,如果没有找到执行完finally块后,控制权转移给.NET运行库。注意:任何时候都不会执行标记点D的代码。

如果在C点抛出异常,如果程序执行到C点中,它就必须处理在B点抛出的异常。在Catch块中抛出另一个异常很正常。此时,异常的处理跟它是外层try块中抛出的一样,程序流会立即退出内层的Catch块,执行内层的finally块,系统在外层的Catch块中搜索处理程序。同样,在内层的finally块中抛出一个异常,搜索会在外层的Catch块开始,控制权会立即转移到最匹配的处理程序。

Try块的嵌套也可能发生在方法之间。例:如果方法A调用了try块中的方法B,那么方法B也包含一个try块。


为什么要用嵌套?


为了修改所抛出的异常的类型。

为了能够在代码的不同地方处理不同的异常。


上一篇:微服务设计的四个原则


下一篇:关于Object[]数组强转成Integer[]类型的数组.