c# throw exception

正確重拋例外 (Exception) 的方式

陳志澤 2018/10/17 15:04:33
2253

正確重拋例外 (Exception) 的方式


簡介

本文說明如何正確重拋例外 (Exception),避免重要資訊被錯誤的重拋方式給隱藏。

作者

陳志澤


 
前言

在 Review 專案程式碼時,常會發現有把 Try-Catch 捕捉到之 Exception 直接拋出的狀況 (如左下圖所示),其實這樣是非常不恰當的做法;較好的方式是使用 throw 重拋例外就好了 (如右下圖所示)。這兩者到底由何差異,以下將就簡單實例來進行探討。
 

 
 
差異比較

當例外被在捕捉時,可從 Exception.StackTrace 屬性中來獲得呼叫堆疊資訊,進而了解錯誤發生的確切位置。以下將就該資訊的完整度探討兩者之差異。
 
首先,先建立一個呼叫的推疊類別做測試。當呼叫 Aoo.DoSomethingInBoo() 方法時,會依序向 Boo, Coo, Doo 的 DoSomethingInXoo() 方法執行,且最後在 Doo.DoSomethingInDoo() 中故意造成錯誤,再交由 Boo.DoSomethingInBoo() 捕捉此例外錯誤,並以 throw ex 或 throw 拋出例外至呼叫端中來比較差異。
 


呼叫端測試主程式如下,捕捉到例外錯誤後印出 StackTrace 資訊於畫面上。



 
使用 throw ex 拋出

如下圖所示,先使用 throw ex 來拋出例外,執行看看。

 

當我們使用 throw ex 時,會重置呼叫堆疊,造成確切例外發生位置的遺失。如下圖所示,錯誤發生點明明就在 Doo.DoSomethingInDoo() 中,而 StackTrace 提供的資料卻只有到 Boo.DoSomethingInBoo(),因此單就此訊息無法得知實際錯誤到底是在哪個類別方法中發生的,實在是會造成很大的困擾。
 



使用 throw 拋出

接著使用 throw 來拋出例外,執行看看。
 


使用 throw 拋出錯誤時,並不會重置呼叫堆疊,保有完整 StackTrace 資訊,方便獲得例外發生點來進行除錯。如下圖所示,明確地指出錯誤發生點就在 Doo.DoSomethingInDoo() 中,獲得完整呼叫堆疊資訊。

 
 
 
測試代碼
 
以下是完整的測試代碼,有興趣的朋友可以自行測試
 
class Program
{
    static void Main(string[] args)
    {
         try
        {
            Aoo aoo = new Aoo();
            aoo.DoSomethingInAoo();
        }
        catch (Exception ex)
        {
            // display stack trace info
            Console.WriteLine( "----- stack info -----" );
            Console.WriteLine( ex.StackTrace.ToString() );
            Console.WriteLine( "----------------------" );
        }

        Console.Read();
    }
}

// level 1
public class Aoo
{
    public Boo boo = new Boo();
    public void DoSomethingInAoo()
    { boo.DoSomethingInBoo(); }
}

// level 2
public class Boo
{
    public Coo coo = new Coo();
    public void DoSomethingInBoo()
    {
        try
        {
            coo.DoSomethingInCoo();
        }
        catch (Exception ex)
        {
            // log here
            // ...

            // ## Rethrow exception ##
            // destroys the strack trace info!
            //throw ex;

            // ## Rethrow exception ##
            // preserves the stack trace
            throw;
        }
    }
}

// level 3
public class Coo
{
    public Doo doo = new Doo();
    public void DoSomethingInCoo()
    {  doo.DoSomethingInDoo(); }
}

// level 4
public class Doo
{
    public void DoSomethingInDoo()
    {
        int i = 0;
        i = 1 / i;  // cause exception
    }
}
 
 
陳志澤