与买桂花同载酒 🌙

闭包传值

name 这个变量明明只是 Subscribe 方法的一个临时参数,当 Subscribe 方法执行完毕后,按理说 name 就应该在内存里被销毁了。但是!当传感器在未来的某个时刻触发报警时,那个 Lambda 表达式居然还能准确地喊出 name 的名字!

当你写下这段代码时,C# 编译器在底层悄悄为你做了一件极其复杂的事:

它发现你的 Lambda 表达式里用到了外面的局部变量 name。

编译器会在内存里偷偷新建一个隐藏的类。

它把 name 放进了这个隐藏类的“背包”里。

哪怕 Subscribe 方法早就执行完了,这个“背包”依然被 Lambda 表达式死死抓在手里,直到程序结束。

所以,你利用了“环境捕获(闭包)”,完美地绕过了 EventHandler 的参数限制,把自定义的数据(name)带进了执行逻辑里。这在实际开发中极其常用!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
using System;
using System.Data.SqlClient;

namespace ConsoleApp1
{
// 请在此处完成 Sensor 类
class Sensor
{
public int _currentTemperature;
public int CurrentTemperature
{
get=>_currentTemperature;
set
{
_currentTemperature = value;
TemperatureChanged?.Invoke(this, _currentTemperature);
}
}
// ... 定义事件,并在温度改变时通知所有订阅者
public event EventHandler<int> TemperatureChanged;
}

// 请在此处完成 Alarm 类
class Alarm
{
public int Threshold { get; private set; }
public Alarm(int threshold) { Threshold = threshold; }
// ... 订阅事件,并在内部判断温度是否超过 Threshold
public void Subscribe(Sensor sensor,string name)
{
sensor.TemperatureChanged += (send, newTemperature) =>
{
if (newTemperature > Threshold)
{
Console.WriteLine($"Alarm! {name} Temperature {newTemperature} exceeds threshold {Threshold}.");
}
};
}
}

internal class Program
{
static void Main(string[] args)
{
var sensor = new Sensor();

var livingRoomAlarm = new Alarm(30); // 30度以上报警
var kitchenAlarm = new Alarm(50); // 50度以上报警

// TODO: 把报警器订阅到传感器上
livingRoomAlarm.Subscribe(sensor, "livingRoom");
kitchenAlarm.Subscribe(sensor, "kitchen");

sensor.CurrentTemperature = 35; // 此时客厅应该响,厨房不响
sensor.CurrentTemperature = 60; // 此时两个都应该响
}
}
}

可以取消订阅的闭包写法,待验证。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 1. 用一个显式的委托变量,把 Lambda 抓在手里!
EventHandler<CalculationEventArgs> rule = (sender, e) =>
{
if (e.A < 0 || e.B < 0)
{
e.IsCancelled = true;
e.CancelReason = "不允许使用负数进行计算!";
}
};

// 2. 拿变量去订阅
engine.OnBeforeCalculate += rule;

// 3. 拿同一个变量去解绑(内存地址完全一致,解绑成功!)
engine.OnBeforeCalculate -= rule;