目錄
我們平時在寫程序時,無意中(或技術不夠),而導致程序運行時出現意外(或異常),對於這個問題, C# 有專門的異常處理程序。 異常處理所涉及到的關鍵字有 try
、catch
和 finally
等,用來處理失敗的情況。 CLR、.NET 自身的類庫、其它第三方庫或者你寫的程序代碼都有可能會出現異常。當然,你也可以直接使用 throw ,通過顯式的形式來進行創建異常。
在你的代碼中出現異常的時候,程序會找到並執行最先匹配的 catch
塊。 如果在調用堆棧中的任意位置中,異常處理程序都沒有找到合適(你寫的)的 catch
塊,就會自動終止該進程,並向用戶顯示(拋出)一條錯誤的信息。
在這裡我寫了個被 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 }
使用 try
塊包圍你認為可能會出現異常的代碼。
一旦 try
塊中發生異常,控制流將按順序找到與之關聯的 catch 塊,如果沒有找到合適的,就會引發最終的異常基類 Exception 內的處理程序(前提你已經 catch)。
如果出現異常卻沒有對應的異常處理程序,則該程序將會停止執行,並拋出對應錯誤的信息。
在 catch
定義了的異常變量,可以獲取對應異常類型的信息。比如調用堆棧的狀態和錯誤的說明,具體看 Excetion 的屬性。
throw
關鍵字可以顯式引發異常。
即使出現異常也會執行 finally
塊中的代碼。一般來說,我們會使用 finally
塊釋放資源,例如,關閉xx流。
程序在運行時出現的錯誤,會不斷在程序中進行傳播,這種機制稱為“異常”。 異常通常由錯誤的代碼引發,並由能夠更正錯誤的代碼進行 catch。 異常也可以由 .NET 的 CLR 或由程序中的代碼引發, 一旦引發了異常,這個異常將會在調用堆棧中一直向上進行傳播,直到尋找到跟它匹配的 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 }
在引發異常之後,運行時程序會檢查當前語句確定它是否包含在 try
塊中。 如果是的話,就會檢查與該 try
塊相關聯的所有 catch
塊,來確定它們是否能夠 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 }
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
塊之前,CLR 會檢查 finally
塊。 finally
塊使程序員能夠清除中止的 try
塊可能遺留下的任何模糊狀態,或者釋放任何外部資源(例如圖形句柄、db 連接或 IO 流),而無需等待 CLR 中的垃圾回收器終結這些對象。 例如:
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 }
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# 知識回顧 - 序列化》
《C# 知識回顧 - 表達式樹 Expression Trees》
《C# 知識回顧 - 特性 Attribute》、《剖析 AssemblyInfo.cs - 了解常用的特性 Attribute》《C# 知識回顧 - 委托 delegate》、《C# 知識回顧 - 委托 delegate (續)》
《C# 知識回顧 - 事件入門》、《C# 知識回顧 - Event 事件》
《string 與 String,大 S 與小 S 之間沒有什麼不可言說的秘密》
【博主】反骨仔
【出處】http://www.cnblogs.com/liqingwen/p/6206251.html
【參考】微軟官方文檔