首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> .NET 網頁程式設計 >> 關於.NET

告別 Try/Catch:以 EventHandlerWrapper 實現優雅的異常處理

     編輯:關於.NET

異常處理的困境

對於 .NET 開發人員而言,異常處理既熟悉又棘手。熟悉是因為語法僅止於 try/catch;棘手則在於:try/catch 該放在哪?何時記錄日誌?何時封裝、替換或重新拋出異常?這些問題往往難以給出一致答案。事實上,合理的異常處理應由場景驅動,策略需可配置、動態維護。企業庫 的 Exception Handling Application Block (EHAB) 便提供了這類場景驅動、可配置的異常處理框架。

透明化異常處理:理想與現實

在分散式架構中,業務邏輯與資料存取集中於服務端,透過 WCF 暴露。伺服器端透過 WCF 與 EHAB 整合,開發人員無需撰寫任何 try/catch 代碼。然而客戶端(Smart Client)的 UI 事件處於呼叫堆疊最頂層,難以用 AOP 動態注入異常處理,導致大量重複代碼:

private void buttonCalculate_Click(object sender, EventArgs e)
{
    try
    {
        // 業務邏輯
    }
    catch (Exception ex)
    {
        if (ExceptionPolicy.HandleException(ex, "policyName"))
        {
            throw;
        }
    }
}

嘗試:公共方法復用

首個直覺是將重複邏輯封裝為公共方法:

public void Invoke(Action action)
{
    try
    {
        action();
    }
    catch (Exception ex)
    {
        if (ExceptionPolicy.HandleException(ex, "data access policy"))
        {
            throw;
        }
    }
}

雖然減少行數,但每個事件仍需呼叫 Invoke,重複問題依然存在。

核心方案:封裝 EventHandler

既然目的是在事件處理器外層套上 try/catch,何不直接封裝 EventHandler 本身?EventHandler 本質是委派,包含 MethodInfo 與 Target。我們可建立 EventHandlerWrapper,在其 Invoke 方法中統一處理異常:

public class EventHandlerWrapper
{
    public object Target { get; private set; }
    public MethodInfo Method { get; private set; }
    public EventHandler Handler { get; private set; }

    public EventHandlerWrapper(EventHandler eventHandler)
    {
        if (eventHandler == null) throw new ArgumentNullException(nameof(eventHandler));
        Target = eventHandler.Target;
        Method = eventHandler.Method;
        Handler += Invoke;
    }

    public static implicit operator EventHandler(EventHandlerWrapper wrapper)
    {
        return wrapper.Handler;
    }

    private void Invoke(object sender, EventArgs args)
    {
        try
        {
            Method.Invoke(Target, new object[] { sender, args });
        }
        catch (TargetInvocationException ex)
        {
            // 記錄事件日誌、彈出訊息框等統一處理
            var msg = new StringBuilder();
            msg.AppendLine($"Message: {ex.InnerException.Message}");
            msg.AppendLine($"Exception Type: {ex.InnerException.GetType().AssemblyQualifiedName}");
            msg.AppendLine($"Stack Trace: {ex.InnerException.StackTrace}");
            EventLog.WriteEntry("Application", msg.ToString());
            MessageBox.Show(ex.InnerException.Message + Environment.NewLine + "詳細資訊請查看事件日誌", "錯誤", MessageBoxButtons.OK, MessageBoxIcon.Error);
        }
    }
}

註冊事件時僅需:

this.buttonCalculate.Click += new EventHandlerWrapper(buttonCalculate_Click);

原始事件處理器保持純淨,無任何異常處理代碼:

private void buttonCalculate_Click(object sender, EventArgs e)
{
    int op1 = int.Parse(textBoxOp1.Text);
    int op2 = int.Parse(textBoxOp2.Text);
    int result = op1 / op2;
    textBoxResult.Text = result.ToString();
}

計算器介面 異常記錄於事件日誌 訊息框顯示錯誤

延伸應用:等待游標與記憶體洩漏防護

EventHandlerWrapper 模式不限於異常處理。稍作修改即可實現「點擊按鈕自動切換等待游標」:

private void Invoke(object sender, EventArgs args)
{
    if (Form.ActiveForm != null)
        Form.ActiveForm.Cursor = Cursors.WaitCursor;
    try
    {
        Method.Invoke(Target, new object[] { sender, args });
    }
    finally
    {
        if (Form.ActiveForm != null)
            Form.ActiveForm.Cursor = Cursors.Default;
    }
}

同樣原理亦可解決事件註冊導致的記憶體洩漏(參考筆者早期文章)。封裝委派,讓橫切關注點(異常、UI 狀態、生命週期)集中管理,業務代碼回歸本質——這才是「無 Try/Catch 程式設計」的精髓。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved