咱們在C#的try catch代碼塊中裏面常常使用throw語句拋出捕捉到的異常,可是你知道嗎使用throw ex和throw拋出捕獲到的異常效果是不同的。函數
異常捕捉的原理spa
首先先介紹一下C#異常捕捉的原理,默認狀況下在C#的一個函數中(注意這裏說的是在一個函數中,不是跨多個函數),只會將最後一個異常拋出的位置記錄到異常堆棧中,也就是說在一個函數中不管你用throw語句拋出了多少次異常,異常堆棧中始終記錄的是函數最後一次throw異常的位置,以下面代碼的函數ThrowExceptionFunction中使用throw語句拋出了4次異常,可是在46行的代碼處只顯示函數ThrowExceptionFunction在32行拋出了異常,以前拋出的3次異常都沒有被記錄到異常堆棧之中。.net
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 6 namespace ExceptionTest2 7 { 8 class Program 9 { 10 static void ThrowExceptionFunction() 11 { 12 try 13 { 14 try 15 { 16 try 17 { 18 throw new Exception("模擬異常"); 19 } 20 catch (Exception ex1) 21 { 22 throw; 23 } 24 } 25 catch (Exception ex2) 26 { 27 throw; 28 } 29 } 30 catch (Exception ex3) 31 { 32 throw; 33 } 34 35 } 36 37 38 static void Main(string[] args) 39 { 40 try 41 { 42 ThrowExceptionFunction(); 43 } 44 catch (Exception ex) 45 { 46 Console.WriteLine(ex.StackTrace);//由於C#會爲每一個函數的異常記錄一次堆棧信息,而本例中有兩個函數分別爲ThrowExceptionFunction和Main,因此這裏堆棧捕捉到了兩個異常一個是在函數ThrowExceptionFunction中32行,另外一個是Main函數中42行, 47 } 48 49 Console.ReadLine(); 50 } 51 } 52 }
在.net framework3.5及以前,函數中catch代碼塊拋出的異常沒法準確捕捉到位置,以下面代碼中Main函數最後一次拋出異常是在代碼20行,可是在25行輸出的信息中卻顯示異常是在代碼29行拋出的,這應該是.net framework3.5及以前的一個BUG,在.net framework4.0中已經修復了這個問題。code
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 6 namespace ExceptionTest3 7 { 8 class Program 9 { 10 static void Main(string[] args) 11 { 12 try 13 { 14 try 15 { 16 throw new Exception("異常模擬"); 17 } 18 catch (Exception ex1) 19 { 20 throw; 21 } 22 } 23 catch (Exception ex2) 24 { 25 Console.WriteLine(ex2.StackTrace); 26 } 27 28 Console.ReadLine(); 29 } 30 } 31 }
上面咱們說了C#只會將一個函數中最後一次拋出異常的位置記錄到異常堆棧之中,那麼有什麼辦法能將一個函數中拋出的全部異常都記錄到異常堆棧中嗎?答案是能夠的,構造嵌套異常便可,以下代碼所示:blog
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 6 namespace ExceptionTest4 7 { 8 class Program 9 { 10 static void ThrowExceptionFunction() 11 { 12 try 13 { 14 try 15 { 16 try 17 { 18 throw new Exception("模擬異常1"); 19 } 20 catch (Exception ex1) 21 { 22 throw new Exception("模擬異常2", ex1); 23 } 24 } 25 catch (Exception ex2) 26 { 27 throw new Exception("模擬異常3", ex2); 28 } 29 } 30 catch (Exception ex3) 31 { 32 throw new Exception("模擬異常4", ex3); 33 } 34 35 } 36 37 38 static void Main(string[] args) 39 { 40 try 41 { 42 ThrowExceptionFunction(); 43 } 44 catch (Exception ex) 45 { 46 Console.WriteLine(ex.ToString());//要想輸出函數ThrowExceptionFunction內拋出的全部異常,將ThrowExceptionFunction內部的異常都嵌套封裝便可,而後在輸出異常的時候使用ex.ToString()函數,就能夠輸出全部嵌套異常的堆棧信息 47 } 48 49 Console.ReadLine(); 50 } 51 } 52 }
上面代碼中咱們在函數ThrowExceptionFunction中將四個throw出來的異常都嵌套封裝了,最後在Main函數中使用ex.ToString()函數便可輸出完整的異常堆棧,在ThrowExceptionFunction函數中拋出的全部異常都顯示在了ex.ToString()函數輸出的堆棧列表之中。string
throw ex和throwit
咱們知道在try catch的catch代碼塊捕捉到異常以後可使用throw ex和throw將捕捉到的異常再拋出來,那麼這兩種寫法有什麼不一樣呢?io
throw exclass
throw ex這種寫法會讓C#重置異常的拋出點,咱們來看這段代碼:原理
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.Threading.Tasks; 6 7 namespace ExceptionTesting 8 { 9 class Program 10 { 11 static void InnerException() 12 { 13 throw new Exception("模擬異常"); 14 } 15 16 static void OuterException() 17 { 18 try 19 { 20 InnerException(); 21 } 22 catch (Exception ex) 23 { 24 throw ex; 25 } 26 } 27 28 static void Main(string[] args) 29 { 30 try 31 { 32 OuterException(); 33 } 34 catch (Exception ex) 35 { 36 Console.WriteLine(ex.StackTrace);//因爲代碼24行使用throw ex重置了異常拋出點,因此這裏異常堆棧只能捕捉到代碼32行和24行拋出的異常,可是13行的異常在堆棧中沒法捕捉到 37 } 38 39 Console.ReadLine(); 40 } 41 } 42 }
能夠看到使用throw ex會使得C#重置代碼中異常的拋出點,從而讓C#認爲異常的原始拋出點應該是在代碼24行,在異常堆棧中沒法捕捉到代碼13行所拋出的異常。
throw
使用throw和throw ex惟一的不一樣就是throw並不會讓C#重置異常的拋出點,咱們將上面代碼中24行的throw ex改成throw以下:
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.Threading.Tasks; 6 7 namespace ExceptionTesting 8 { 9 class Program 10 { 11 static void InnerException() 12 { 13 throw new Exception("模擬異常"); 14 } 15 16 static void OuterException() 17 { 18 try 19 { 20 InnerException(); 21 } 22 catch(Exception ex) 23 { 24 throw; 25 } 26 } 27 28 static void Main(string[] args) 29 { 30 try 31 { 32 OuterException(); 33 } 34 catch(Exception ex) 35 { 36 Console.WriteLine(ex.StackTrace);//因爲如今代碼24行使用了throw拋出捕獲到的異常,並無重置原始異常的拋出點,因此這裏異常堆棧不但能捕捉到代碼32行和24行拋出的異常,還能捕捉到代碼13行拋出的異常。 37 } 38 39 Console.ReadLine(); 40 } 41 } 42 }
因爲這一次咱們使用了throw來拋出代碼24行中catch代碼塊中捕獲到的異常,並無重置異常的拋出點,所以在上面代碼36行這一次異常堆棧輸出了13行、24行、32行三個異常。