ローカル関数

ヘルパー ロジックにわかりやすい名前を付け、そのヘルパーが必要なメンバーは 1 つだけである場合は、ローカル関数を選択します。 含まれている型のメソッドと比較すると、ローカル関数はヘルパーがこの操作に属していることをリーダーに伝えます。 その名前を使用すると、閲覧者は詳細を調べる前に主な流れを把握することができます。

Tip

この記事では、メソッドの記述方法を知っている開発者向けのローカル関数の実用的な使用方法について説明します。 宣言の詳細と診断規則については、「 ローカル関数の宣言」を参照してください。

ローカル関数は、メソッドやコンストラクターなど、別のメンバー内で宣言された名前付き関数です。 その名前は、そのメンバー内でのみ有効です。

次の図は、ローカル関数を宣言して呼び出します。

string CreateMessage(string name)
{
    return AddGreeting(name);

    string AddGreeting(string value) => $"Hello, {value}!";
}

AddGreeting は CreateMessage内にとどまります。 型の他のメンバーはそれを呼び出すことはできません。

ヘルパーロジックを呼び出し元の近くに配置する

ローカル関数を使用すると、メインフローは何が起こるかを記述し、ヘルパー関数は個々のステップを説明します。 次の例では、読書プランを作成します。 ローカル関数を使用して、一度に 1 週間の割り当てを追加します。

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);
    }
}

ReadingWeekは、週番号とその週に割り当てられた章の範囲を記録します。 AddWeek は CreateReadingPlanにのみ属しているため、そのローカル スコープはその関係を示します。

ローカル関数は、それを呼び出すステートメントの前または後に宣言できます。 コンテキストを失うことなく、読者がメイン フローに従うことができる場所に配置します。

パラメーターを使用するか、ローカル状態をキャプチャする

非静的ローカル関数は、その包含メンバーのパラメーターとローカル変数を使用できます。 この用途はキャプチャと呼ばれます。 読み取り計画の例では、AddWeek が取得する内容は次のとおりです。

  • chaptersPerWeek パラメーターからのbookTitle、chapterCount、および CreateReadingPlan。
  • plan をローカル変数から。

キャプチャは、ヘルパーが自然に周囲の操作に属している場合に便利です。 取得する値は少なく、分かりやすいものにしてください。 ヘルパーがパラメーターを介してすべてのデータを受け取ることができる場合は、 static ローカル関数を検討してください。

static int LastChapter(int firstChapter, int chaptersPerWeek, int chapterCount) =>
    Math.Min(firstChapter + chaptersPerWeek - 1, chapterCount);

静的ローカル関数は、包含しているメンバーからローカル変数、パラメーター、またはインスタンスの状態をキャプチャできません。 そのパラメーターにより、指定されたデータが明示的になりますが、キャプチャではヘルパーがヘルパーを含む操作のコンテキストを使用できます。

問題が繰り返し現れる場合は、再帰を使用する

再帰は 、関数が問題の一部を小さくして自身を呼び出すときに発生します。 AddWeek この例では 1 週間を追加し、次の週の最初の項目で自分自身を呼び出します。

すべての再帰関数には、呼び出しを停止する条件が必要です。 AddWeekはfirstChapterがchapterCountを超えると停止します。

if (firstChapter > chapterCount)
{
    return;
}

ローカル関数は、その名前は包含メンバー全体でスコープ内にあるため、再帰に適しています。 繰り返しの作業をより直接的に表現する場合は、ループを優先します。 再帰構造によってアルゴリズムの理解が容易になる場合は、再帰を選択します。

ローカル関数またはラムダ式を選択する

ローカル関数とラムダ式はどちらも周囲の変数を使用できますが、異なる意図を伝えます。

Choose When
ローカル関数 ヘルパーは、名前を付ける利点がある場合、複数のステートメントがある場合、再帰が必要な場合、または包含メンバー内の複数の場所から呼び出される場合。
ラムダ式 短い処理を別のメソッドに直接渡すか、デリゲート変数に格納します。

次のコードでは、両方の形式を使用します。

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に渡されたラムダは、その使用時に 1 つの短い変換を実行します。 両方とも、ここで別のメソッドに振る舞いを提供します。 ローカル関数は、読み取り計画の例での AddWeek のように、直接呼び出すヘルパーにすることもできます。

複数のメンバーがヘルパーを必要とする場合は、ローカル関数の代わりに通常のメソッドを使用します。 動作が短く、その目的が呼び出し先のメソッドから明確な場合は、代わりにラムダを使用します。

ローカル関数は、yield returnを含む反復子関数にすることもできます。 その用途については、「 反復子」を参照してください。

参照