匿名方法作为事件处理程序 - 第二部分





0/5 (0投票)
匿名方法作为事件处理程序
上一篇文章讨论了匿名方法作为事件处理程序,并以一个问题结尾——为什么订阅有效,但取消订阅却不起作用?
Vivek给出了正确的答案——C# 编译器处理和转换匿名方法的方式是原因所在。
这是涉及的代码
1: public void Initialize()
2: {
3: control.KeyPressed += IfEnabledThenDo(control_KeyPressed);
4: control.MouseMoved += IfEnabledThenDo(control_MouseMoved);
5: }
6:
7: public void Destroy()
8: {
9: control.KeyPressed -= IfEnabledThenDo(control_KeyPressed);
10: control.MouseMoved -= IfEnabledThenDo(control_MouseMoved);
11: }
12:
13: public EventHandler<Control.ControlEventArgs>
IfEnabledThenDo(EventHandler<Control.ControlEventArgs> actualAction)
14: {
15: return (sender, args) => { if (args.Control.Enabled) actualAction(sender, args); };
16: }
编译器将 IfEnabledThenDo
转换为以下代码
1: public EventHandler<Control.ControlEventArgs>
IfEnabledThenDo(EventHandler<Control.ControlEventArgs> actualAction)
2: {
3: <>c__DisplayClass1 CS$<>8__locals2 = new <>c__DisplayClass1();
4: CS$<>8__locals2.actualAction = actualAction;
5: return new EventHandler<Control.ControlEventArgs>(
CS$<>8__locals2.<IfEnabledThenDo>b__0);
6: }
现在问题应该很明显了——每次调用该函数时,都会创建一个新对象,而返回的事件处理程序实际上引用了新实例上的一个方法(<IfEnabledThenDo>b__0
)。 这就是取消订阅失败的原因。 –=
不会从调用列表中删除同一类的不同实例的委托——如果这样做,如果同一类的多个实例订阅事件,后果将不堪设想。
但是,编译器为什么会将我们的 lambda 表达式这样转换? Raymond Chen 撰写了一篇很棒的博文解释了原因,但简而言之,这是为了“保存”actualAction
(IfEnabledThenDo
的方法参数),以便在事件处理程序实际执行时可用。
既然我们知道了原因,解决此问题的办法是缓存 IfEnabledThenDo
返回的委托实例,并在订阅和取消订阅时使用相同的实例。
1: EventHandler<Control.ControlEventArgs> keyPressed;
2: EventHandler<Control.ControlEventArgs> mouseMoved;
3:
4: public void Initialize()
5: {
6: keyPressed = IfEnabledThenDo(control_KeyPressed);
7: mouseMoved = IfEnabledThenDo(control_MouseMoved);
8:
9: control.KeyPressed += keyPressed;
10: control.MouseMoved += mouseMoved;
11: }
12:
13: public void Destroy()
14: {
15: control.KeyPressed -= keyPressed;
16: control.MouseMoved -= mouseMoved;
17: }
了解底层工作原理确实有其优势,我想。
附言:对原始示例进行一个非常小的语法更改,代码就能立即工作。 如果你一直关注到这里,你应该能够弄清楚原因。
1: public void Initialize()
2: {
3: actualAction = control_KeyPressed;
4: control.KeyPressed += IfEnabledThenDo();
5: }
6:
7: public void Destroy()
8: {
9: control.KeyPressed -= IfEnabledThenDo();
10: }
11:
12: EventHandler<Control.ControlEventArgs> actualAction;
13: public EventHandler<Control.ControlEventArgs> IfEnabledThenDo()
14: {
15: return (sender, args) => { if (args.Control.Enabled) actualAction(sender, args); };
16: }