異常是程序中的運行時錯誤,它違反了系統約束或應用程序約束,或出現了在正常操做時未預料的情形。例如,程序試圖除以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;">