對於 .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,重複問題依然存在。
既然目的是在事件處理器外層套上 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 程式設計」的精髓。