zoukankan      html  css  js  c++  java
  • 也谈事件(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的程序》,仅供参考。

    作者:Artech
    出处:http://artech.cnblogs.com
    本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
  • 相关阅读:
    激光雷达slam之LOAM中的坐标转换与IMU融合
    记录一次失败的coding面
    因子图相关理论汇总
    [ICP]手推SVD方法
    SLAM中的卡方分布
    第五篇 openvslam建图与优化模块梳理
    第四篇 跟踪过程以及openvslam中的相关实现详解
    第三篇 视觉里程计(VO)的初始化过程以及openvslam中的相关实现详解
    第二篇 特征点匹配以及openvslam中的相关实现详解
    第一篇 特征提取以及openvslam中的相关实现详解
  • 原文地址:https://www.cnblogs.com/artech/p/InsideEvent.html
Copyright © 2011-2022 走看看