咱們平時在寫代碼或程序時,無心中(通常就是技術不夠),而致使程序運行時出現意外(又稱爲異常),對於這個問題, C# 有專門的異常處理程序(固然其餘語言也有)。html
異常處理所涉及到的關鍵字有幾個,不用說都知道,已經耳熟能詳了:try
、catch
和 finally
等,用來處理失敗的狀況。固然,儘管這些操做也有可能失敗,通常來講是釋放,清理某些資源或記錄日誌等。程序員
哪些代碼會出現異常呢:使用的基類庫 BCL、第三方類庫和咱們寫的自覺得是的代碼,還有,可使用 throw 顯式拋出異常。數據庫
一種廣泛出現的情形,異常極可能不是由代碼直接引起,而是由調用堆棧中更靠下的位置其它方法所引起。在這種情形下下,CLR 會展開堆棧,並查找是否有包含針對你指定異常類型的 catch
塊的代碼,若是找到的話,就會執行最早匹配的 catch
塊。 若是在調用堆棧中的任意位置中,異常處理程序都沒有找到合適(你寫的)的 catch
塊,就會自動終止該進程,並向用戶顯示(拋出)一條錯誤的信息。ide
在這裏我寫了一個被 0 處會出現異常(會顯式引起 DivideByZeroException 異常)的示例;若是在執行的途中出現異常,則捕獲該異常。函數
1 /// <summary> 2 /// 除法 3 /// </summary> 4 /// <param name="x"></param> 5 /// <param name="y"></param> 6 /// <returns></returns> 7 static double Division(double x, double y) 8 { 9 if (y == 0) 10 { 11 throw new DivideByZeroException(); 12 } 13 14 return x / y; 15 } 16 17 static void Main(string[] args) 18 { 19 //定義兩個變量 x, y 20 double x = 250, y = 0; 21 22 try 23 { 24 var result = Division(x, y); 25 Console.WriteLine($"result: {result}"); 26 } 27 catch (DivideByZeroException e) 28 { 29 30 Console.WriteLine(e); 31 } 32 33 Console.Read(); 34 }
全部異常類型(包括自定義的異常)都是由基類 Exception
派生的。post
使用 try
塊包圍你認爲可能會出現異常的代碼。this
一旦 try
塊中發生異常,控制流將按順序找到與之關聯的 catch 塊,若是沒有找到合適的,就會引起最終的異常基類 Exception 內的處理程序(前提你已經 catch)。url
若是出現異常卻沒有對應的異常處理程序,則該程序將會中止執行,並拋出對應錯誤的信息。spa
在 catch
定義了的異常變量,能夠獲取對應異常類型的信息。好比調用堆棧的狀態和錯誤的說明,具體看 Excetion 的屬性。線程
throw
關鍵字能夠顯式引起異常。
即便出現異常也會執行 finally
塊中的代碼。通常來講,咱們會使用 finally
塊釋放資源,例如,關閉xx流。
異常:指的是咱們寫的程序在運行時出現了錯誤,而且它會不斷的蔓延、傳播和擴散,有點像病毒同樣。
異常一般由錯誤的代碼引起,多是用戶的錯誤輸入,多是一方沒有按照約定來傳輸格式,也多是數據傳輸的過程當中被篡改。咱們會對本身認爲有可能報錯的代碼進行 catch ,這稱爲捕獲異常。
一旦引起了異常,這個異常將會在調用堆棧中一直向上進行傳播,直到尋找到跟它匹配的 catch
語句。沒有 catch 的異常會由系統提供的默認的異常處理程序進行處理,也就是你常常看到的一個忽然形成調試中斷並顯示異常信息的對話框。
全部的異常,它都是從 Exception 派生出來的,他們都包含了詳細的異常描述屬性。在這裏我將自定義了一個新的異常類,而後使用 throw
關鍵字顯式引起該對象(即異常)。
1 /// <summary> 2 /// 定義新異常 3 /// </summary> 4 class MyException : Exception 5 { 6 public MyException(string msg) { } 7 } 8 9 /// <summary> 10 /// 拋出新定義的異常 11 /// </summary> 12 static void ThrowMyExcetion() 13 { 14 throw new MyException("Sorry, this is test!"); 15 }
在引起異常以後,CLR 運行時程序會檢查當前語句肯定它是否包含在 try
塊中。 若是是的話,就會檢查與該 try
塊相關聯的全部 catch
塊,來肯定它們是否可以 catch 該異常。若是該 catch
塊的類型與異常或它的基類的相同(或匹配),則該 catch
塊就可以捕獲並處理。
1 static void Main(string[] args) 2 { 3 try 4 { 5 ThrowMyExcetion(); //直接調用拋出異常的方法 6 } 7 catch (MyException e) 8 { 9 Console.WriteLine(e); 10 } 11 12 Console.Read(); 13 }
若是引起異常的語句不在 try
塊中,或者包含該語句的 try
塊沒有匹配的 catch
塊,CLR 運行時將檢查調用方法中是否有合適 try
語句和 catch
塊。 運行時將在調用堆棧中繼續往上搜索兼容(或匹配)的 catch
塊。在找到並執行 catch
塊以後,控制權將傳遞給 catch
塊以後的下一個語句。
一個 try
語句可能包含多個 catch
塊。 將執行第一個可以處理該異常的 catch
語句;任何後續的 catch
語句都將被忽略。 所以,在任何狀況下都應該按照從最具體(或者派生程度最高)到最不具體這一順序排列 catch 塊。 例如:
1 static void Main(string[] args) 2 { 3 StreamWriter sw = null; 4 5 try 6 { 7 sw = new StreamWriter(@"C:\book\小二和小三的故事.txt"); 8 sw.Write("You are 250."); 9 } 10 catch (FileNotFoundException e) 11 { 12 //將具體的異常放在第一位 13 Console.WriteLine(e); 14 } 15 catch (IOException e) 16 { 17 //將並不具體的放在相對後面的位置 18 Console.WriteLine(e); 19 } 20 catch (Exception e) 21 { 22 Console.WriteLine(e); 23 } 24 finally 25 { 26 if (sw != null) 27 { 28 sw.Close(); 29 } 30 } 31 32 Console.Read(); 33 }
執行 catch
塊以前,運行時會檢查 finally
塊。 Finally
塊使程序員可以清除停止的 try
塊可能遺留下的任何模糊狀態,或者釋聽任何外部資源(例如圖形句柄、數據庫鏈接或文件流),而無需等待運行時中的垃圾回收器終結這些對象。 例如:
1 static void Main(string[] args) 2 { 3 FileStream fs = null; 4 FileInfo fi = new FileInfo(@"小二和小三的故事.txt"); 5 6 try 7 { 8 fs = fi.OpenWrite(); 9 fs.WriteByte(0); 10 } 11 finally 12 { 13 //記住哦,若是你忘記 close,將會引起 IO 異常! 14 //if (fs != null) 15 //{ 16 // fs.Close(); 17 //} 18 } 19 20 try 21 { 22 fs = fi.OpenWrite(); 23 fs.WriteByte(1); 24 Console.WriteLine("OK!"); 25 } 26 catch (IOException e) 27 { 28 Console.WriteLine("Fail!"); 29 } 30 31 Console.Read(); 32 }
「Fail!」,這是由於上面註釋了須要關閉文件流的語句,你能夠嘗試下去掉註釋看看結果,記住哦,IO 操做都應該在結束時釋放資源。
若是 WriteByte(0)(第9行)
引起了異常,那麼在沒有調用 fs.Close()
的狀況下,你在第二個 try
塊中嘗試從新 OpenWrit() 的代碼就會失敗,由於此時文件會保持鎖定狀態。 假如你取消註釋,因爲會執行 finally
塊(即便已引起異常),使得能夠正確地關閉文件,從而避免再次引起異常。
若是在引起異常以後沒有在調用堆棧上找到相匹配的 catch
塊,則:
若是異常出如今析構函數中,則停止該析構函數並調用基類的析構函數(若是有)。
若是調用堆棧包含靜態構造函數或靜態字段初始值設定項,則會引起 TypeInitializationException,並將原始異常分配給新異常的 InnerException 屬性。
若是到達線程的開頭,將會終止線程。
你可使用 try 塊來對你以爲可能會出現異常的代碼進行分區。 其中,與之關聯的 catch 塊可用於處理任何異常狀況。 一個包含代碼的 finally 塊,不管 try
塊中是否在運行時引起異常(例如,釋放在 try
塊中分配的資源),這些 finally 塊的代碼都會運行。 這些「異常部分」:能夠由一個 try
塊、一個或多個關聯的 catch
塊、一個 finally
塊分別組合。
這裏我列舉了 3 種狀況:一個 try-catch
語句,一個 try-finally
語句,和一個 try-catch-finally
語句。
(1)try-catch:
1 static void Main(string[] args) 2 { 3 try 4 { 5 //須要執行的代碼 6 } 7 catch (Exception e) 8 { 9 //這裏能夠獲取到被捕獲的異常 10 //你須要知道本身應該如何處理該異常 11 } 12 }
(2)try-finally:
1 try 2 { 3 //須要執行的代碼 4 } 5 finally 6 { 7 //在 try 塊後執行的代碼 8 }
(3)try-catch-finally:
1 try 2 { 3 //須要執行的代碼 4 } 5 catch (Exception e) 6 { 7 //這裏處理異常 8 } 9 finally 10 { 11 //在 try 塊(也多是 catch 塊)後執行的代碼 12 }
【備註】不帶有 catch
或 finally
塊的 try
塊將致使編譯器錯誤。
catch
塊能夠指定要捕捉的異常類型,又能夠稱爲「異常篩選器」。 異常類型都是從 Exception 派生出來。 通常而言,不會將全部異常的基類 System.Exception 指定爲要 catch 的「異常篩選器」,除非你很是瞭解如何處理由 try
塊引起的全部異常,或者在 catch
塊中包括了 throw 語句。
多個 catch
塊能夠串聯在一塊兒(要求異常篩選器不一樣)。 多個 catch
塊的執行順序是:在代碼中,從頂部到底部,可是,對於在運行時所引起的每個異常,程序都只會執行一個 catch
數據塊。 與指定的異常類型或它的基類相匹配的第一個 catch
塊,纔會被執行。 一般,咱們須要將最特殊(最具體或者說派生程度最最最高)的異常類,這段 catch
塊放在全部 catch 塊的最前面,而他們的基類 Excetion 的 catch 塊就放在最後(固然,也能夠不寫)。
在如下條件爲真時,你應該選擇 catch 異常:
瞭解引起異常的緣由,並可實現有選擇性的恢復。例如,在捕獲 FileNotFoundException 時你能夠提示用戶「文件找不到」和「請輸入新的文件名」等。
你也能夠新建一個更具體或者說更具備表明性的異常,並選擇引起該異常。
1 double GetNum(double[] nums,int index) 2 { 3 try 4 { 5 return nums[index]; 6 } 7 catch (IndexOutOfRangeException e) 8 { 9 throw new ArgumentOutOfRangeException("Sorry, 你想要的索引已經超出界限!"); 10 } 11 }
但願在將異常拋出去時,咱們一般會選擇處理部分異常。 在下面這個示例中,catch
塊在再次 throw 異常以前,添加錯誤日誌。
1 try 2 { 3 //嘗試訪問系統資源 4 } 5 catch (Exception e) 6 { 7 //僞代碼:記錄錯誤日誌 8 log.Error(e); 9 10 //再從新拋出錯誤 11 throw; 12 }
可使用 finally
塊釋放(清理)在 try
塊中須要執行釋放(清理)資源的操做。 若是存在finally
塊,它將在最後執行,也就是在 try
塊和任何匹配 catch
塊以後執行。 不論是否引起異常或者說是否找到與異常類型相匹配的 catch
塊,finally
塊它始終都會運行。
可使用 finally
塊釋放資源(如 IO 流、DB 鏈接和圖形句柄),而不要等待運行時中的垃圾回收器來完成對象資源的回收。 其實,咱們更建議使用 using 語句。
在下面的示例中,我使用 finally
塊關閉在 try
塊中打開的文件。注意,在關閉文件以前你應該要檢查該文件句柄的狀態。 若是 try
塊沒法打開文件,則文件句柄的值依然爲 null
,這時, finally
塊就不會嘗試關閉它。 或者說,若是在 try
塊中成功打開該文件,則 finally
塊纔會成功地關閉正在打開的文件。
1 static void Main(string[] args) 2 { 3 FileStream fs = null; 4 FileInfo fi = new System.IO.FileInfo("C:\\小二和小三的故事.txt"); 5 6 try 7 { 8 fs = fi.OpenWrite(); 9 fs.WriteByte(0); 10 } 11 finally 12 { 13 // 記得判斷 null 哦,否則可能觸發其它異常 14 if (fs != null) 15 { 16 fs.Close(); 17 } 18 } 19 20 }
《C# 知識回顧 - 委託 delegate》、《C# 知識回顧 - 委託 delegate (續)》
《C# 知識回顧 - 事件入門》、《C# 知識回顧 - Event 事件》
《string 與 String,大 S 與小 S 之間沒有什麼不可言說的祕密》
【博主】反骨仔
【出處】http://www.cnblogs.com/liqingwen/p/6206251.html
【參考】微軟官方文檔