[C#] C# 知識回顧 - 你真的懂異常(Exception)嗎?

来源:http://www.cnblogs.com/liqingwen/archive/2016/12/21/6206251.html
-Advertisement-
Play Games

你真的懂異常(Exception)嗎? 目錄 異常介紹 異常的特點 怎樣使用異常 處理異常的 try-catch-finally 捕獲異常的 Catch 塊 釋放資源的 Finally 塊 捕獲異常的 Catch 塊 釋放資源的 Finally 塊 一、異常介紹 我們平時在寫程式時,無意中(或技術不 ...


你真的懂異常(Exception)嗎?

目錄

  • 異常介紹
  • 異常的特點
  • 怎樣使用異常
  • 處理異常的 try-catch-finally
    • 捕獲異常的 Catch 塊
    • 釋放資源的 Finally 塊

 

一、異常介紹

  我們平時在寫程式時,無意中(或技術不夠),而導致程式運行時出現意外(或異常),對於這個問題, C# 有專門的異常處理程式。 異常處理所涉及到的關鍵字有 trycatch 和 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         }

 

二、異常的特點

  • 所有異常類型(包括自定義的異常)都是由基類 Exception 派生的。

  • 使用 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         }

 

  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 塊之前,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         }

 

  你看到結果了嗎,是:“Fail!”,這是因為上面註釋了需要關閉 IO 流的語句,你可以嘗試下去掉註釋再看看結果,記住哦,IO 操作都應該在結束時釋放資源。 

  如果 WriteByte(0)(第9行) 引發了異常,那麼在沒有調用 fs.Close() 的情況下,你在第二個 try 塊中嘗試重新 OpenWrit() 的代碼就會失敗,因為此時文件會保持鎖定狀態。 假如你取消註釋,由於會執行 finally 塊(即使已引發異常),使得可以正確地關閉文件,從而避免再次引發異常。

  如果在引發異常之後沒有在調用堆棧上找到相匹配的 catch 塊,則會可能會出現下麵的情況:

  • 如果異常出現在析構函數中,則中止該析構函數並調用基類的析構函數(如果有)。

  • 如果調用堆棧包含靜態構造函數或靜態欄位初始值設定項,則會引發 TypeInitializationException,並將原始異常分配給新異常的 InnerException 屬性。

  • 如果到達線程的開頭,將會終止線程。

 

四、處理異常的 try-catch-finally

  你可以使用 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 塊將導致編譯器錯誤。

 

4.1 捕獲異常的 Catch 塊

  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             }

 

4.2 釋放資源的 Finally 塊

  可以使用 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# 知識回顧 - 序列化

  《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

【參考】微軟官方文檔

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 一、借鑒說明 1.《Head First Design Patterns》(中文名《深入淺出設計模式》) 2.維基百科,策略模式,https://zh.wikipedia.org/wiki/%E7%AD%96%E7%95%A5%E6%A8%A1%E5%BC%8F 二、策略模式 基礎知識 將一類相同的... ...
  • "檢索COM類工廠中 CLSID為 {00024500-0000-0000-C000-000000000046}的組件時失敗,原因是出現以下錯誤: 80070005" 問題的解決 ...
  • 菜單收縮有很多種方法具體如何實現還是看個人想法: 第一種通過後臺控制收起與展開: 效果圖: 代碼 : <Grid> <Grid.ColumnDefinitions> <ColumnDefinition x:Name="cd" Width="154"/> <ColumnDefinition /> </ ...
  • 用html實現圖片上傳 後臺採用.net其中在這裡要借用一個js插件 在這裡我會寫一個圖片上傳的一個小Demo,有不全的地方多多包容,和提議, 我把已經寫好的demo已經上傳到百度雲 在這裡可以下載 http://pan.baidu.com/s/1bG2934 開始 html中的內容是 <body> ...
  • 年關將至,對於大部分程式員來說,馬上就可以閑下來一段時間了,然而在這個閑暇的時間里,唯有爭論哪門語言更好可以消磨時光,估計最近會有很多關於java與.net的博文出現,我表示要作為一個吃瓜群眾,靜靜的看著大佬們發表心情。 以上的廢話說的夠多了,這裡就不再廢話了,還是切入正題吧。 在項目開發中,對於系 ...
  • 本文要介紹的是ASP.NET怎樣讀寫文本文件,但更重要的是實現的過程。使用的工具是Visual Studio 2015 ,.NET版本是4.6.1 。一共建立的2個項目,HoverTreePanel和HoverTreeWeb,都是ASP.NET項目。文章末尾附源碼下載。項目結果如下圖:讀寫文件功能在 ...
  • 在多線程(線程同步)中,我們將學習多線程中操作共用資源的技術,學習到的知識點如下所示: 執行基本的原子操作 使用Mutex構造 使用SemaphoreSlim構造 使用AutoResetEvent構造 使用ManualResetEventSlim構造 使用CountDownEvent構造 使用Bar ...
  • 目錄 前言 OAuth2.0簡介 授權模式 (SimpleSSO示例) 使用Microsoft.Owin.Security.SimpleSSO模擬OpenID認證 通過authorization code授權模式申請令牌 通過implicit授權模式申請令牌 通過password模式申請令牌 通過c ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...