對於任何一個.NET應用程序中的類,其所包含的方法都有一個異常處理表,如果此方法中沒有try...catch...finally語句,則異常處理表爲空。(即此方法生成的IL指令中不包括任何的異常處理子句)
根據之前的學習我們知道,當應用程序擁有多層嵌套的異常捕獲結構時,如果最底層發生了異常,CLR將優先在引發異常的那一層去搜索catch語句塊,看看有沒有“兼容”此類型異常的處理代碼,如果沒有,跳到上一層去查找,如果上一層還沒有,繼續搜索上一層的上一層,直到應用程序的最頂層。
這就是CLR處理嵌套異常捕獲結構應用程序的第一輪遍歷——查找合適的異常處理程序。
如果在某一層中找到了異常處理代碼,CLR並不會馬上執行,而是回到事故現場,進行第二輪遍歷,執行所有中間層的finally語句塊,然後執行找到的異常處理代碼,最後再從本層開始一直遍歷到最頂層,執行所有的finally語句塊。
這就是.NET應用程序異常處理策略的兩輪遍歷。
示例代碼如下:
class ExceptionA : Exception
{
}
class Program
{
static void Main(string[] args)
{
try
{
throw new ExceptionA();
}
catch (FormatException ex)
{
Console.WriteLine(ex.Message);
}
catch (NullReferenceException ex)
{
Console.WriteLine(ex.Message);
}
catch (ExceptionA ex)
{
Console.WriteLine(ex.Message);
}
finally
{
Console.WriteLine("finally");
}
}
}
生成的IL指令代碼如下:
當.NET應用程序運行時,如果某個方法引發了一個異常,CLR首先會將相應的異常對象推入計算堆棧,然後掃描此方法的所包含的異常處理表查找處理程序,其處理過程大致如下:
CLR獲取引發異常的IL指令地址,然後從上到下的掃描異常處理表,取出每個catch子句中.try關鍵字後面跟着的用於定位“塊”的起始和結束地址,判斷一下引發異常的IL地址是否落入此地址範圍內,如果是,取出catch關鍵字後面跟着的異常類型,對比一下是否與拋出的異常對象類型一致(或兼容)如果滿足,CLR取出handler後面的兩個IL地址,“準備”執行這兩個地址指定範圍內的IL指令(其實就是對應的catch語句塊的異常處理代碼)。
如果本方法的異常處理表中未找到合適的catch子句,CLR會依據引發異常的線程所關聯的方法調用堆棧,查找此方法的調用者所包含的異常處理表。
此過程將一直進行下去,直到找了一個可以處理異常的處理程序爲止。
假設CLR在整個方法調用堆棧的某一個方法中找到了可處理異常的catch語句,它就做好了執行此語句定義的異常處理代碼的準備。
掃描並查找相匹配的catch子句過程是CLR異常處理流程的第一輪。
當找到了合適的異常處理代碼後,CLR再回到原地,再次掃描引發異常方法的異常處理表,這次CLR關注的不再是catch子句,而是finally子句,判斷一下引發異常的IL指令代碼是否落入某個finally語句所監視的IL指令範圍之內,CLR據此查找合適的finally語句並執行。
掃描並查找相匹配的finally子句過程是CLR異常處理流程的第二輪。
第二輪的掃描,開始於引發異常的方法,結束於找到異常處理代碼的那一層的方法。然後開始執行異常處理代碼,最後執行上一層的finally語句塊,一直向上執行直到所有finally語句都執行結束。
經過兩輪的掃描之後,CLR就完成了對.NET應用程序引發異常的捕獲與處理工作。
但是,如果CLR並不是每次都能找到合適的異常處理代碼,如果.NET應用程序中沒有定義處理某種異常類型的代碼,而程序運行時又恰好出現此類型的異常,那麼CLR在第一輪掃描時一直上溯到Main方法所包含的異常處理表,然後無功而返。
緊接着CLR會進行第二輪的掃描,執行所有應該被執行的finally語句。在執行了完了finally語句後,CLR將控制權交給操作系統,由它強制中止此進程所創建的所有線程(即使線程本身運行正常),並顯示一個出錯的對話框後結束整個進程。