异常是程序中的运行时错误,它违反了系统约束或应用程序约束,或出现了在正常操做时未预料的情形。例如,程序试图除以0或试图写一个只读文件。当这些发生时,系统捕获这个错误并抛出(raise)一个异常。
若是程序没有提供处理该异常的代码,系统会挂起这个程序。例如,下面的代码在试图用0除一个数时抛出一个异常:ide
class Program { static void Main() { int x=10,y=0; x/=y; //用0除以一个数时抛出一个异常 } }
当这段代码运行时,系统显示下面的错误信息:
3d
try语句用来指明为避免出现异常而被保护的代码段,并在发生异常时提供代码处理异常。try语句由3个部分组成,以下图所示。code
前面的示例显示了除以0会致使一个异常。能够修改此程序,把那段代码放在一个try块中,并提供一个简单的catch子句,以处理该异常。当异常发生时,它被捕获并在catch块中处理。对象
static void Main() { int x = 10; try { int y=0; x/=y; //抛出异常 } catch { …//异常处理代码 Console.WriteLine("Handling all exceptions - keep on Running"); } }
这段代码产生如下消息。注意,除了输出消息,没有异常已经发生的迹象。
blog
有许多不一样类型的异常能够在程序中发生。BCL定义了许多类,每个类表明一个指定的异常类型。当一个异常发生时,CLR:排序
全部异常类都从根本上派生自System.Exception类。异常继承层次的一个部分以下图所示。
异常对象含有只读属性,带有致使该异常的信息。这些属性的其中一些以下表所示。
继承
catch子句处理异常。它有3种形式,容许不一样级別的处理。这些形式以下图所示。
通常catch子句能接受任何异常,但不能肯定引起异常的类型。这只容许对任何可能发生的异常的普通处理和清理。
特定catch子句形式把一个异常类的名称做为参数。它匹配该指定类或派生自它的异常类的异常。
带对象的特定catch子句提供关于异常的最多信息。它匹配该指定类的异常,或派生自它的异常类的异常。它还给出一个异常实例(称为异常变量),是一个对CLR建立的异常对象的引用。能够在catch子句块内部访问异常变量的属性,以获取关于引发的异常的详细信息。
例如,下面的代码处理IndexOutOfRangeException类型的异常。当异常发生时,一个实际异常对象的引用被参数名e传入代码。那3个WriteLine语句中,每一个都从异常对象中读取一个字符串字段。字符串
catch(IndexOutOfRangeException e) { Console.WriteLine("Message: {0}",e.Message); Console.WriteLine("Source: {0}",e.Source); Console.WriteLine("Stack: {0}",e.StackTrace); }
回到除以0的示例,下面的代码把前面的catch子句修改成指定处理DivideByZeroException类的异常。在前面的示例中,catch子句会处理所在try块中引发的任何异常,而这个示例将只处理DivideByZeroException类的异常。string
int x=10; try { int y=0; x/=y; } catch(DivideByZeroException) { … Console.WriteLine("Handling an exception."); }
能够进一步修改catch子句以使用一个异常变量。这容许在catch块内部访问异常对象。it
int x=10; try { int y=0; x/=y; } catch(DivideByZeroException e) { Console.WriteLine("Message: {0}",e.Message); Console.WriteLine("Source: {0}",e.Source); Console.WriteLine("Stack: {0}",e.StackTrace); }
在笔者的电脑上,这段代码会产生如下输出。对于读者的机器,第三行和第四行的代码路径可能不一样,这要与你的项目位置和解决方案目录匹配。
catch子句的目的是容许你以一种优雅的方式处理异常。若是你的catch子句接受一个参数,那么系统会把这个异常变量设置为异常对象,这样你就能够检査并肯定异常的缘由。若是异常是前一个异常引发的,你能够经过异常变量的InnerException属性来得到对前一个异常对象的引用。catch子句段能够包含多个catch子句。下图显示了catch子句段。
当异常发生时,系统按顺序搜索catch子句的列表,第一个匹配该异常对象类型的catch子句被执行。所以,catch子句的排序有两个重要的规则。具体以下。
若是程序的控制流进人了一个带finally块的try语句,那么finally始终会被执行。下图阐明了它的控制流。
即便try块中有return语句或在catch块中抛出一个异常,finally块也老是会在返回到调用代码以前执行。例如,在下面的代码中,在try块的中间有一条return语句,它在某条件下被执行。
这不会使它绕过finally语句。
try { if(inVal<10) { Console.Write("First Branch - "); return; } else { Console.Write("Second Branch - "); } } finally { Console.WriteLine("In finally statement"); }
这段代码在inVal值为5时产生如下输出:
当程序产生一个异常时,系统查看该程序是否为它提供了一个处理代码。下图阐明了这个控制流。
若是异常在一个没有被try语句保护的代码段中产生,或若是try语句没有匹配的异常处理程序,系统将不得不更进一步寻找匹配的处理代码。为此它会按顺序搜索调用栈,以看看是否存在带匹配的处理程序的封装try块。
下图阐明了这个搜索过程。图左边是代码的调用结构,右边是调用栈。该图显示Method2被从Method1的try块内部调用。若是异常发生在Method2内的try块内部,系统会执行如下操做。
下图展现了处理异常的通常法则。
在下面的代码中,Main开始执行并调用方法A,A调用方法B。代码以后给出了相应的说明, 并在图22-9中再现了整个过程。
class Program { static void Main() { var MCls=new MyClass(); try { MCls.A(); } catch(DivideByZeroException e) { Console.WriteLine("catch clause in Main()"); } finally { Console.WriteLine("finally clause in Main()"); } Console.WriteLine("After try statement in Main."); Console.WriteLine(" -- keep running."); } } class MyClass { public void A() { try { B(); } catch(System.NullReferenceException) { Console.WriteLine("catch clause in A()"); } finally { Console.WriteLine("finally clause in A()"); } } void B() { int x=10,y=0; try { x/=y; } catch(System.IndexOutOfRangeException) { Console.WriteLine("catch clause in B()"); } finally { Console.WriteLine("finally clause in B()"); } } }
这段代码产生如下输出:
可使用throw语句使代码显式地引起一个异常。throw语句的语法以下:
throw ExceptionObject;
例如,下面的代码定义了一个名称为PrintArg的方法,它带一个string参数并把它打印出来。在try块内部,它首先作检査以确认该参数不是null。若是是null,它建立一个ArgumentNullException实例并抛出它。该异常实例在catch语句中被捕获,而且该出错消息被打印。Main调用该方法两次:一次用null参数,而后用一个有效参数。
class MyClass { public static void PrintArg(string arg) { try { if(arg==null) { var myEx=new ArgumentNullException("arg"); throw myEx; } Console.WriteLine(arg); } catch(ArgumentNullException e) { Console.WriteLine("Message: {0}",e.Message); } } } class Program { static void Main() { string s=null; MyClass.PrintArg(s); MyClass.PrintArg("Hi there!"); } }
这段代码产生如下输出:
throw语句还能够不带异常对象使用,在catch块内部。
例如,下面的代码从第一个catch子句内部从新抛出异常:
class MyClass { public static void PrintArg(string arg) { try { try { if(arg==null) { var myEx=new ArgumentNullException("arg"); throw myEx; } Console.WriteLine(arg); } catch(ArgumentNullException e) { Console.WriteLine("Inner Catch: {0}",e.Message); throw; } } catch { Console.WriteLine("Outer Catch: Handling an Exception."); } } } class Program { static void Main() { string s=null; MyClass.PrintArg(s); } }
这段代码产生如下输出:
<wiz_tmp_tag id="wiz-table-range-border" contenteditable="false" style="display: none;">