看看这个常常被初级程序员弄不懂的“事件”

王朝学院·作者佚名  2016-08-27
窄屏简体版  字體: 小  |  中  |  大  |  超大  

众所周知在面试中,经常有些崽子面试官会问些“事件和委托”的关系,也许一路走来的程序员大多都会被问到这个,那么对于这个

高频的”事件和委托“问题,如何回击呢?首先我从最经典的一套面试题说起,用事件来实现 “猫爪老鼠“,这是一个从网上copy过来的一

个例子。

staticvoidMain(string[] args)

{

Mouse mouse=newMouse();

Cat cat=newCat();

cat.OnCry();

Console.ReadLine();

}

}publicdelegatevoidCryEventHandler();publicclassCat

{publicstaticeventCryEventHandler Cry;publicCat()

{

Console.WriteLine("Cat:I'm coming.");

}publicvirtualvoidOnCry()

{

Console.WriteLine("Cat:MiaoMiao");if(Cry !=null)

{

Cry.Invoke();

}

}

}publicclassMouse

{publicMouse()

{

Cat.Cry+=newCryEventHandler(Run);

Console.WriteLine("Mouse:I go to find something,and I must always listen cat's crying.");

}publicvoidRun()

{

Console.WriteLine("Mouse:A cat is coming,I must go back!");

}

}

事件定义啥的什么玩意这个我就不说了,没什么意思,为了了解这个跟委托有什么关系,下面我们来看看这段代码最后生成的IL是什么样的。

1:CryEventHandler委托

1publicdelegatevoidCryEventHandler();

这个我想大家都清楚,委托本质上是一个继承于MulticastDelegate的类,同时会生成仅有的4个方法,看下IL即知。

2:Cat类

1publicclassCat2{3publicstaticeventCryEventHandler Cry;45publicCat()6{7Console.WriteLine("Cat:I'm coming.");8}910publicvirtualvoidOnCry()11{12Console.WriteLine("Cat:MiaoMiao");13if(Cry !=null)14{15Cry.Invoke();16}17}18}

从这个类中,我们看到了一个Cry事件,然后就是一个Cry.Invoke(),不过当你看到Invoke的时候,你是不是很怀疑Cry是不是一个委托字段呢?

其实你怀疑的是一点问题都没有,32个赞,看下IL。

从上图中我们看到了两个好玩的东西:

① field Cry 字段,完整定义如下,然来所谓的“事件字段” 其实在IL下面蜕变成了委托字段,如果你觉得很奇怪,请看第二点。

.fieldPRivatestaticclassSample.CryEventHandler Cry

② add_Cry,remove_Cry,如果仅仅将事件字段变成委托字段,那确实是编译器在发什么神经,然来编译器还给事件配备了两个方法,这个

其实也就是事件里面+=,-=的奥秘所在,下面我们挑add_Cry方法说下,看看方法定义的IL代码是怎么样的。

很新奇,我们找到了Combine方法,这个我们都知道,原来事件中的+=,其实就是利用Combine来将当前的委托实例放到Delegate的

委托链表中(其实里面是array实现的),为了方便理解,我把上面的IL代码翻译成C#代码。

1publicclassCat2{3///<summary>4///私有的委托变量5///</summary>6privatestaticCryEventHandler Cry;78///<summary>9///事件生成的方法10///</summary>11///<param name="cryEventHandler"></param>12publicvoidAdd_Cry(CryEventHandler cryEventHandler)13{14varresult =(CryEventHandler)Delegate.Combine(Cry, cryEventHandler);1516Interlocked.CompareExchange<CryEventHandler>(refCry, result, Cry);17}1819publicvoidRemove_Cry(CryEventHandler cryEventHandler)20{21varresult =(CryEventHandler)Delegate.Remove(Cry, cryEventHandler);2223Interlocked.CompareExchange<CryEventHandler>(refCry, result, Cry);24}2526publicCat()27{28Console.WriteLine("Cat:I'm coming.");29}3031publicvirtualvoidOnCry()32{33Console.WriteLine("Cat:MiaoMiao");3435if(Cry !=null)36{37//委托专用的四个方法,invoke,begininvoke,endinvoke,ctor38Cry.Invoke();39}40}41}

可能有些同学对IL指令不是很熟悉,没关系,我也一样,咱博客园上面有位大神飞鸟的一篇IL指令集的博文或许能帮得到你。

3:Mouse类

如果你对Cat类的IL代码琢磨的差不多的话,下面这个Mouse类就非常简单了,仅仅调用而已嘛。

1publicclassMouse2{3publicMouse()4{5Cat.Cry +=newCryEventHandler(Run);6Console.WriteLine("Mouse:I go to find something,and I must always listen cat's crying.");7}89publicvoidRun()10{11Console.WriteLine("Mouse:A cat is coming,I must go back!");12}13}

这个地方最让人关心的就是:Cat.Cry += new CryEventHandler(Run) 这个语句,从它的IL中可以看到其实做了三件事。

① ldftn: 将Run方法的非托管指针推送到计算堆栈上。

② newobj: 将CryEventHandler委托new一下,同时将计算堆栈上的Run方法的非托管指针作为构造函数的参数。

③ call: 调用Cat类的Add_Cry方法,将CryEventHandler的实例作为参数传递下去。

下面继续将该IL代码反编译回来,不过针对IL指令:call void Sample.Cat::add_Cry(class Sample.CryEventHandler)

并没有很好的翻译过来,只能new Cat()了一下才能调用Add_Cry,从而触发了Cat的构造函数。

1publicclassMouse2{3publicMouse()4{5varcryHandler =newCryEventHandler(Run);67/*8* 针对IL:call void Sample.Cat::add_Cry(class Sample.CryEventHandler)9* 这个没有反编译好,因为我new Cat()将会再次调用构造函数。10*/11newCat().Add_Cry(cryHandler);1213Console.WriteLine("Mouse:I go to find something,and I must always listen cat's crying.");14}1516publicvoidRun()17{18Console.WriteLine("Mouse:A cat is coming,I must go back!");19}20}

好了,说了这么多,应该也有总结性的东西出来了,原来事件是完完全全的建立在委托的基础上,你可以认为事件就是用委托来玩一个

观察者模式的,你甚至可以认为事件就是委托。没有本质区别。

 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
 
© 2005- 王朝網路 版權所有 導航