← 筆記

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