注:本文系学习笔记。

  上一篇文章记录了我对C#中委托的理解。委托实际上是一种类型。可以将一个或多个方法绑定到委托上面,调用委托时,一次执行委托上面绑定的方法。本文要讲述的事件实际上和委托有很深的“感情”。还是以上课的例子开始吧,假设距离上课时间前30分钟去教室上课。在距离上课前5分钟,会发生下面两件事:预备上课铃响,电子屏幕上显示上课时间。我们以下面的代码来表示模拟这个过程。

class Lesson{
    private int remainTime;//距离上课时间
    //课前动作
    private void PrepareLesson(){
         ;i>=;i--)
         {
              remainTime=i;
              )
              {
                  RingBell(remainTime);
                  DisplayLesson(remainTime);
              }
         }
     }
     //响铃
    private void RingBell(int remainTime){
         console.WriteLine("叮铃铃,距离上课还有 {0} 分钟,请同学们最好上课准备。",remainTime);
     }

     //屏幕显示准备上课    private void DisplayLesson(int remainTime){
         console.WriteLine("距离上课还有 {0} 分钟。",remainTime);
     }
}

class Program{
     static void main(){
          Lesson lesson=new Lesson();
          lesson.PrepareLesson();
     }
}

上面的代码很清楚,能够达到我们想要实现的效果。但是这样写并不好,假设学校期中考试期间,为了不打扰考试的考试,要求不能响铃,而考试结束后恢复响铃,这时候我们处理起来就比较麻烦。又或者我们的Lesson这个类表示课前准备工作,是表示上课前30分钟,我们学生完成的一些事情(假设还有其他事情,比如复习上节课内容,预习新知识等等)。把响铃和屏幕显示上课时间放在这个类里就会有点奇怪。根据面向对象原则,我们应该把响铃和屏幕显示单独放在各自的一个类里。代码修改如下:

public class Lesson{
     private int remainTime;
     private void PrepareLesson(){
         ;i>=;i--)
         {
            remainTime=i;
         }
     }
}

public class Bell{
      private void RingBell(int remainTime){

         console.WriteLine("叮铃铃,距离上课还有 {0} 分钟,请同学们最好上课准备。",remainTime);

     }}

public class Display{

      private void DisplayLesson(int remainTime){

         console.WriteLine("距离上课还有 {0} 分钟。",remainTime);

     }}

这样就可以了,但是现在,如何让在距离上课时间不到5分钟的时候,响铃和屏幕显示准备上课呢。这里用到ObServer设计模式。这里简单举个例子说明ObServer设计模式,中国移动有提供每月话费账单、流量账单之类的查询业务。但是并不是每个人都需要它推送这样的消息。有的人可能不需要查询,有的人可能只关心话费账单,有的人可能只关心流量问题,有的人可能两者都需要。那么移动公司具体是如何为每个人提供他所需要的服务呢?当然是根据用户订阅的种类,用户关心的什么,就发送什么。Observer设计模式与此类似,它包含两类对象。

  1. Subject:监视对象,它包含着其他对象所感兴趣的内容。在本范例中,上课就是一个监视对象,它包含的其他对象所感兴趣的内容,就是remainTime字段,当这个字段的值小于等于5时,会不断把数据发给监视它的对象。
  2. Observer:监视者,它监视Subject,当Subject中的某件事发生的时候,会告知Observer,而Observer则会采取相应的行动。在本范例中,Observer有铃铛和屏幕显示器,它们采取的行动分别是响铃和显示上课准备。

Observer设计模式:Observer设计模式是为了定义对象间的一种一对多的依赖关系,以便于当一个对象的状态改变时,其他依赖于它的对象会被自动告知并更新。Observer模式是一种松耦合的设计模式。

     下面继续修改代码

上例可见,事件实际上就是一个委托。

public class Lesson{
     private int remainTime;
     public delegate void PrepareHandler(int remainTime);
     public event PrepareHandler PrepareEvent;
     private void PrepareLesson(){
         ;i>=;i--)
         {
            remainTime=i;
            )
            {
                if(PrepareEvent!=null)
                {
                     PrepareEvent(remainTime);
                }
            }
         }
     }
}

public class Bell{
      private void RingBell(int remainTime){
         console.WriteLine("叮铃铃,距离上课还有 {0} 分钟,请同学们最好上课准备。",remainTime);
     }
}

public class Display{
      private static void DisplayLesson(int remainTime){
         console.WriteLine("距离上课还有 {0} 分钟。",remainTime);
     }
}

class Program{
     static void main(){
          Lesson lesson=new Lesson();
          lesson.PrepareEvent+=(new Bell()).RingBell;
          lesson.PrepareEvent+=Display.DisplayLesson;
          lesson.PrepareLesson();
     }
}

那么事件跟委托有什么区别呢,上篇文章介绍了,委托必须初始化之后才能添加绑定的方法,而上面的代码我们可以看到直接给事件添加绑定方法。这是因为事件是一个封装了的委托,.NET框架实际上在编译的时候已经为时间做了初始化。上面事件的用法与我们见到的.NET中的事件形式上不同,实际上.NET Framework中的事件模型是规范化了的,.NET事件的编码规范如下

  • 委托类型的名称都应该以EventHandler结束。
  • 委托的原型定义:有一个void返回值,并接受两个输入参数:一个Object 类型,一个 EventArgs类型(或继承自EventArgs)。
  • 事件的命名为 委托去掉 EventHandler之后剩余的部分。
  • 继承自EventArgs的类型应该以EventArgs结尾。

那么我们继续修改我们的代码,让它遵循规范

class Lesson{
      private int remainTime;
      public delegate void PrepareEventHandler(Object sender,PrepareEventArgs e);
      public Event PrepareEventHandler Prepare;

      public class PrepareEventArgs:EventArgs{
              public readonly int remainTime;
              public PrepareEventArgs(int remainTime){
                       this.remainTime=remainTime;
              }
      }

      protected virtual void OnPrepare(PrepareEventArgs e){
              if (Prepare!=null)
              {
                   Prepare(this,e)
              }
      }

      public void PrepareLesson(){
             ;i>=;i--)
             {
                 remainTime=i;
                 )
                 {
                     PrepareEventArgs e=new PrepareEventArgs(remainTime);
                     OnPrepare(e)
                 }
              }
       }

public class Bell{
       public void RingBell(Object sender,Lesson.PrepareEventArgs e){
              Lesson lesson=(Lesson)sender;
              console.WriteLine("叮铃铃,距离上课还有 {0} 分钟,请同学们最好上课准备。",e.remainTime);
       }
}

public class Display{
        public static void DisplayLesson(Object sender,Lesson.PrepareEventArgs e){
               Lesson lesson=(Lesson)sender;
               console.WriteLine("距离上课还有 {0} 分钟。",e.remainTime);
        }
}

class Program{
       static void main(){
             Lesson lesson=new Lesson();
             Bell bell=new Bell();
             lesson.Prepare+=bell.RingBell;
             lesson.Prepare+=Display.DiaplayLesson;
             lesson.PrepareLesson();
       }
}
      

最后总结一下:C#中的事件处理实际上是一种具有特殊签名的delegate,它是将委托进行封装,不允许直接方位委托本身,只能通过给委托添加和移除绑定的方法。(+=、-=实际上是调用了add 和 remove方法)像下面这个样子:

public delegate void MyEventHandler(object sender, MyEventArgs e);

其中的两个参数,sender代表事件发送者,e是事件参数类。MyEventArgs类用来包含与事件相关的数据,所有的事件参数类都必须从System.EventArgs类派生。当然,如果你的事件不含参数,那么可以直接用System.EventArgs类作为参数。

就是这么简单,结合delegate的实现,我们可以将自定义事件的实现归结为以下几步:

  • 定义delegate对象类型,它有两个参数,第一个参数是事件发送者对象,第二个参数是事件参数类对象。
  • 定义事件参数类,此类应当从System.EventArgs类派生。如果事件不带参数,这一步可以省略。
  • 定义事件处理方法,它应当与delegate对象具有相同的参数和返回值类型。
  • 用event关键字定义事件对象,它同时也是一个delegate对象。
  • 用+=操作符添加事件到事件队列中(-=操作符能够将事件从队列中删除)。
  • 需要触发事件的地方用调用delegate的方式写事件触发方法。一般来说,此方法应为protected访问限制,既不能以public方式调用,但可以被子类继承。名字是OnEventName。
  • 适当的地方调用事件触发方法触发事件。

学习和理解C#中的事件的更多相关文章

  1. 怎么理解js中的事件委托

    怎么理解js中的事件委托 时间 2015-01-15 00:59:59  SegmentFault 原文  http://segmentfault.com/blog/sunchengli/119000 ...

  2. JDK学习---深入理解java中的HashMap、HashSet底层实现

    本文参考资料: 1.<大话数据结构> 2.http://www.cnblogs.com/dassmeta/p/5338955.html 3.http://www.cnblogs.com/d ...

  3. JDK学习---深入理解java中的LinkedList

    本文参考资料: 1.<大话数据结构> 2.http://blog.csdn.net/jzhf2012/article/details/8540543 3.http://blog.csdn. ...

  4. 一个demo让你彻底理解Android中触摸事件的分发

    注:本文涉及的demo的地址:https://github.com/absfree/TouchDispatch 1. 触摸动作及事件序列 (1)触摸事件的动作 触摸动作一共有三种:ACTION_DOW ...

  5. 【Swing】理解Swing中的事件与线程

    talk is cheap , show me the code. Swing中的事件 事件驱动 所有的GUI程序都是事件驱动的.Swing当然也是. GUI程序不同于Command Line程序,一 ...

  6. 理解JavaScript中的事件轮询

    原文:http://www.ruanyifeng.com/blog/2014/10/event-loop.html 为什么JavaScript是单线程 JavaScript语言的一大特点就是单线程,也 ...

  7. 【jQuery基础学习】03 jQuery中的事件与动画

    关于jQuery中的事件 js与HTML之间的交互是通过用户和浏览器操作页面时引发的事件来处理的. jQuery增加并扩展了基本的事件处理机制,jQuery不仅提供了更加优雅的事件处理方法,而且极大地 ...

  8. 理解Javascript中的事件绑定与事件委托

    最近在深入实践js中,遇到了一些问题,比如我需要为动态创建的DOM元素绑定事件,那么普通的事件绑定就不行了,于是通过上网查资料了解到事件委托,因此想总结一下js中的事件绑定与事件委托. 事件绑定   ...

  9. 再次理解javascript中的事件

    一.事件流的概念 + 事件流描述的是从页面中接收事件的顺序. 二.事件捕获和事件冒泡 +    事件冒泡接收事件的顺序:

随机推荐

  1. Fast scroller styles

    <!-- Fast scroller styles --> <!-- Drawable to use as the fast scroll thumb. --> <att ...

  2. android SharedPreferences 使用

    除了SQLite数据库外,SharedPreferences也是一种轻型的数据存储方式,它的本质是基于XML文件存储key-value键值 对数据,通常用来存储一些简单的配置信息.其存储位置在/dat ...

  3. regsvr32提示模块加载失败 请确保二进制

    微软官方的一部分解释 https://support.microsoft.com/en-us/kb/249873 关于32位和64位的说明 http://csi-windows.com/blog/al ...

  4. struts采用JavaServlet/JSP技术,实现了基于Java EEWeb应用的MVC设计模式的应用框架

    今天我用Ecipse搭建Struts框架,并且使用Struts框架编写一个最简单的例子,相信读者能够很容易的明白. Struts是当今Java比较流行的三大框架之一,三大框架是Struts,sprin ...

  5. 结构体 typedef struct hash_cell_struct hash_cell_t;

    typedef struct hash_cell_struct hash_cell_t; struct hash_cell_struct{ void* node; /*!< hash chain ...

  6. POJ 2398 Toy Storage

    这道题和POJ 2318几乎是一样的. 区别就是输入中坐标不给排序了,=_=|| 输出变成了,有多少个区域中有t个点. #include <cstdio> #include <cma ...

  7. bzoj1058: [ZJOI2007]报表统计

    set.操作:insert(u,v)在u后面插入v,若u后面已插入过,在插入过的后面插入.mingap求出序列两两之间差值的最小值.minsortgap求出排序后的序列两两之间的最小值.用multis ...

  8. Cacti 'graph_xport.php' SQL注入漏洞

    漏洞版本: Cacti < 0.8.8b 漏洞描述: Bugtraq ID:66555 Cacti是一套基于PHP,MySQL,SNMP及RRDTool开发的网络流量监测图形分析工具. Cact ...

  9. 导出Excel帮助类

    using System; using System.Collections.Generic; using System.Text; using System.Data; using System.D ...

  10. 使用 Apache MINA2 实现 Web 系统的消息中间件

    本文将介绍如何使用 Apache MINA2(以下简称 MINA2)解决复杂 Web 系统内各子系统之间同步消息中间件的问题.MINA2 为开发高性能和高可用性的网络应用程序提供了非常便利的框架.从本 ...