闭包与捕获变量
了解局部函数如何捕获状态。
闭包与捕获变量 是 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 反馈 — 无需本地设置。
此课程中的所有课时
- 声明局部函数
- 闭包与捕获变量
- 静态局部函数
- 局部函数与 Lambda