C#筆記 – 異常處理

  • 異常

    • 成員沒有完成它的名稱所宣稱的行動

  • C#中的異常處理使用

    • public static void ExceptionControl()
      {
         string a = null;
         try
        {
             a.Contains("Str");
        }
         catch (NullReferenceException e)
        {
             Console.WriteLine("Null Reference Exception Caught!");
        }
         catch
        {
             Console.WriteLine("Some Exception Caught");
        }
         finally
        {
             Console.WriteLine("Exception Test Finished");
        }
      }
    • try塊

      • 應包含以下類型的代碼

        • 需要執行的資源清理操作的代碼

        • 需要從異常中恢復的代碼

        • 可能會拋出異常的代碼

      • 一個try塊至少要有一個關聯的catch塊/finally塊

    • catch塊

      • 響應一個異常需要執行的代碼

      • 可以有0個或多個catch塊

      • 如果try塊代碼沒有造成異常的拋出,則永遠不會執行任何catch塊,直接執行finally塊(如有)

      • catch塊後的圓括號中的表達式為「捕捉類型」

        • 該類型必須派生自System.Exception

        • 如果只有catch關鍵字,沒有指定任何捕捉類型,代表捕捉除了上面catch塊指定了的具體捕捉類型以外的捕捉類型

          • 如果在捕捉類型後指定一個變量,該變量將引用拋出的System.Exception派生對象

        • CLR自上而下搜索匹配的catch塊,因此越是具體的(從System.Exception派生得越遠)的異常,應最先被捕捉

          • 如果搜索過後,沒有任何捕捉類型與拋出的異常匹配,CLR會去調用棧更高的一層嘗試搜索匹配的異常捕捉類型

          • 一旦找到,就會執行「內層」的所有finally塊代碼

    • finally塊

      • 保證會執行

      • 一般執行try塊行動所要求的資源清理操作

      • public static void ReadData(string path)
        {
           FileStream fs = null;
           try
          {
               fs = new FileStream(path, FileMode.Open);
          }
           catch (IOException) { }
           finally
          {
               if(fs != null)
              {
                   fs.Close();
              }                
          }
        }
      • 如在該段代碼中,無論try中的代碼有沒有拋出異常,文件保證會被finally中的代碼所關閉

        • 否則,如果文件流關閉的代碼放了在finally塊之後的地方,那當try塊拋出異常後,finally塊後的語句沒法被執行,文件流也就無法被關閉

        • 因此,C#編譯器看到一些必須進行清理的代碼,會自動生成try/finally塊

          • lock/using/foreach/定義析構器

      • 一個try塊最多只能關聯一個finally塊

System.Exception
  • 雖然CLR允許異常拋出任何類型的實例,但Microsoft定義了System.Exception類,並規定所有CLS相容的語言都必須能拋出和捕捉派生自該類型的異常

    • 派生自System.Exception的異常類型被認為是CLS相容的

    • C#也只允許拋出CLS相容的異常

  • System.Exception提供了一個唯讀的StackTrace屬性

    • catch塊可讀取該屬性來獲取一個stack trace,它描述了異常發生前調用了哪些方法

    • 訪問該屬性會調用CLR的代碼

      • 一個異常拋出時,CLR在內部記錄throw指令的位置

約束執行區域(CER)
  • CER是必須對錯誤有適應力的代碼塊

    • 在拋出了非預期的異常時維護狀態非常有用

  • RuntimeHelpers.PrepareConstrainedRegions

    • public static void Demo()
      {
         RuntimeHelpers.PrepareConstrainedRegions();
         try
        {
             Console.WriteLine("In Try");
        }
         finally
        {
             Type2.M();
        }
      }

      public class Type2
      {
         static Type2()
        {
             Console.WriteLine("Type2 ctor called");
        }

        [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]
         public static void M() { }
      }
    • JIT編譯器如果發現在一個try塊前調用了RuntimeHelpers.PrepareConstrainRegions,就會提前編譯與try關聯的catch和finally塊中的代碼

      • 但是,在catch/finally塊裡被調用的方法,必須應用ReliabilityContract特性。且傳遞了Consistency.WillNotCorruptState/Consistency.MayCorruptInstance枚舉值

代碼協定
  • 代碼協定提供了直接在代碼中聲明代碼設計決策的一種方式

    • 前條件:驗證實參

    • 後條件:方法因返回/拋出異常而終止時,對狀態進行驗證

    • 對象不變性:在對象生命期內確保對象的字段的良好狀態

  • 核心為靜態類:System.Diagnostics.Contracts.Contract

參考書目

  • 《CLR via C#》(第4版) Jeffrey Richter