Chapter 5 - Item 49 : Prefer Exception Filters to catch and re-throw

Effective C# (Covers C# 6.0), (includes Content Update Program): 50 Specific Ways to Improve Your C#, 3rd Edition By Bill Wagner 讀後心得

try catch 區塊中的 catch,時常會呼叫 throw 重新擲出例外以處理無法回復的例外。重新擲出例外不僅增加運行階段支出成本,更重要是,重新擲出例外會讓喪失呼叫端的資訊。本節提出利用 when 條件式來處理例外,延遲例外的判斷時機;同時也保留呼叫端的資訊。

範例:

var retryCount = 0;
var dataString = default(string);

while (dataString == null)
{
	try
	{
		dataString = MakeWebRequest();
	}
	catch (TimeoutException ex) when (retryCount++ < 3)
	{
		Debug.WriteLine("Operation timed out. Trying again.");

		// 延遲一段時間並重試。
		Thread.Sleep(retryCount * 1000);
	}
}

這段程式碼會嘗試抓取 TimeoutException,當重試次數小於三次時自動延遲一段時間並重試。當不符合上述情況時,將繼續往上層找,直到找到能處理例外的程式碼為止;所有的運行階段資訊都被保留起來。

下面是重新擲出例外的寫法:

var retryCount = 0;
var dataString = default(string);

while (dataString == null)
{
	try
	{
		dataString = MakeWebRequest();
	}
	catch (TimeoutException ex) 
	{
		if( retryCount++< 3 )
		{
			Debug.WriteLine("Timed out. Trying again.");
			// 延遲一段時間並重試。
			Thread.Sleep(retryCount * 1000);
		}
		else
			throw;
	}
}

同樣的嘗試捕捉 TimeoutException,但擲出的例外 call stack 會從 throw 開始記錄(也就是 catch block);呼叫端的資訊已經遺失。

下面用簡單的例子說明擲出例外的資訊不同之處:

void TreeOfError()
{
	try
	{	        
		SingleBadThing();
	}
	catch (Exception ex)
	{
		// Reported on Call Stack.
		throw;
	}
}

void TreeOfError2()
{
	try
	{
		SingleBadThing(); // Reported on Call Stack.
	}
	catch (Exception ex) when (false)
	{
		Debug.WriteLine("Can't happen");
	}
}

void SingleBadThing()
{
	throw new NotImplementedException();
}

TreeOfError 在 Debug 時的 CallStack 最上層只記錄到 TreeOfError 方法,但實際上擲出例外的是 SingleBadThing 方法。try 區塊可能呼叫了不同方法,這時要再去追蹤擲出例外的方法為何就很困難;而 TreeOfError2 則保留了擲出例外方法的資訊,我們可以很輕易的確定出問題的方法是誰。

結論:
1. 利用 when 條件式保留 CallStack 資訊。
2. 避免重新擲出例外,以減少運行階段資源支出。