NET基本探究——事件
(一)“事件”与“委托”:
在“NET基本探究系列”委托的部分我们已经知道了委托的出现为不同类之间动态调用同参、同返回值的函数提供了便利。现在我们来进一步研究委托在实际开发中的例子——事件(event)。
我们只要是用NET开发的就一直不断在接触事件——举个最简单的例子就是你在WinForm上拉一个Button,然后双击就出现一个“事件函数”。这个“事件函数”的本质是和“事件”挂钩的。如果你查看InitializeComponent()这部分的代码,你会发现有这样一行东西:
private void InitializeComponent()
{
this.button1 = new System.Windows.Forms.Button();
this.SuspendLayout();
……
this.button1.Click += new System.EventHandler(this.button1_Click);
}
注意上述代码中黑色的部分:其中Click是一个Button的事件,而“+=”(VB中是AddressOf)类似于为Click事件指明触发函数(在本文件的button1_Click)。
如果你右键查看Click的定义,你会发现Button类的Click事件的定义:
public event EventHandler Click;
而如果你再“刨根问底”下去,会发现Click的类型是EventHandler,而这恰恰又是一个委托。正是因为“委托”具备不同类之间允许动态调用同参同返回值函数的功能,所以你在一个Form类上引入Button类(拖拽一个Button到上面),双击Button之后在Form类自身生成一个与Button事件委托同参同返回值的私有函数,同时把这个函数和事件使用“+=”的形式挂接起来,完成了事件函数的初始化操作。至此,我们可以得出这样一个结论:“事件”和“委托”是密不可分的——“事件”恰恰利用了委托的“动态函数”特性使得你可以在不同类之间调用同参同返回值的函数,以便达到“触发”的效果。同时,我们也知道了一般事件定义方法:public event 委托类型名 事件名;——注意,委托类型必须是无返回值的,即void类型。
我们可以把“事件”的初始化和“委托”的初始化加以对比,增强我们对于它们之间关系理解的记忆:
委托: EventHander eh = new EventHander (同参同返回类型函数名)
事件:XXX.Click += new 事件委托名 (同参同返回类型函数名)
请特别注意我放大的这个部分:既然事件的本质是委托,为何不像委托一样使用等于符号,而偏偏使用“+=”进行初始化操作呢?这里就牵涉到了“隐式事件声明”的概念:一般地,任何在NET中的事件都是一个事件列表,通过已经重载的“+=”对事件进行批量的增加或者删除操作。为了解释清楚这个概念,现在我们“+=”代码展开让读者看看,NET究竟背后“秘密地”干了些什么——
(二)显示声明的“事件”:
我们注意到一个非常有意思的现象——如果你把一个类的事件(比如Button)同时指向(绑定到一个以上)的事件函数中的时候:
private void InitializeComponent()
{
this.button1 = new System.Windows.Forms.Button();
this.SuspendLayout();
……
this.button1.Click += new System.EventHandler(this.button1_Click);
this.button1.Click += new System.EventHandler(this.button2_Click); //自己对应添加一个Button方法
}
此时,如果你在button1_click中写上了弹出显示“1”的msgbox,然后在button2_click同时写上弹出“2”的对话框,你会“惊讶地”发现不是仅仅弹出2,而是“先1后2”!——Why?难道“事件”天性有记忆力?
如果我们回顾“多路委托”的概念的时候,知道一个委托可以加载多个同参同返回值的委托函数;同样地,一个事件也可以像委托一样进行“多路事件”的加载,只是微软在每一个事件背后本质上做了这样一些东西(拿Button作例子):
public class Button
{
private List<EventHandler> clickevents = new List<EventHandler>();
public event EventHandler Click
add
{
clickevents.Add(value);
}
remove
{
clickevents.Remove(value);
}
public void OnClick()
{
foreach (var item in clickevents)
{
item(this,null); //此处自行调用事件,表明到OnClick函数被执行后触发相应挂接的事件函数。
}
}
}
这个定义看上去很像属性,但是和属性不一样的在于“事件属性”多出了两个特殊的关键词“add”和“remove”,其本质是对“+=”和“-=”的重载定义:即用“+=”挂接到一个新函数时候,自动委派执行add中的Add方法,把真正执行的函数通过value传递并且添加到clickevents列表中。反之,使用“-=”,会自动执行remove,去掉已经定义的事件函数,从事件列表中移除。这样,当你点击了一个Button的时候,系统自然调用OnClick类似的函数,然后从事件列表中依次执行已经添加的事件函数。事件的“记忆性”就是这样形成的。
实际上,你完全可以按照这种方式,自行添加一个事件列表去有选择地控制事件的执行。不信你可以试一试。