區域函式

當你想給輔助邏輯一個描述性名稱,且只有一個成員需要該輔助時,請選擇一個本地函式。 與包含型別的方法相比,本地函式會告訴讀者該輔助器屬於此操作。 其名稱讓讀者能跟上主體流程,再檢視細節。

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 的迭代函式。 關於此用途,請參見 迭代器。

也請參閱