本地函数

如果要为帮助程序逻辑提供描述性名称,并且只有一个成员需要该帮助程序,请选择本地函数。 与包含类型上的方法相比,本地函数告知读者该辅助函数是此操作的一部分。 它的名称让读者在查看详细信息之前先跟随主流程。

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

ReadingWeek 记录该周分配的周数和章节范围。 AddWeek 仅属于 CreateReadingPlan,因此其局部作用域表明了这种关系。

可以在调用它的语句之前或之后声明本地函数。 将它置于读者可以遵循主要流程而不丢失上下文的位置。

使用参数或捕获本地状态

非静态本地函数可以使用其包含成员中的参数和局部变量。 此用法称为 捕获。 在阅读计划示例中,AddWeek包括:

  • 来自 CreateReadingPlan 参数的 bookTitle、chapterCount 和 chaptersPerWeek。
  • plan 来自局部变量。

当辅助函数自然属于周围操作时,捕获非常有用。 将捕获的值保持为少量且明确 如果帮助程序可以通过参数接收其所有数据,请考虑使用 static 本地函数:

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

静态局部函数无法从包含成员捕获局部变量、参数或实例状态。 其参数使提供的数据显式,而捕获允许辅助函数使用其所在操作的上下文。

当问题具有重复性时使用递归

递归发生在函数以该问题的较小部分作为输入调用自身时。 AddWeek 在示例中,增加一周,然后传入下一周的第一章作为参数再次调用自身。

每个递归函数都需要一个能终止递归调用的条件。 chapterCount超过firstChapter时,AddWeek停止:

if (firstChapter > chapterCount)
{
    return;
}

本地函数适用于递归,因为其名称在整个包含成员中都处于作用域内。 当循环更直接地表达重复工作时,首选循环。 当递归结构使算法更易于理解时选择递归。

选择本地函数或 lambda 表达式

本地函数和 lambda 表达式都可以使用周围的变量,但它们传达不同的意向:

选择 什么时候
本地函数 帮助程序适合命名、具有多个语句、需要递归,或从包含成员中的多个位置调用。
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 在其使用点执行一个短转换。 这两种形式都在此处为另一个方法提供行为。 本地函数也可以是直接调用的帮助程序,就像阅读计划示例中的 AddWeek 一样。

当多个成员都需要该辅助方法时,请使用常规方法而不是本地函数。 当行为较短且其用途从接收方法中清晰可见时,请改用 lambda。

本地函数也可以是包含 yield return的迭代器函数。 有关该用途,请参阅 迭代器。

另请参阅