一:背景
1. 讲故事
前几天公司一个妹子问我,事件和委托有什么区别?先由衷感叹一下,编码十余年,年老的时候常被面试官问起,当初年长了,却被后辈们时常问候,看样子逃离编码生涯之前是跑不掉了,不过奇怪的是,这个问题被问起的时候,我发现有很多人用: 事件是一种非凡的委托
来进行总结,是不是挺有意思,我想这句话可能来自于网络上的面试题答案吧,这篇我就试着彻底总结一下。
二:事件真的是非凡的委托吗?
1. 猫和老鼠 经典案例
要想晓得两者到底什么关系?先得有一些根底代码,这里就用大家初学事件时用到的 猫和老鼠
经典案例,代码简化如下:
class Program
{static void Main(string[] args)
{Cat cat = new Cat("汤姆");
Mouse mouse1 = new Mouse("杰瑞", cat);
Mouse mouse2 = new Mouse("杰克", cat);
cat.CatComing();
Console.ReadKey();}
}
class Cat
{
public event Action CatCome; // 申明一个事件
private string name;
public Cat(string name)
{this.name = name;}
public void CatComing()
{Console.WriteLine("猫" + name + "来了");
CatCome?.Invoke();}
}
class Mouse
{
private string name;
public Mouse(string name, Cat cat)
{
this.name = name;
cat.CatCome += this.RunAway; //Mouse 注册 CatCome 主题
}
public void RunAway()
{Console.WriteLine(name + "正在逃跑");
}
}
代码十分简洁,猫的 CatCome 动作一旦触发,注册到 CatCome 上的 两只 mouse 就会执行各自的逃跑动作 RunAway
,如果大家没有看懂能够多看几遍哈。
2. 观察者模式 / 公布订阅模式
如果你理解过设计模式,我想你应该第一眼就能看出这是 观察者模式,对的,当初有数的框架都在应用这个模式,比方前端的:Vue,Knockout,React,还有 redis 的公布订阅等等,如果用图画一下大略就是这样。
从图中能够看到,几个 subscribe 都订阅了一个叫做 subject 的主题,一旦有外来的 publish 推送到了 subject,那么订阅 subject 的 subscribe 都会收到告诉,接下来依据这张图对方才的代码再缕一篇:
- 猫的
public event Action CatCome
就是一个主题(subject)。 - 老鼠的
cat.CatCome += this.RunAway
就是 subscribe 对 subject 的订阅。 - 最初的
public void CatComing()
就是对 subject 的推送,pubish 了一条猫来了
。
3. 应用观察者模式 对 猫鼠进行解剖
有了观察者模式的根底, 对下面的代码进行革新就不便多了,我能够把 public event Action CatCome;
改成 一个 List<Action>
数组,模仿 Subject
哈,简化后的代码如下:
class Cat
{public List<Action> Subject = new List<Action>(); // 定义一个主题
private string name;
public Cat(string name)
{this.name = name;}
public void CatComing()
{Console.WriteLine("猫" + name + "来了");
Subject.ForEach(item => { item.Invoke(); });
}
}
class Mouse
{
private string name;
public Mouse(string name, Cat cat)
{
this.name = name;
cat.Subject.Add(RunAway); // 将 逃跑 办法注入到 subject 中
}
public void RunAway()
{Console.WriteLine(name + "正在逃跑");
}
}
看到这里,我想你对 事件和委托
应该有一个大略的意识了吧,但这里还有一个问题,C# 中的事件 真的如我写的观察者模式这样的吗???要答复这个问题,须要从 IL 角度看一下事件到底生成了什么。
三:从 IL 角度看事件
1. 应用 ilspy /ildasm 小工具
首先来看一下所谓的事件到底在 IL 层面是个什么货色, 如下图:
从图中看其实就是两个接管 Action
参数的 add_CatCome
和 remove_CatCome
办法,这两个办法简化后的 il 代码如下:
.event [mscorlib]System.Action CatCome
{.addon instance void ConsoleApp2.Cat::add_CatCome(class [mscorlib]System.Action)
.removeon instance void ConsoleApp2.Cat::remove_CatCome(class [mscorlib]System.Action)
}
.method public hidebysig specialname
instance void add_CatCome (class [mscorlib]System.Action 'value'
) cil managed
{
// Method begins at RVA 0x2090
// Code size 41 (0x29)
.maxstack 3
.locals init ([0] class [mscorlib]System.Action,
[1] class [mscorlib]System.Action,
[2] class [mscorlib]System.Action
)
IL_0000: ldarg.0
IL_0001: ldfld class [mscorlib]System.Action ConsoleApp2.Cat::CatCome
IL_0006: stloc.0
// loop start (head: IL_0007)
IL_000b: call class [mscorlib]System.Delegate [mscorlib]System.Delegate::Combine(class [mscorlib]System.Delegate, class [mscorlib]System.Delegate)
IL_0010: castclass [mscorlib]System.Action
IL_0017: ldflda class [mscorlib]System.Action ConsoleApp2.Cat::CatCome
IL_001e: call !!0 [mscorlib]System.Threading.Interlocked::CompareExchange<class [mscorlib]System.Action>(!!0&, !!0, !!0)
// end loop
IL_0028: ret
} // end of method Cat::add_CatCome
.method public hidebysig specialname
instance void remove_CatCome (class [mscorlib]System.Action 'value'
) cil managed
{
IL_0000: ldarg.0
IL_0001: ldfld class [mscorlib]System.Action ConsoleApp2.Cat::CatCome
IL_0006: stloc.0
// loop start (head: IL_0007)
IL_000b: call class [mscorlib]System.Delegate [mscorlib]System.Delegate::Remove(class [mscorlib]System.Delegate, class [mscorlib]System.Delegate)
IL_0010: castclass [mscorlib]System.Action
IL_0017: ldflda class [mscorlib]System.Action ConsoleApp2.Cat::CatCome
IL_001e: call !!0 [mscorlib]System.Threading.Interlocked::CompareExchange<class [mscorlib]System.Action>(!!0&, !!0, !!0)
IL_0026: bne.un.s IL_0007
// end loop
IL_0028: ret
} // end of method Cat::remove_CatCome
接下来看看 mouse
类的注册是怎么实现的。
从图中能够看到,所谓的注册就是将 RunAway
作为 add_CatCome
办法的参数传进去而已,回过头来看,最外围的就是那两个所谓的 addxxx
和 removexxx
办法。
2. 将 IL 代码进行 C# 还原
可能有些同学对 IL 代码不是很相熟,如果能还原成 C# 代码就???????? 了,接下来我就试着还原一下。
class Cat
{
Action CatCome;
public void add_CatCome(Action value)
{
Action action = this.CatCome;
Action action2 = null;
do
{
action2 = action;
Action value2 = (Action)Delegate.Combine(action2, value);
action = Interlocked.CompareExchange(ref this.CatCome, value2, action2);
}
while ((object)action != action2);
}
public void remove_CatCome(Action value)
{
Action action = this.CatCome;
Action action2 = null;
do
{
action2 = action;
Action value2 = (Action)Delegate.Remove(action2, value);
action = Interlocked.CompareExchange(ref this.CatCome, value2, action2);
}
while ((object)action != action2);
}
private string name;
public Cat(string name)
{this.name = name;}
public void CatComing()
{Console.WriteLine("猫" + name + "来了");
CatCome?.Invoke();}
}
class Mouse
{
private string name;
public Mouse(string name, Cat cat)
{
this.name = name;
cat.add_CatCome(this.RunAway);
}
public void RunAway()
{Console.WriteLine(name + "正在逃跑");
}
}
能够看出还原后的 C# 代码跑起来是没有问题的,和观察者模式相比,这里貌似没有看到 subject
这样的 List<Action>
汇合,然而你仔细分析的话,其实是有的,你肯定要着重剖析这句代码:Action value2 = (Action)Delegate.Combine(action2, value);
它用的就是多播委托,用 Combine
办法将后续的 Action 送到前者 Action 的 _invocationList
中,不信的话,我调试给你看哈。
没故障吧,Action CatCome
中曾经有了两个 callback 办法啦,一旦 CatCome.Invoke()
, _invocationList 中的办法就会被执行,也就看到两只老鼠在逃跑啦。
四:总结
您当初是不是明确啦,委托和事件的关系 好比 砖头和房子的关系,房子只是砖头的一个利用场景,您如果说房子是一种非凡的砖,这句话品起来是不是有一种怪怪的感觉,不是吗?