當你想給輔助邏輯一個描述性名稱,且只有一個成員需要該輔助時,請選擇一個本地函式。 與包含型別的方法相比,本地函式會告訴讀者該輔助器屬於此操作。 其名稱讓讀者能跟上主體流程,再檢視細節。
Tip
本文教導懂得如何撰寫方法的開發者實際使用局部函數。 關於宣告詳細資料與診斷規則,請參見 本地函數宣告。
局部函式是指在其他成員中宣告的命名函式,例如方法或建構子。 其名稱僅在該成員的作用域內。
以下圖形宣告並呼叫一個區域函式:
string CreateMessage(string name)
{
return AddGreeting(name);
string AddGreeting(string value) => $"Hello, {value}!";
}
AddGreeting 待在 CreateMessage 裡面。 其他同類型的成員無法呼叫它。
將輔助邏輯放在呼叫者附近
局部函式讓主流程說明會發生什麼,而輔助函式則解釋個別步驟。 以下範例建立閱讀計畫。 它使用本地函式,一次新增一週的作業。
IReadOnlyList<ReadingWeek> plan =
CreateReadingPlan("The Long Way Home", chapterCount: 10, chaptersPerWeek: 3);
foreach (ReadingWeek week in plan)
{
Console.WriteLine(
$"Week {week.Number}: {week.BookTitle}, chapters {week.FirstChapter}-{week.LastChapter}");
}
static IReadOnlyList<ReadingWeek> CreateReadingPlan(
string bookTitle,
int chapterCount,
int chaptersPerWeek)
{
var plan = new List<ReadingWeek>();
AddWeek(firstChapter: 1);
return plan;
void AddWeek(int firstChapter)
{
if (firstChapter > chapterCount)
{
return;
}
int lastChapter = Math.Min(
firstChapter + chaptersPerWeek - 1,
chapterCount);
plan.Add(new ReadingWeek(
plan.Count + 1,
bookTitle,
firstChapter,
lastChapter));
AddWeek(lastChapter + 1);
}
}
A ReadingWeek 會記錄週次及該週分配的章節範圍。
AddWeek 只屬於 CreateReadingPlan,因此其局部範圍傳達了這種關係。
你可以在呼叫它的語句之前或之後宣告一個本地函式。 將它放在讀者能跟上主線流程且不失去上下文的地方。
使用參數或捕捉本機狀態
非靜態的本地函式可以使用其所屬成員中的參數和區域變數。 這種用法稱為 捕獲。 在閱讀計畫的例子中,AddWeek 擷取:
-
bookTitle、chapterCount和chaptersPerWeek,來自CreateReadingPlan參數。 -
plan來自一個局部變數。
當輔助者自然屬於周遭作業時,Capture 非常有用。 保持擷取的值精簡且明確。 如果輔助器能透過參數接收所有資料,請考慮一個 static 本地函數:
static int LastChapter(int firstChapter, int chaptersPerWeek, int chapterCount) =>
Math.Min(firstChapter + chaptersPerWeek - 1, chapterCount);
靜態的本地函式無法從包含成員中捕捉本地變數、參數或實例狀態。 其參數使所提供的資料明確化,而 capture 則讓輔助工具能利用其所在操作的上下文。
當問題重複出現時,請使用遞迴
遞迴發生在函數以問題的較小部分呼叫自身時。
AddWeek 在範例中,加了一週,然後以下一週的第一章來呼叫自己。
每個遞迴函數都需要一個條件來停止呼叫。
firstChapter 超過 chapterCount 時,AddWeek 停止:
if (firstChapter > chapterCount)
{
return;
}
局部函數適合遞迴,因為它們的名稱在整個包含成員內皆在作用域中。 偏好循環,因為它能更直接表達重複的工作。 當遞迴結構使演算法更容易理解時,選擇遞迴。
選擇局部函數或 lambda 表達式
局部函數與 lambda 表達式都可以使用周圍變數,但它們傳達不同的意圖:
| 選擇 | When |
|---|---|
| 本地功能 | 這個 helper 值得命名、包含多個陳述式、需要遞迴,或是從其所屬成員中的多個位置被呼叫。 |
| Lambda 運算式 | 你可以直接將簡短的行為傳給另一個方法,或是把它存進委派變數裡。 |
以下代碼同時使用兩種形式:
static IEnumerable<string> DescribeShortWeeks(
IEnumerable<ReadingWeek> plan,
int maximumChapters)
{
return plan
.Where(IsShortWeek)
.Select(week => $"Week {week.Number}: {week.FirstChapter}-{week.LastChapter}");
bool IsShortWeek(ReadingWeek week) =>
week.LastChapter - week.FirstChapter + 1 <= maximumChapters;
}
Console.WriteLine("Short weeks:");
foreach (string description in DescribeShortWeeks(plan, maximumChapters: 2))
{
Console.WriteLine(description);
}
IsShortWeek 為傳遞給 Where 的規則指定描述性名稱。 傳遞給 Select 的 lambda 在其使用處執行一次簡短的轉換。 這兩種形式都為另一個方法提供了行為。 本地函式也可以是你直接呼叫的輔助器,就像在 reading-plan 範例中,AddWeek 就是如此。
當多個成員需要輔助函式時,使用一般方法而非本地函式。 當行為簡短且目的明確於接收方法時,則可改用 lambda。
本機函式也可以是包含 yield return 的迭代函式。 關於此用途,請參見 迭代器。