CodeGym /课程 /C# SELF /用于 event 和 callback 的 Lambda 表达式

用于 event 和 callback 的 Lambda 表达式

C# SELF
第 50 级, 课程 3
可用

1. 介绍

在 C#(以及整个 .NET)里,事件是对象之间互相通信的一种方式。比如你有一个按钮,点击它应该触发某些操作。可以通过声明单独的方法来处理事件,然后把那个方法显式订阅到事件上……但很多时候你只想“就在这儿写反应”,别把逻辑分散到文件的各个部分。

Lambda 表达式非常适合解决这个问题 — 允许你直接在订阅事件的地方写出对事件的反应。仪式感少,效率高。

同样地,回调(callback)场景也是:有时候你把某段逻辑传给方法,等某个操作完成后再调用。Lambda 表达式让这件事简单直观:需要的逻辑就在调用处,清晰易读。

事件和回调:要点以及什么时候用 Lambda

事件 — 本质上是对象上的一个“挂钩”:别人可以订阅,当某件事发生时就会执行订阅者的代码。

Callback(回调) — 就是“我现在给你一个函数,等需要的时候你来调用它”。很像“留下联系方式,我们会回电”那种模式。

当你学过 delegate 和 event 的时候,可能见过这样的语法:

button.Click += MyButtonClickHandler;

其中 MyButtonClickHandler 是你需要在某处声明的专门方法。但当逻辑很简单时,你宁愿用几行代码搞定,而不是为一个动作额外写方法。

这时 Lambda 就登场了:

button.Click += (sender, args) => { Console.WriteLine("按钮被按下!"); };

简洁、直观、就在这里!

2. 作为事件处理器的 Lambda:从简单到稍复杂

简单形式:灯一闪 — 控制台输出

假设我们有一个类 Button,想要响应它的 Click 事件。

// 假设有这么个按钮:
public class Button
{
    public event EventHandler? Click;

    public void SimulateClick() // 举例:程序性地“点击”按钮
    {
        // 如果有人订阅了 — 调用处理器
        Click?.Invoke(this, EventArgs.Empty);
    }
}

现在使用这个按钮:

var button = new Button();

// 用 Lambda 订阅按钮事件!
button.Click += (sender, args) =>
{
    Console.WriteLine("太棒了!按钮被按下了!");
};

button.SimulateClick(); // => 太棒了!按钮被按下了!

说明:
— 在 => 之后的是你这个“小函数”的主体,当事件发生时会被调用。
— 不用纠结处理器的生命周期:它活着只要你的订阅(+=)还在。注意:如果 Lambda 捕获了外部变量但你不取消订阅,可能会导致内存泄漏。

捕获外部变量的 Lambda 处理器

int clickCount = 0;

button.Click += (sender, args) =>
{
    clickCount++;
    Console.WriteLine($"按钮已经被按了 {clickCount} 次!");
};

button.SimulateClick(); // => 按钮已经被按了 1 次!
button.SimulateClick(); // => 按钮已经被按了 2 次!

发生了什么:
Lambda 捕获了外部变量 clickCount,并在多次调用之间记住它的值。

3. 把 Lambda 用作 callbacks

回调 常用于某段代码需要在将来调用你的函数,比如某个耗时操作完成后。

把 Lambda 作为 callback 传递

假设有个方法接收一个 delegate 或 Lambda 作为参数:

void DoWork(Action callback)
{
    Console.WriteLine("开始工作...");
    // 模拟工作
    System.Threading.Thread.Sleep(500); // (真实应用别这么做,这是阻塞操作!) 
    callback(); // 工作结束后调用 callback
}

DoWork(() => Console.WriteLine("工作完成!"));

结果:
控制台先会输出 "开始工作...",然后是 "工作完成!"。

也可以传带参数的 Lambda:

void Calculate(int a, int b, Action<int> onResult)
{
    int sum = a + b;
    onResult(sum);
}

Calculate(5, 8, result => Console.WriteLine($"结果: {result}"));

4. 使用 Lambda 时的常见错误

不取消事件订阅

当你用 Lambda 订阅事件时,通常没有“处理器名称”可以方便取消订阅:

button.Click += (s, e) => Console.WriteLine("...");
button.Click -= ??? // 怎么把你的 Lambda 传进去?如果没保存,做不到。

如果确实需要取消订阅 — 把 Lambda 保存到变量:

EventHandler handler = (s, e) => Console.WriteLine("...");
button.Click += handler;
// ...然后
button.Click -= handler;

这对长生命周期对象很重要:不取消订阅会留下引用,可能导致内存泄漏。

循环中捕获变量的问题

经典情况:在循环里订阅事件:

for (int i = 0; i < 3; i++)
    button.Click += (s, e) => Console.WriteLine(i); // 所有 Lambda “记住”的都是同一个 i

在新版 C# 中,foreach 的行为已经修正过,但 for 有时还会有细节问题 — 对于被捕获的变量要始终小心!

如果你希望每个 Lambda 记住各自的值 — 创建局部副本:

for (int i = 0; i < 3; i++)
{
    int localI = i;
    button.Click += (s, e) => Console.WriteLine(localI);
}
2
任务
C# SELF, 第 50 级, 课程 3
已锁定
带变量捕获的点击计数器
带变量捕获的点击计数器
评论
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION