Part I of Events in Asp.Net: Events in .Net

声明:本blog所有文章均为EagleFish在cnBlogs上的原创,欢迎转载,但请注明出处。

在介绍Asp.net中的事件之前,我们先对.net中的事件处理和实现机制做以简单介绍。

相信所有人对事件(Event)的概念都是很熟悉的。要实现一个事件系统,下列元素是必不可少的:
1.事件源  (sender)。
2.事件的接收方  (receiver)。//注:准确的说,应该是接收方的事件处理方法。
3.发送方所要发送给接收方的信息 (message)。
4.接收方在发送方注册/注销 (register/unregister)。
前三者都是静态数据结构,而注册是一个动态的过程,其实就是把接收方和它所关心事件的发送方连接起来,这种连接过程的结果其实也是一个数据结构,一般体现为发送方对象实例内部维持的一个动态 列表,列表里的每一项都是一个接收方对象实例的事件处理方法(可理解为函数指针)。在事件发生的时候,发送方遍历此列表,并依次调用列表中的方法,这个过程就叫做事件的触发(fire)。

下面,我们就以这4个概念为着眼点,来解释一下.net中的事件体系(.net中事件的实现利用了.net中的委托机制,这是一种被编译器直接支持的类型安全的回调函数定义方法,对它的详细介绍超出了本文的范围)。先介绍一下一个非常简单的应用场景吧:有若干个button,位于一个Panel上,每一个button有不同的name。当任意一个button被push的时候,Panel都会接收到这个事件,并作出响应:打印被点击button的name。好,要实现这个场景,我们要做些什么呢?

一.我们先从最容易的开始,先构建一个消息类,让这个类去封装button的名字。按约定,所有传递给事件处理程序用于存放事件信息的类都必须继承自System.EventArgs:
ContractedBlock.gif ExpandedBlockStart.gif 事件消息类
 1None.gifsealed class ButtonPushEventArgs : EventArgs
 2ExpandedBlockStart.gifContractedBlock.gifdot.gif{
 3InBlock.gif    private readonly string buttonName;
 4InBlock.gif
 5InBlock.gif    public ButtonPushEventArgs(string name)
 6ExpandedSubBlockStart.gifContractedSubBlock.gif    dot.gif{
 7InBlock.gif        buttonName = name;
 8ExpandedSubBlockEnd.gif    }

 9InBlock.gif
10InBlock.gif    public string ButtonName
11ExpandedSubBlockStart.gifContractedSubBlock.gif    dot.gif{
12InBlock.gif        get
13ExpandedSubBlockStart.gifContractedSubBlock.gif        dot.gif{
14InBlock.gif        return buttonName;
15ExpandedSubBlockEnd.gif        }

16ExpandedSubBlockEnd.gif    }

17ExpandedBlockEnd.gif}

二. 接下来我们要定义事件源了,让我们先看一下Button类的代码:
ContractedBlock.gif ExpandedBlockStart.gif 事件源
 1None.gifclass Button
 2ExpandedBlockStart.gifContractedBlock.gifdot.gif{
 3InBlock.gif    private string name;//我们没有提供这个成员变量的公有属性访问器,外界环境只能从事件引发的消息中
 4InBlock.gif                //得到它的值。
 5InBlock.gif
 6InBlock.gif    public Button(string name)
 7ExpandedSubBlockStart.gifContractedSubBlock.gif    dot.gif{
 8InBlock.gif        this.name = name;
 9ExpandedSubBlockEnd.gif    }

10InBlock.gif
11ExpandedSubBlockStart.gifContractedSubBlock.gif    /**//// <summary>
12InBlock.gif    /// 事件源中的必备要素1:对事件的定义
13ExpandedSubBlockEnd.gif    /// </summary>

14InBlock.gif    public event EventHandler<ButtonPushEventArgs> Push;
15InBlock.gif
16ExpandedSubBlockStart.gifContractedSubBlock.gif    /**//// <summary>
17InBlock.gif    /// 事件源中的必备要素2:定义一个方法,用来触发接收方的动作。
18InBlock.gif    /// 如果事件源是一个类型系统的话,这个方法一般只需要在基类中进行定义
19InBlock.gif    /// </summary>
20ExpandedSubBlockEnd.gif    /// <param name="e"></param>

21InBlock.gif    public void OnPush(ButtonPushEventArgs e)
22ExpandedSubBlockStart.gifContractedSubBlock.gif    dot.gif{
23InBlock.gif        EventHandler<ButtonPushEventArgs> temp = Push;
24InBlock.gif        if (temp != null)
25InBlock.gif        temp(this, e);
26ExpandedSubBlockEnd.gif    }

27InBlock.gif
28ExpandedSubBlockStart.gifContractedSubBlock.gif    /**//// <summary>
29InBlock.gif    /// 事件源中的必备要素3:对事件的触发(或者说,事件总是出现在事件源的某一个动作之后,这里定义的就是那个
30InBlock.gif    /// 动作)
31ExpandedSubBlockEnd.gif    /// </summary>

32InBlock.gif    public void PushMe()
33ExpandedSubBlockStart.gifContractedSubBlock.gif    dot.gif{
34InBlock.gif        ButtonPushEventArgs args = new ButtonPushEventArgs(this.name);
35InBlock.gif        this.OnPush(args);
36ExpandedSubBlockEnd.gif    }

37ExpandedBlockEnd.gif}
在程序中的注释里已经提到了,Button类中的Push,OnPush和PushMe是和事件相关的3个重要元素,让我们来一一认识它们:
1.public event EventHandler<ButtonPushEventArgs> Push;
  这里定义了一个名为Push的事件。event是一个特殊的关键字,用来提示编译器生成特定的IL代码(我们稍后就会看到),这些代码对.Net中的事件机制起到了关键作用,因为正是这些代码操作着发送方内部的 动态列表。而后面的EventHandler<ButtonPushEventArgs>定义了事件的具体类型。注意,我们在看.net的一些资料的时候,经常会看到“事件类型”这类的说法,这里的事件类型和我们日常生活中的事件类型有很大差别,它定义的其实是“事件处理方法”的类型(我们生活中的事件类型恐怕和程序中的事件消息message在意义上更为贴近)。当然,事件源自己是不清楚事件处理方法的,它只清楚自己发出的消息,所以这里定义事件类型,其实只能是 一个有确定参数(EventArgs)的委托(Delegate)类型,而msdn上对EventHandler泛型委托的定义印证了我们这个猜测:
public delegate void EventHandler<TEventArgs> (
 Object sender,
 TEventArgs e
) where TEventArgs : EventArgs
由于所有委托类型都继承自System.MulticastDelegate类,可以用委托类型对象内部的_invocationList变量很容易的形成委托链,而这个委托链,就是事件源内部最关键的数据结构,数据源正是靠这个链表来依次调用接收方的注册方法。那么对这个委托链的具体操作到底在哪儿呢?前面我们说过,当编译器看到event关键字的时候,会生成一些特定的IL代码,本程序中的相关IL代码如下:
ContractedBlock.gif ExpandedBlockStart.gif public event EventHandler Push;生成的IL代码
 1None.gif.field private class [mscorlib]System.EventHandler`1<class EventInDotNet.ButtonPushEventArgs> Push
 2None.gif
 3None.gif.method public hidebysig specialname instance void 
 4None.gif        add_Push(class [mscorlib]System.EventHandler`1<class EventInDotNet.ButtonPushEventArgs> 'value') cil managed synchronized
 5ExpandedBlockStart.gifContractedBlock.gifdot.gif{
 6InBlock.gif  // 代码大小       24 (0x18)
 7InBlock.gif  .maxstack  8
 8InBlock.gif  IL_0000:  ldarg.0
 9InBlock.gif  IL_0001:  ldarg.0
10InBlock.gif  IL_0002:  ldfld      class [mscorlib]System.EventHandler`1<class EventInDotNet.ButtonPushEventArgs> EventInDotNet.Button::Push
11InBlock.gif  IL_0007:  ldarg.1
12InBlock.gif  IL_0008:  call       class [mscorlib]System.Delegate [mscorlib]System.Delegate::Combine(class [mscorlib]System.Delegate,
13InBlock.gif                                                                                          class [mscorlib]System.Delegate)
14InBlock.gif  IL_000d:  castclass  class [mscorlib]System.EventHandler`1<class EventInDotNet.ButtonPushEventArgs>
15InBlock.gif  IL_0012:  stfld      class [mscorlib]System.EventHandler`1<class EventInDotNet.ButtonPushEventArgs> EventInDotNet.Button::Push
16InBlock.gif  IL_0017:  ret
17ExpandedBlockEnd.gif}
 // end of method Button::add_Push
18None.gif
19None.gif.method public hidebysig specialname instance void 
20None.gif        remove_Push(class [mscorlib]System.EventHandler`1<class EventInDotNet.ButtonPushEventArgs> 'value') cil managed synchronized
21ExpandedBlockStart.gifContractedBlock.gifdot.gif{
22InBlock.gif  // 代码大小       24 (0x18)
23InBlock.gif  .maxstack  8
24InBlock.gif  IL_0000:  ldarg.0
25InBlock.gif  IL_0001:  ldarg.0
26InBlock.gif  IL_0002:  ldfld      class [mscorlib]System.EventHandler`1<class EventInDotNet.ButtonPushEventArgs> EventInDotNet.Button::Push
27InBlock.gif  IL_0007:  ldarg.1
28InBlock.gif  IL_0008:  call       class [mscorlib]System.Delegate [mscorlib]System.Delegate::Remove(class [mscorlib]System.Delegate,
29InBlock.gif                                                                                         class [mscorlib]System.Delegate)
30InBlock.gif  IL_000d:  castclass  class [mscorlib]System.EventHandler`1<class EventInDotNet.ButtonPushEventArgs>
31InBlock.gif  IL_0012:  stfld      class [mscorlib]System.EventHandler`1<class EventInDotNet.ButtonPushEventArgs> EventInDotNet.Button::Push
32InBlock.gif  IL_0017:  ret
33ExpandedBlockEnd.gif}
 // end of method Button::remove_Push
看到上面的add_Push方法,大家对于数据源如何新增事件接收者就心里有数了。事件接收者的内部一定有一个以Object和ButtonPushEventArgs为类型的方法,这个方法将被添加到事件源的事件委托链上,最终在事件发生的时候完成这个方法的调用。
  可以在在这里多提一句的是,大家可以注意一下IL代码的第12行,委托链的增长是通过调用Delegate类的静态方法Combine方法把一个新的委托添加到已有委托链尾端的。而这个方法的两个参数分别是当前调用栈栈顶的两个元素,第一个参数是编译器根据event关键字为Button类生成的类型为EventHandler<>的成员变量Push(见IL代码第一行),而第二个参数就是add_Push方法的传入参数了,事件的接收者最终会调用这个方法,并将自己的处理方法传入。在方法第一次被调用的时候,Push是一个空引用,而根据Delegate.Combine()方法的说明,把一个空引用和一个委托实例a进行连接,则返回a。所以,Push是在第一次调用了add_Push之后才有值的(这也印证了OnPush()方法定义中的对if(temp==null)的判断)。
  细心的朋友可能会问,这个add_Push方法是在IL代码中才生成的,那在写C#代码的时候,事件接收者是通过什么方式把事件处理方法挂载到委托链上的呢?别急,我们有比直接调用add_Push简单得多的方法,等下面介绍Panel类的时候您就知道了。
2.OnPush()和PushMe()
  首先要说的是,其实我们并不是一定要把它们写成两个方法。比如说,在我们这个简单的应用里面,完全可以不定义OnPush()方法,直接将PushMe()定义成如下形式:
ContractedBlock.gif ExpandedBlockStart.gif 改变后的PushMe
1None.gifpublic void PushMe()
2ExpandedBlockStart.gifContractedBlock.gifdot.gif{
3InBlock.gif    ButtonPushEventArgs args = new ButtonPushEventArgs(this.name);
4InBlock.gif
5InBlock.gif    EventHandler<ButtonPushEventArgs> temp = Push;
6InBlock.gif    if (temp != null)
7InBlock.gif    temp(this, args);
8ExpandedBlockEnd.gif}
但我们在实际的开发过程中,事件源很少是一个单一类型,而是一个类型体系。比如说Button类的下面可能又分为ImageButton和LinkedButton等,把委托链调用OnPush提出来放在基类里面,而具体的事件触发PushMe则放到每一个子类里面(每一个子类发送的消息可能有所不同),是我们更常采用的设计方式。
  具体在OnPush和PushMe的内部,代码都比较简单了。前面提到了委托链,接下来还要提供一个方法去触发这个委托链被调用的过程,OnPush完成的就是这样的功能,它里面就是对委托方法的调用。每一个委托方法被Invoke以后,都会自动去调用委托链下一节点的方法(这是.net在委托类型里内置的功能)。而PushMe是事件的触发位置,最重要的功能自然就是对事件消息的封装了(这也是为什么要把这个方法下放到各个子类的原因),封装完毕之后直接调用OnPush即可。

三.事件接收者
接收者要做的事情比事件源少多了,我们只需要把注意力集中在它如何把自己的事件处理方法挂载到事件源的委托链上即可(讲到现在,大家应该可以体会出了,对于事件的注册,是接收者主动;而对于事件的触发,则是事件源主动,是一个push模型)。先看一下Panel类的代码:
ContractedBlock.gif ExpandedBlockStart.gif Panel类
 1None.gifclass Panel
 2ExpandedBlockStart.gifContractedBlock.gifdot.gif{
 3InBlock.gif    private List<Button> bList = new List<Button>();
 4InBlock.gif
 5InBlock.gif    public void Add(Button b)
 6ExpandedSubBlockStart.gifContractedSubBlock.gif    dot.gif{
 7InBlock.gif        b.Push += this.WhenButtonPushed;
 8InBlock.gif        bList.Add(b);
 9ExpandedSubBlockEnd.gif    }

10InBlock.gif
11InBlock.gif    private void WhenButtonPushed(Object sender, ButtonPushEventArgs e)
12ExpandedSubBlockStart.gifContractedSubBlock.gif    dot.gif{
13InBlock.gif        Console.WriteLine(string.Format("The button named {0} is Pushed!", e.ButtonName));
14ExpandedSubBlockEnd.gif    }

15ExpandedBlockEnd.gif}
在这个类中,WhenButtonPushed是Panel定义的事件处理方法,而它的参数也完全满足在Button中的事件设定。而Add方法是用来把Button实例添加到Panel实例中,在这里我们最感兴趣的肯定是b.Push+=this.WhenButtonPushed。你可能已经想到了,当编译器看到这样一句语句的时候,会生成对add_Push调用的IL代码,而事实正是如此,我们一起来看看Panel.Add()方法编译出的IL代码:
ContractedBlock.gif ExpandedBlockStart.gif Panel.Add()的IL代码
 1None.gif.method public hidebysig instance void  Add(class EventInDotNet.Button b) cil managed
 2ExpandedBlockStart.gifContractedBlock.gifdot.gif{
 3InBlock.gif  // 代码大小       34 (0x22)
 4InBlock.gif  .maxstack  8
 5InBlock.gif  IL_0000:  nop
 6InBlock.gif  IL_0001:  ldarg.1
 7InBlock.gif  IL_0002:  ldarg.0
 8InBlock.gif  IL_0003:  ldftn      instance void EventInDotNet.Panel::WhenButtonPushed(object,
 9InBlock.gif                                                                           class EventInDotNet.ButtonPushEventArgs)
10InBlock.gif  IL_0009:  newobj     instance void class [mscorlib]System.EventHandler`1<class EventInDotNet.ButtonPushEventArgs>::.ctor(object,
11InBlock.gif                                                                                                                           native int)
12InBlock.gif  IL_000e:  callvirt   instance void EventInDotNet.Button::add_Push(class [mscorlib]System.EventHandler`1<class EventInDotNet.ButtonPushEventArgs>)
13InBlock.gif  IL_0013:  nop
14InBlock.gif  IL_0014:  ldarg.0
15InBlock.gif  IL_0015:  ldfld      class [mscorlib]System.Collections.Generic.List`1<class EventInDotNet.Button> EventInDotNet.Panel::bList
16InBlock.gif  IL_001a:  ldarg.1
17InBlock.gif  IL_001b:  callvirt   instance void class [mscorlib]System.Collections.Generic.List`1<class EventInDotNet.Button>::Add(!0)
18InBlock.gif  IL_0020:  nop
19InBlock.gif  IL_0021:  ret
20ExpandedBlockEnd.gif}
 // end of method Panel::Add

看到代码段的第12行您一定会感到非常亲切,它正是我们想看到的。

好,到现在为止,我们已经把一个有关.net事件的例子的各个重要部分都写完了,现在只要几句简单的调用,我们就能看到Event为我们带来的效果了。最后给出完整程序如下(包括上面提到的每一部分代码,可运行):

ContractedBlock.gif ExpandedBlockStart.gif 完整示例
  1None.gifusing System;
  2None.gifusing System.Collections.Generic;
  3None.gifusing System.Text;
  4None.gifusing System.Threading;
  5None.gif
  6None.gifnamespace EventInDotNet
  7ExpandedBlockStart.gifContractedBlock.gifdot.gif{
  8ExpandedSubBlockStart.gifContractedSubBlock.gif    /**//// <summary>
  9InBlock.gif    /// Target:提供一个展示.net事件框架的最简单示例
 10InBlock.gif    /// Author:EagleFish
 11InBlock.gif    /// Date:20070726
 12ExpandedSubBlockEnd.gif    /// </summary>

 13InBlock.gif    sealed class ButtonPushEventArgs : EventArgs
 14ExpandedSubBlockStart.gifContractedSubBlock.gif    dot.gif{
 15InBlock.gif        private readonly string buttonName;
 16InBlock.gif    public string ButtonName
 17ExpandedSubBlockStart.gifContractedSubBlock.gif        dot.gif{
 18InBlock.gif            get
 19ExpandedSubBlockStart.gifContractedSubBlock.gif            dot.gif{
 20InBlock.gif                return buttonName;
 21ExpandedSubBlockEnd.gif            }

 22ExpandedSubBlockEnd.gif        }

 23InBlock.gif
 24InBlock.gif        public ButtonPushEventArgs(string name)
 25ExpandedSubBlockStart.gifContractedSubBlock.gif        dot.gif{
 26InBlock.gif            buttonName = name;
 27ExpandedSubBlockEnd.gif        }

 28InBlock.gif       
 29ExpandedSubBlockEnd.gif    }

 30InBlock.gif
 31InBlock.gif    class Button
 32ExpandedSubBlockStart.gifContractedSubBlock.gif    dot.gif{
 33InBlock.gif        private string name;//我们没有提供这个成员变量的公有属性访问器,外界环境只能从事件引发的消息中
 34InBlock.gif                            //得到它的值。
 35InBlock.gif        
 36InBlock.gif        public Button(string name)
 37ExpandedSubBlockStart.gifContractedSubBlock.gif        dot.gif{
 38InBlock.gif            this.name = name;
 39ExpandedSubBlockEnd.gif        }

 40InBlock.gif
 41ExpandedSubBlockStart.gifContractedSubBlock.gif        /**//// <summary>
 42InBlock.gif        /// 事件源中的必备要素1:对事件的定义
 43ExpandedSubBlockEnd.gif        /// </summary>

 44InBlock.gif        public event EventHandler<ButtonPushEventArgs> Push;
 45InBlock.gif
 46ExpandedSubBlockStart.gifContractedSubBlock.gif        /**//// <summary>
 47InBlock.gif        /// 事件源中的必备要素2:定义一个方法,用来触发接收方的动作。
 48InBlock.gif        /// 如果事件源是一个类型系统的话,这个方法一般只需要在基类中进行定义
 49InBlock.gif        /// </summary>
 50ExpandedSubBlockEnd.gif        /// <param name="e"></param>

 51InBlock.gif        public void OnPush(ButtonPushEventArgs e)
 52ExpandedSubBlockStart.gifContractedSubBlock.gif        dot.gif{
 53InBlock.gif            EventHandler<ButtonPushEventArgs> temp = Push;
 54InBlock.gif            if (temp != null)
 55InBlock.gif                temp(this, e);
 56ExpandedSubBlockEnd.gif        }

 57InBlock.gif
 58ExpandedSubBlockStart.gifContractedSubBlock.gif        /**//// <summary>
 59InBlock.gif        /// 事件源中的必备要素3:对事件的触发(或者说,事件总是出现在事件源的某一个动作之后,这里定义的就是那个 动作)
 60ExpandedSubBlockEnd.gif        /// </summary>

 61InBlock.gif        public void PushMe()
 62ExpandedSubBlockStart.gifContractedSubBlock.gif        dot.gif{
 63InBlock.gif            ButtonPushEventArgs args = new ButtonPushEventArgs(this.name);
 64InBlock.gif             this.OnPush(args);
 65ExpandedSubBlockEnd.gif        }

 66ExpandedSubBlockEnd.gif    }

 67InBlock.gif
 68InBlock.gif    class Panel
 69ExpandedSubBlockStart.gifContractedSubBlock.gif    dot.gif{
 70InBlock.gif        private List<Button> bList = new List<Button>();
 71InBlock.gif
 72InBlock.gif        public void Add(Button b)
 73ExpandedSubBlockStart.gifContractedSubBlock.gif        dot.gif{
 74InBlock.gif            b.Push += this.WhenButtonPushed;
 75InBlock.gif            bList.Add(b);
 76ExpandedSubBlockEnd.gif        }

 77InBlock.gif
 78InBlock.gif        private void WhenButtonPushed(Object sender, ButtonPushEventArgs e)
 79ExpandedSubBlockStart.gifContractedSubBlock.gif        dot.gif{
 80InBlock.gif            Console.WriteLine(string.Format("The button named {0} is Pushed!", e.ButtonName));
 81ExpandedSubBlockEnd.gif        }

 82ExpandedSubBlockEnd.gif    }

 83InBlock.gif
 84InBlock.gif    class Program
 85ExpandedSubBlockStart.gifContractedSubBlock.gif    dot.gif{
 86InBlock.gif        static void Main(string[] args)
 87ExpandedSubBlockStart.gifContractedSubBlock.gif        dot.gif{
 88InBlock.gif            Button red = new Button("red");
 89InBlock.gif            Button green = new Button("green");
 90InBlock.gif            Panel p = new Panel();
 91InBlock.gif            p.Add(red);
 92InBlock.gif            p.Add(green);
 93InBlock.gif
 94InBlock.gif            red.PushMe();
 95InBlock.gif            Thread.Sleep(2000);
 96InBlock.gif            green.PushMe();
 97InBlock.gif            Thread.Sleep(2000);
 98ExpandedSubBlockEnd.gif        }

 99ExpandedSubBlockEnd.gif    }

100ExpandedBlockEnd.gif}

 




 

转载于:https://www.cnblogs.com/xingyukun/archive/2007/07/26/831868.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值