0Pricing
C# Academy · 课时

闭包与捕获变量

了解局部函数如何捕获状态。

闭包与捕获变量 是 CoddyKit 上的免费 C# Academy 课时。 这是第 2 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 C# Academy 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 C# Academy 课程共包含 4 节课。

捕获局部状态

局部函数(或匿名函数)可以读取和修改其外层方法中的变量。这称为捕获,而函数与它捕获的变量共同构成闭包。

捕获变量

局部函数直接使用外层方法中的 factor。它不会将其作为参数接收,而是从周围的作用域中捕获它。

using System;

public class Program
{
    public static void Main()
    {
        int factor = 3;
        int Scale(int n) => n * factor; // captures factor

        Console.WriteLine(Scale(5));
        Console.WriteLine(Scale(10));
    }
}

捕获的变量是实时引用

闭包捕获的是变量,而不是变量值的快照。如果函数定义后变量发生变化,函数看到的就是新值。

using System;

public class Program
{
    public static void Main()
    {
        int offset = 10;
        int Add(int n) => n + offset;

        Console.WriteLine(Add(1)); // 11
        offset = 100;
        Console.WriteLine(Add(1)); // 101 - sees updated offset
    }
}

修改捕获的变量

闭包可以写入捕获的变量,而更改在外层方法中同样可见。因此,闭包适合用于累积状态。

using System;

public class Program
{
    public static void Main()
    {
        int count = 0;
        void Tick() => count++;

        Tick(); Tick(); Tick();
        Console.WriteLine(count); // 3
    }
}

返回闭包

当您返回一个捕获了局部变量的匿名函数时,这些变量的存活时间会超过该方法。捕获的状态会与委托一样长久存在。

using System;

public class Program
{
    static Func<int> MakeCounter()
    {
        int count = 0;
        return () => ++count; // captures count
    }

    public static void Main()
    {
        var next = MakeCounter();
        Console.WriteLine(next());
        Console.WriteLine(next());
        Console.WriteLine(next());
    }
}

相互独立的闭包

工厂的每次调用都会创建一组全新的捕获变量。用这种方式构建的两个计数器不会相互干扰。

using System;

public class Program
{
    static Func<int> MakeCounter()
    {
        int count = 0;
        return () => ++count;
    }

    public static void Main()
    {
        var a = MakeCounter();
        var b = MakeCounter();
        Console.WriteLine(a()); // 1
        Console.WriteLine(a()); // 2
        Console.WriteLine(b()); // 1 - separate state
    }
}

修复遍历循环的捕获问题

在现代 C# 中,遍历循环的循环变量会在每次迭代时创建新的变量,因此闭包可以捕获预期的值。下面的每个匿名函数都会记住自己的项目。

using System;
using System.Collections.Generic;

public class Program
{
    public static void Main()
    {
        var actions = new List<Action>();
        foreach (var i in new[] { 1, 2, 3 })
            actions.Add(() => Console.WriteLine(i));

        foreach (var act in actions) act(); // prints 1, 2, 3
    }
}

在计数循环中捕获变量

一个经典陷阱是:单个 for 循环变量会在各次迭代之间共享,因此所有闭包看到的都是它的最终值。请将它复制到每次迭代独有的局部变量中,以捕获不同的值。

using System;
using System.Collections.Generic;

public class Program
{
    public static void Main()
    {
        var actions = new List<Action>();
        for (int i = 0; i < 3; i++)
        {
            int copy = i; // fresh local each iteration
            actions.Add(() => Console.WriteLine(copy));
        }
        foreach (var act in actions) act(); // prints 0, 1, 2
    }
}

闭包按引用而非按值捕获

由于捕获采用按引用的方式,捕获后对原变量的修改会被闭包观察到。当读取和写入的时机很重要时,请牢记这一点。

using System;

public class Program
{
    public static void Main()
    {
        string message = "first";
        Action print = () => Console.WriteLine(message);

        print();          // first
        message = "second";
        print();          // second
    }
}

实际应用:可配置的行为

闭包可以构建预先加载了上下文的小型函数。这里的折扣函数捕获一个折扣率,从而生成一个可复用的、已配置好的计算器。

using System;

public class Program
{
    static Func<double, double> MakeDiscount(double rate)
        => price => price * (1 - rate);

    public static void Main()
    {
        var tenOff = MakeDiscount(0.10);
        var halfOff = MakeDiscount(0.50);
        Console.WriteLine(tenOff(100));
        Console.WriteLine(halfOff(100));
    }
}

动手实践

构建一个计数器工厂,让它返回的闭包保持私有状态,然后根据它创建两个相互独立的计数器。

using System;

public class Program
{
    static Func<int> MakeStepper(int step)
    {
        int current = 0;
        return () => { current += step; return current; }; // captures current and step
    }

    public static void Main()
    {
        var byTwos = MakeStepper(2);
        var byTens = MakeStepper(10);
        Console.WriteLine(byTwos()); // 2
        Console.WriteLine(byTwos()); // 4
        Console.WriteLine(byTens()); // 10 - separate state
    }
}

快速检查

推理捕获的工作方式。

回顾

闭包按引用捕获外层变量。

  • 函数看到的是变量的当前值,包括之后发生的更改。
  • 它可以修改捕获的变量,与方法共享状态。
  • 返回的闭包会让其捕获的变量保持存活。
  • 在 for 循环中使用每次迭代独有的局部变量,以捕获不同的值。

常见问题解答

「闭包与捕获变量」课时是免费的吗?

是的 — 「闭包与捕获变量」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 C# Academy 课程的其余内容,请升级到 CoddyKit PRO。 C# Academy 课程共包含 4 节课。

「闭包与捕获变量」这节课中我会学到什么?

了解局部函数如何捕获状态。 你通过在浏览器中直接运行的动手代码来练习 C# Academy,全天候 AI 导师会在你学习这节课的过程中回答你的问题。

学习 C# Academy 需要有经验吗?

无需任何先前经验。CoddyKit 上的 C# Academy 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 2 节课,共 4 节。

「闭包与捕获变量」课时需要多长时间?

大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。

我能在这节 C# Academy 课中编写并运行代码吗?

能。每节 C# Academy 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。

此课程中的所有课时

  1. 声明局部函数
  2. 闭包与捕获变量
  3. 静态局部函数
  4. 局部函数与 Lambda
← 返回 C# Academy