這個做法討論 Local Functions
的應用大部分的內容在 Effective C# 29 有稍微提到過,主要就是 C# 有一些延遲執行特性的方法,當這個延遲特性與一些需要馬上回報的功能會產生衝突,
例如要立即檢查傳入參數,發現錯誤後馬上拋出 ArgumentException
,如果這段檢查邏輯是包含在距由延遲特性的方法裡面,那麼檢查邏輯就會被延後到呼叫發法時才會檢查。
比較常用具有延遲特性的方法有 Iterators 與 Async 方法,下面就是常見的 Iterators 方法,雖然有包含參數檢查邏輯但實際上要等到最後一行
foreach
讀取資料的時候才會拋出錯誤。
public IEnumerable<T> GenerateSample<T>(IEnumerable<T> sequence, int sampleFrequency)
{
if (sequence == null)
throw new ArgumentException("Source sequence cannot be null.");
if (sampleFrequency < 1)
throw new ArgumentException("Sample frequency must be positive.");
int index = 0;
foreach (var item in sequence)
{
if (index % sampleFrequency == 0)
yield return item;
}
}
// 錯誤未即時拋出,要等到真正讀取資料才會拋出。
var samples = GenerateSample(fullSequence, -1);
foreach (var item in samples)
{
Console.WriteLine(item);
}
同樣的問題也會發生在 async 方法上,這裡的 LoadMessage 方法經由編譯器處理過後會回傳一個 Task
物件用來管理狀態和非同步工作,
必須等到 await Task
發生時才會真正執行 LoadMessage 方法。
async void Main()
{
// 呼叫方法
var task = LoadMessage(null);
Console.WriteLine("Not Throw");
await task;
}
public async Task<string> LoadMessage(string userName)
{
if (string.IsNullOrWhiteSpace(userName))
throw new ArgumentException("Invalid username.");
return userName ?? "No message.";
}
解決的方法也很簡單,就是把驗證的邏輯拆分成另一個方法,讓它不再具備延遲特性。
這裡使用的是 Local Functions
寫法,能夠把一個方法包裝在另一個方法裡面,這個寫法的好處就是避免被其他方法給呼叫,
因為 generateSampleImpl
是 private 的,所以只有 GenerateSample
才有能力呼叫它。
同時也代表你沒辦法跳過檢查邏輯直接執行 generateSampleImpl
方法,如果不是用 Local Functions
寫法就有可能被不知情的使用者跳過檢查邏輯。
public IEnumerable<T> GenerateSample<T>(IEnumerable<T> sequence, int sampleFrequency)
{
if (sequence == null)
throw new ArgumentException("Source sequence cannot be null.");
if (sampleFrequency < 1)
throw new ArgumentException("Sample frequency must be positive.");
return generateSampleImpl();
IEnumerable<T> generateSampleImpl()
{
int index = 0;
foreach (var item in sequence)
{
if (index % sampleFrequency == 0)
yield return item;
}
}
}
同樣的寫法也可以套用在 async 方法上,注意到 async 是寫在 Local Functions
上,這樣外層可以做到馬上執行。
public Task<string> LoadMessage(string userName)
{
if (string.IsNullOrWhiteSpace(userName))
throw new ArgumentException("Invalid username.");
return loadMessageImpl();
async Task<string> loadMessageImpl()
{
return userName ?? "No message.";
}
}
Summary
這個做法把 Effective C# 29 提到的延遲驗證的問題複習了一下,並且改成用 Local Functions
的寫法,來避免不知情的人略過驗證邏輯
直接執行主要方法,而且 Local Functions
也提供了封裝性,避免方法被外部的人呼叫。