首页 / C# 入门教程 / 局部函数

C# 入门教程

局部函数

本教程共 100 篇 · 第 52 篇 · 更新于 2026-07-31 · 约 8 分钟阅读

C#C# 入门教程编程语言局部函数闭包

52. 局部函数

本节目标:学完你能在方法内部定义小函数,理解它如何捕获外部变量,并知道它和 Lambda 各自适合什么场合。

有时一个方法里,某段逻辑只想在该方法内复用,不想污染整个类。C# 允许你在方法体里再定义一个函数,这就是局部函数(local function)。它像方法里的「私房助手」:只帮当前方法干活,别处看不见、也调不到。

把局部函数想象成「厨房里的专用小工具」:做这道菜时你临时削个专门的刨子,用完就收进这道菜的流程里,不会摆到整个厨房的公共工具架上占地方。

一、在方法里定义函数

局部函数写在某个方法内部,只能在这个方法里被调用:

namespace CSharpDemo;

int Factorial(int n)
{
    int Calc(int x) => x <= 1 ? 1 : x * Calc(x - 1);
    return Calc(n);
}

Console.WriteLine(Factorial(5)); // 120

Calc 只在 Factorial 内部可见。它还能直接递归调用自己,这点比 Lambda 写递归自然得多。

为什么需要它

如果只是临时算一步,写个 Calc 比抽成类的公共方法更克制。公共方法属于整个类,局部函数只服务于当前方法,职责边界更清楚,也不会被别处误调用。当你发现某段逻辑只在此处用、又值得单独命名时,局部函数正合适。

适用场景

局部函数特别适合:方法内部需要多次复用的小步骤;需要递归但只想在这个方法里跑的逻辑;想把一段复杂判断「命名出来」让主流程更清爽。它让方法内部也保持「小函数各管一摊」的干净结构。

二、捕获外部变量(闭包)

局部函数能直接用外层方法的变量,就像嵌套的 scope:

namespace CSharpDemo;

void Counter()
{
    int count = 0;
    void Increment() => count++;
    Increment();
    Increment();
    Console.WriteLine(count); // 2
}

Counter();

Increment 修改了外层 count,这种「函数记住并改写外层变量」的能力叫闭包(closure)。它在 C# 里按引用捕获,所以改动对外层可见。

Note

普通 Lambda 也能捕获外部变量,但局部函数捕获的开销通常更小,而且支持 ref 返回等 Lambda 做不到的事。

Warning

闭包捕获的是「变量本身」而非「捕获那一刻的值」。如果你在循环里让局部函数捕获循环变量,多个调用可能共享同一个变量,最后读到的都是循环结束时的值。好在 foreach 从 C# 5 起每次迭代是独立变量,基本不用担心;但 for 循环里的 i 仍是同一个变量,写闭包要留心,必要时在循环内把值复制到一个局部变量再捕获。

三、static 局部函数:明确不捕获

C# 8 起可以给局部函数加 static,声明它不捕获任何外层变量:

namespace CSharpDemo;

int Add(int a, int b)
{
    static int Sum(int x, int y) => x + y;
    return Sum(a, b);
}

Console.WriteLine(Add(3, 4)); // 7

static 局部函数不能访问外层的局部变量或实例成员,只能用品类自己的参数和静态成员。编译器会帮你盯着这一点。

Tip

只要局部函数不需要外层变量,就加上 static。这既是给编译器的提示,也是给读者的声明:这个函数自给自足、没有隐藏依赖。

四、局部函数能当迭代器

局部函数可以是带 yield return 的迭代器,这是 Lambda 做不到的:

namespace CSharpDemo;

IEnumerable<int> Evens(int max)
{
    IEnumerable<int> Gen()
    {
        for (int i = 0; i <= max; i += 2)
            yield return i;
    }
    return Gen();
}

foreach (var n in Evens(8))
    Console.WriteLine(n); // 0 2 4 6 8

五、和 Lambda 表达式怎么选

两者都能封装一段逻辑,但侧重点不同。先看一段等价的 Lambda:

namespace CSharpDemo;

int FactorialLambda(int n)
{
    Func<int, int> calc = null!;
    calc = x => x <= 1 ? 1 : x * calc(x - 1);
    return calc(n);
}

写法别扭,而且每次都要先建一个委托对象,有堆分配。

场景用局部函数用 Lambda
方法内部的小助手适合也行
递归自然别扭
需要 ref 返回可以不行
当委托传给别的方法(如 Where不合适适合
想零分配、强类型适合会分配委托

关键差别

局部函数是真正的方法,没有委托分配,类型完全由签名推断,还能在声明之前被调用(只要在方法体内)。Lambda 会被转换成委托(或表达式树),有对象分配,且要先定义后使用。

Note

默认风格(IDE0039)建议:如果只是方法内部的辅助逻辑、不需要当委托传出去,优先用局部函数而不是 Lambda。

何时用哪个

一句话:逻辑「留在本方法内自己用、要递归、要零分配」→ 局部函数;逻辑「要作为委托传给别的 API(如 LINQ 的 Where/Select)」→ Lambda。两者不是对手,而是搭档:局部函数管内部整洁,Lambda 管对外传参。

六、新手踩坑提醒

第一个坑:误以为 static 局部函数能访问实例字段。加了 static 就断了和外层的联系,想访问 this 或局部变量都会报错。

第二个坑:在循环里用局部函数捕获循环变量。和 Lambda 一样,捕获的是变量本身而非当时的值。详见上面「闭包」里的警告。

第三个坑:局部函数定义在 return 之后却仍能调用。这是允许的——局部函数属于整个方法体,不受书写位置限制。别被「上面已经 return 了」迷惑。

七、最佳实践

  • 只在本方法用的小逻辑,优先抽成局部函数,别污染类的公共接口。
  • 不需要外层变量时,果断加 static,既省分配又表意清晰。
  • 需要当委托传出去(LINQ、回调)时,用 Lambda,别硬套局部函数。
  • 递归优先用局部函数,比 Lambda 的「先 null 后赋值」写法干净。
  • 闭包捕获循环变量时,先复制到局部变量,避免共享同一变量。

八、局部函数与私有方法的取舍

有人会问:既然局部函数只在方法内用,为什么不干脆提成类的私有方法(private)?两者都能复用逻辑,差别在「可见范围」和「能不能捕获」。普通私有方法对整个类可见,也能被别的成员调用;局部函数只属于某个方法,且能直接抓外层的局部变量。

经验:逻辑「只服务于这唯一一处、且要用到外层局部变量」→ 局部函数;逻辑「别的成员也可能用、或想单独测试」→ 提成私有方法。别因为局部函数方便,就把本该是类级复用的逻辑塞进某个方法里,那反而限制了复用。

九、一句话总结

局部函数是「方法里的方法」:强类型、可递归、可迭代、可零分配,还能捕获外层变量。把它当私有小助手用,和 Lambda 搭配,代码既干净又高效。