這個做法說明了 Exception Filters 與 when 子句的使用方式,還有與舊的判斷語句寫法進行比較。

例如這段程式碼會嘗試發送網路請求,並且捕捉 TimeoutException 但如果今天想要先判斷程式運行的狀態就必須透過在 catch 區塊中添加判斷語句, 進行分析,所以這段程式會不斷運行迴圈直到 retryCount 到達三個時才放棄請求,最後把錯誤進行拋出。

var retryCount = 0;
var dataString = default(String);
while (dataString == null)
{
	try
	{
		dataString = MakeWebRequest();
	}
	catch (TimeoutException e)
	{
		if (retryCount++ < 3)
		{
			WriteLine("Timed out. Trying again");
			// pause before trying again.
			Task.Delay(1000 * retryCount);
		}
		else
			throw;
	}
}

這裡要特別注意 else 裡面的 throw 這個寫法是正確的,它會向外拋出原始錯誤並保留原始的 stack trace,不要寫成 throw e 這種寫法 雖然會拋出拋出原始錯誤但會把 stack trace 清除導致資訊丟失,更不要寫成 throw new Exception 這會建立新的 Exception;

接下來可以使用 when 子句達到同樣的效果並且更容易閱讀,注意到程式會先判斷 when 的條件是否達成才會執行內部邏輯, 並且內部並沒有寫到 throw 將錯誤進行拋出,這是因為它找不到符合的 catch 內部會往 call stack 上層進行尋找。

var retryCount = 0;
var dataString = default(String);
while (dataString == null)
{
	try
	{
		dataString = MakeWebRequest();
	}
	catch (TimeoutException e) when (retryCount++ < 3)
	{
		WriteLine("Operation timed out. Trying again");
		// pause before trying again.
		Task.Delay(1000 * retryCount);
	}
}

另外實際上的錯誤發生地點也會有些微差異,例如第一種寫法會在報告中看到發生地點是在 throw 那行造成的,但如果改用 Exception Filters 的寫法則會在報告中看到發生地點是在 SingleBadThing 也就是實際發生問題的地點,這個微小差距在大型的程式中帶來非常有用的幫助, 並且 .NET CLR 會對有 when 子句的 try/catch 區塊進行優化並且提升運作的效率。

static void TreeOfErrors()
{
	try
	{
		SingleBadThing();
	}
	catch (RecoverableException e)
	{
		throw; // reported on Call Stack
	}
}
static void TreeOfErrorsTwo()
{
	try
	{
		SingleBadThing(); // reported on Call Stack
	}
	catch (RecoverableException e) when (false)
	{
		WriteLine("Can't happen");
	}
}

也就是說我們應該徹底改變之前的使用習慣,改成用 Exception Filters 是更好的選擇,例如說 HTTPException 它的 GetHttpCode 方法 會回傳 Http Response Code 表示請求的結果,像是可以在 when 子句判斷 code 是否為 404 之後在進行相對應的處理。


Summary

使用 Exception Filters 能夠把原本傳統判斷寫法大幅省略,只需要留下 catch 與 when 進行檢查,所以跟傳統的拋出處理方式這樣做 又可以把原始的錯誤訊息保留下來也可能讓程式運作得更快一點。