也谈事件(Event)

最近园子里发表了一些讨论“事件(Event)”的文章,我也来凑个热闹,谈谈我对事件的一些粗浅的认识。本文不谈设计模式(观察者模式),只从运行时的角度来分析事件这个对象到底是个什么东西,它有那么神秘吗?为了更好的分析事件,本文将会编写一些例子来模拟事件的订阅机制。本文对事件的分析可以概括为下面三句话:

一、Delegate = Object + MethodInfo

其实你完全可以通过Reflector这样的工具来看Delegate类型是如何定义的。在这里,我们只关注Delegate本质的东西,即Delegate最终是如果执行的。为此,我创建了下面一个简单的MyDelegate类型来模拟Delegate

   1: public class MyDelegate
   2: {           
   3:  
   4:     public object Target { get; private set; }
   5:     public MethodInfo Method { get; private set; }       
   6:  
   7:     public MyDelegate(object target, MethodInfo method)
   8:     {
   9:         this.Target = target;
  10:         this.Method = method;
  11:     }      
  12:  
  13:     public virtual void Invoke(params object[] args)
  14:     {
  15:         this.Method.Invoke(this.Target, args);           
  16:     }      
  17: }

从上面的定义可以看到,MyDelegate只有两个属性:Object类型的Target和MethodInfo类型的Method。委托的执行通过需方法Invoke完成,具体来说,最终的执行通过反射的方式调用Method的Invoke方法完成。

二、MulticastDelegate对象多个Delegate对象的链表

其实我们平时讲的委托,并不是一个单个的Delegate对象,实际上是一个委托链,这样一个委托链通过MulticastDelegate定义。由于定义也相对复杂,我们同样通过定义模拟类型来反映其本质的东西。为此,我创建了如下一个MyMulticastDelegate类型。

   1: public class MyMulticastDelegate : MyDelegate
   2: {
   3:     public MyMulticastDelegate Next { get; set; }
   4:  
   5:     public MyMulticastDelegate(object target, MethodInfo method)
   6:         : base(target, method)
   7:     { }
   8:  
   9:     public override void Invoke(params object[] args)
  10:     {
  11:         base.Invoke(args);
  12:         if (null != Next)
  13:         {
  14:             this.Next.Invoke(args);
  15:         }
  16:     }
  17: }

MyMulticastDelegate继承自上面定义的MyDelegate类型,在此基础上定义了一个额外的属性Next,代表委托链中当前委托对象的下一个委托。最后,Invoke方法被重写:按照委托链的顺序依次执行每一个委托对象。

三、事件本质上是一个MulticastDelegate对象

我们使用的事件一般通过EventHandler或者System.EventHandler<TEventArgs>表示,其本质来时一个通过MulticastDelegate对象表示的委托链。事件注册本质就是将另外一个委托(链)连到当前委托链上。下面定义的类型MyEventHandler模拟了事件的实现。

   1: public class MyEventHandler : MyMulticastDelegate
   2: {
   3:     public MyEventHandler(object target, MethodInfo method)
   4:         : base(target, method)
   5:     { }        
   6:  
   7:     public void Fire(object sender,EventArgs args)
   8:     {
   9:         this.Invoke(sender,args);
  10:     }
  11:  
  12:     public static MyEventHandler operator +(MyEventHandler current, MyEventHandler next)
  13:     {
  14:         if (null == current)
  15:         {
  16:             return next;
  17:         }
  18:  
  19:         MyMulticastDelegate terminator = current;
  20:         while (null != terminator.Next)
  21:         {
  22:             terminator = terminator.Next;
  23:         }
  24:  
  25:         terminator.Next = next;
  26:         return current;
  27:     }
  28:  
  29:     public static implicit operator MyEventHandler(EventHandler eventHandler)
  30:     {
  31:         return new MyEventHandler(eventHandler.Target, eventHandler.Method);
  32:     }
  33: }

事件一般通过+=操作符进行注册,其本质就是将两个委托链相连。为此,在MyEventHandler中,我也重载了操作符+。事件的触发被定义在Fire方法中,其实现就是调用MyMulticastDelegate的Invoke方法。此外,我还定义一个隐式类型转换操作符,将EventHandler转对象化成MyEventHandler类型。

四、事件的订阅

现在我通过一个具体的例子来说明通过上面定义的MyEventHandler来模拟具体的事件注册和触发。在这里,我们模拟的是Button的Click事件,为此我采用标准的事件编程方式定义了如下一个Button类型。MyEventHandler类型的Click属性代表事件本身,Click操作的触发通过执行PerformClick方法完成。进一步地,Click操作的处理实现在虚方法OnClick中,其本质就是调用MyEventHandler的Fire方法。

   1: public class Button
   2: {
   3:     public string Id { get; private set; }
   4:     public MyEventHandler Click { get; set; }
   5:  
   6:     public Button(string id)
   7:     {
   8:         this.Id = id;
   9:     }
  10:  
  11:     protected virtual void OnClick(EventArgs args)
  12:     {
  13:         if(null != this.Click)
  14:         {
  15:             this.Click.Fire(this, args);
  16:         }
  17:     }
  18:  
  19:     public void PerformClick()
  20:     {
  21:         this.OnClick(EventArgs.Empty);
  22:     }
  23: } 

接下来,我们创建另一个模拟订阅Button对象的Click事件的类型,这样一个简单的类型Foo定义如下。当订阅的Click事件触发之后,会回调DoSomethingOnceClick方法,方法会在控制台上输出一段文字。

   1: public class Foo
   2: {
   3:     public void DoSomethingOnceClick(object sender, EventArgs args)
   4:     { 
   5:         Button btn = sender as Button;
   6:         if(null != btn)
   7:         {
   8:             Console.WriteLine("Click {0}", btn.Id);
   9:         }
  10:     }
  11: }

那么最终的事件订阅和触发编写在下面代码中:在创建的Button对象中,进行了6次相同的事件注册,最终通过PerformClick方法触发事件。由于在MyEventHandler定义一个从EventHandler到MyEventHandler类型的隐式转换操作符,所以我们进行事件注册和传统的方式别无二致。

   1: class Program
   2: {
   3:     static void Main(string[] args)
   4:     {
   5:         var btn1 = new Button("Button1");
   6:         var foo = new Foo();
   7:         btn1.Click += new EventHandler(foo.DoSomethingOnceClick);
   8:         btn1.Click += new EventHandler(foo.DoSomethingOnceClick);
   9:         btn1.Click += new EventHandler(foo.DoSomethingOnceClick);
  10:         btn1.Click += new EventHandler(foo.DoSomethingOnceClick);
  11:         btn1.Click += new EventHandler(foo.DoSomethingOnceClick);
  12:         btn1.Click += new EventHandler(foo.DoSomethingOnceClick);
  13:         btn1.PerformClick();
  14:     } 
  15: }

下面是最后的输出结果:

Click Button1
Click Button1
Click Button1
Click Button1
Click Button1
Click Button1

本文提供的例子,你可以通过这里下载,关于事件相关的内容,我还有一篇相关的文章《如何编写没有Try/Catch的程序》,仅供参考。

作者:蒋金楠
微信公众账号:大内老A
微博:www.weibo.com/artech
如果你想及时得到个人撰写文章以及著作的消息推送,或者想看看个人推荐的技术资料,可以扫描左边二维码(或者长按识别二维码)关注个人公众号(原来公众帐号蒋金楠的自媒体将会停用)。
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

原文链接

时间: 2024-08-07 22:33:36

也谈事件(Event)的相关文章

web组件的通信---浅谈事件

web web组件的通信---浅谈事件大家目前开发asp.net程序经常会用到usercontrols即pagelet(.ascx文件).有不少人现在把它当做asp里的include来使用.当然这也是很自然的,但一旦考虑到user controls和他所处的容器web form之间或其他user controls之间的通信,事情好像就比较复杂了.比如说,我有这样一个页面                    User Control(Image Button)User Control(Left

Qt学习之路(19):事件(event)

前面说了几个标准对话框,下面不打算继续说明一些组件的使用,因为这些使用很难讲完,很多东西都是与实际应用相关的.实际应用的复杂性决定了我们根本不可能把所有组件的所有使用方法都说明白.这次来说说Qt相对高级一点的特性:事件. 事件(event)是有系统或者Qt本身在不同的时刻发出的.当用户按下鼠标,敲下键盘,或者是窗口需要重新绘制的时候,都会发出一个相应的事件.一些事件是在对用户操作做出响应的时候发出,如键盘事件等:另一些事件则是由系统自动发出,如计时器事件. 一般来说,使用Qt编程时,我们并不会把

【智能合约】客户端和web端对智能合约的事件Event进行调用的代码示例

客户端和web端对智能合约的事件Event进行调用的代码示例 web truffle 按官网的例子 http://truffleframework.com/boxes/pet-shop truffle作为一个运行测试框架,用的也是web3对智能合约进行调用. 文件所在的位置src/js/app.js initWeb3: function() { // web3入口 if (typeof web3 !== 'undefined') { App.web3Provider = web3.current

从海尔砸冰箱事件浅谈事件营销

中介交易 http://www.aliyun.com/zixun/aggregation/6858.html">SEO诊断 淘宝客 云主机 技术大厅 对于一家公司来讲,诚信和品质是消费者最关注的了,如果本着诚信的责任,那么,其用户群体一定是非常的广泛.不知道大家对于85年海尔"砸冰箱"事件是否记得,这是一次公司对于质量的考核让很多人认识了"海尔"这个品牌,同时让海尔至今在消费群体中占到了举足轻重的位置.所以,在今天,我们不仅在回顾这个品牌,更要从这个

KnockoutJS 3.X API 第四章之事件event绑定_javascript技巧

目的 event绑定即为事件绑定,即当触发相关DOM事件的时候回调函数.例如keypress,mouseover或者mouseout等 例如: Mouse over me 源码: <div> <div data-bind="event: { mouseover: enableDetails, mouseout: disableDetails }"> Mouse over me </div> <div data-bind="visibl

C#总结(二)事件Event 介绍总结

最近在总结一些基础的东西,主要是学起来很难懂,但是在日常又有可能会经常用到的东西.前面介绍了 C# 的 AutoResetEvent的使用介绍, 这次介绍事件(event). 事件(event),对于初学者来说,确实比较神秘,难懂.但是在日常编程过程中却经常遇到.事件使用得当,会让你的代码更加整洁,也能少些很多代码.   一.Event事件,是一种封装过的委托. 它拥有以下三要素: 1. 事件发行者:达到某些条件时激发事件的对象 2. 事件订阅者:订阅事件并对事件发生时进行处理的对象 3. 定义

事件(Event),绝大多数内存泄漏(Memory Leak)的元凶[上篇]

最近这两天一直在忙着为一个项目检查内存泄漏(Memory Leak)的问题,对相关的知识进行了一下简单的学习和探索,其间也有了一些粗浅的经验积累,今天特意写一篇相关的文章与大家分享.那些对内存泄漏稍微有点了解的人,对于本篇文章的标题,相信不会觉得是在危言耸听.就我查阅的资料,已经这两天的发现也证实了这一点:觉得部分的内存泄漏问题与事件(Event)有关.本篇文章将会介绍其原理,以及如何发现和解决由事件导致的内存泄漏问题. 为了让读者首先对这个主题有一个感官的印象,让大家觉得内存泄漏问题离我们并不

事件(Event),绝大多数内存泄漏(Memory Leak)的元凶[下篇] (提供Source Code下载)

在上篇中我们谈到:将一个生命周期较短的对象(对象A)注册到一个生命周期较长(对象B)的某个事件(Event)上,两者便无形之间建立一个引用关系(B引用A).这种引用关系导致GC在进行垃圾回收的时候不会将A是为垃圾对象,最终使其常驻内存(或者说将A捆绑到B上,具有了和B一样的生命周期).这种让无用的对象不能被GC垃圾回收的现象,在托管环境下就是一种典型的内存泄漏问题.我们今天将会着重解释其背后的原因.[本篇文章的Source Code从这里下载) 一.CLR垃圾回收简介 在一个托管应用程序中,我们

JS事件Event元素(兼容IE,Firefox,Chorme)_javascript技巧

好的程序往往是兼容多种浏览器的. 看下例: 一个简单的button,我们可以通过点击下面的button直接通过event对象得到当前的button元素,兼容多浏览器.当然,其他元素事件的写法同. <input type="button" id="btn" name="btn" value="button1" onclick="getEvent(event)"/>