事件与委托
一句话:委托(delegate)是“可以装方法的变量”;事件(event)是“受保护的委托通知机制”。
小例子
1 | // 比如定义一个“能接收两个 int,返回一个 int 的方法类型 |
这句话的意思不是写了一个方法,而是定义了一种方法签名类型。
也就是说,凡是长这样的方法,都可以装进 Calc:
1 | static int Add(int x, int y) => x + y; |
使用:
1 | Calc c1 = Add; |
你可以把它理解成:
Add 是一个方法
Calc 是一种“能引用这类方法的类型”
c1 是一个装了 Add 的变量
为什么需要委托
核心价值有两个:
- 把“做什么”当参数传进去,相当于把“算法”传进去了。
- 回调一个方法做完事后,通知另一个方法处理结果,这就是回调。
委托特别适合干这个。此外,为保证安全性,委托不是随便什么方法都能赋值,签名必须匹配(参数与返回值一致)。
委托的回调:
1 | using System; |
输出:
1 | Main: 开始执行 |
流程拆解
- step 1 ProcessTask(OnTaskCompleted); OnTaskCompleted 方法“交给”了 ProcessTask
- step 2 进入 ProcessTask
static void ProcessTask(Notify callback)
这里的 callback 实际上就是:
callback == OnTaskCompleted
- step3 执行任务逻辑
Console.WriteLine("处理中...");for{...} - Step 4:触发回调(核心)
callback("任务已经完成!");这行代码等价于:OnTaskCompleted("任务已经完成!");
也就是说:ProcessTask 反过来调用了 Main 提供的方法 - step 5 执行回调函数
static void OnTaskCompleted(string msg)
输出:也就是说,可以定义多个不同的函数,只要满足参数和返回值类型与定义的委托一致,就可以作为参数传入到其他函数中1
2回调函数被调用!
收到消息: 任务已经完成!
同时,委托还支持多播
1 | delegate void Notify(); |
输出
1 | A |
同理,也可以移除
1 | n -= A; |
事件
事件本质上是对委托的一层封装,限制外部只能订阅/取消订阅,不能随便触发
示例:
1 | class Button |
也就是说,发布者负责触发,订阅者只负责响应
经典反例:
1 | class Button |
问题:
👉 外部把“按钮点击”这个行为控制权拿走了
这在设计上是灾难:谁都可以伪造点击 谁都可以把监听清空
所以引入了event
1 | class Button |
事件的本质:一个 private 委托 + 受限访问
完整例子:
1 | using System; |
输出:
1 | 按钮被按下 |
流程拆解:
订阅
button.Click += OnClick1; button.Click += OnClick2;
现在内部相当于:_click = OnClick1 + OnClick2;
2. 触发事件 Click?.Invoke();
等价于:
1 | OnClick1(); |
事件三要素:
- 发布者 这里是 class Button
- 事件本身 public event Action Click;
- 订阅者 button.Click += OnClick1;
其余要点
- Click?.Invoke() 此处的?表示执行前进行非空判断,若为null则不执行,Click.Invoke()则会在null时报错
- 事件本质上就是委托,但进行了event封装,委托访问权在外部,事件访问权在内部
- 多播的返回值问题,如: 答案是:
1
2
3
4
5
6
7
8delegate int Calc();
// 再绑定多个方法
Calc c = A;
c += B;
c += C;
//调用
int result = c();
// 此时该输出什么?只会得到最后一个方法的返回值
完整例子:输出: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
38using System;
namespace MulticastReturnDemo
{
delegate int Calc();
class Program
{
static void Main(string[] args)
{
Calc c = Method1;
c += Method2;
c += Method3;
int result = c();
Console.WriteLine("最终返回值: " + result);
}
static int Method1()
{
Console.WriteLine("Method1 执行");
return 10;
}
static int Method2()
{
Console.WriteLine("Method2 执行");
return 20;
}
static int Method3()
{
Console.WriteLine("Method3 执行");
return 30;
}
}
}1
2
3
4Method1 执行
Method2 执行
Method3 执行
最终返回值: 30 - 中间有异常咋办? 比如: Method1 正常 Method2 抛异常 Method3 正常
那么默认情况下,执行到 Method2 出错后,后面的 Method3 就不会继续执行。 此外,可以通过GetInvocationList()此方法来返回当前绑定的所有方法列表。这样你就可以一个一个调用,并分别处理异常