• 学习和理解C#中的事件


      注:本文系学习笔记。

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

    复制代码
    class Lesson{
        private int remainTime;//距离上课时间
        //课前动作
        private void PrepareLesson(){
             for(int i=30;i>=0;i--)
             {
                  remainTime=i;
                  if(i<=5)
                  {
                      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(){
             for(int i=30;i>=0;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(){
             for(int i=30;i>=0;i--)
             {    
                remainTime=i;   
                if(i<=5)
                {
                    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(){
                 for(int i=30;i>=0;i--)
                 {
                     remainTime=i;
                     if(remainTime<=5)
                     {
                         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。
    • 适当的地方调用事件触发方法触发事件。
  • 相关阅读:
    由老赵反对青鸟想到的——关于自学编程的讨论
    蛙蛙推荐:《代码大全》第45章读书笔记
    大家来找错自己写个正则引擎(二)构建抽象模式树
    大家来找错自己写个正则引擎(五)检查表及总结
    大家来找错自己写个正则引擎(一)概要介绍
    大家来找错自己写个正则引擎(三)构建正则解析树及分词
    蛙蛙推荐:《代码大全》1至3章读书笔记
    sql for xml path用法(转) dodo
    sql语句总结一 dodo
    System.Management.ManagementException: 访问遭到拒绝的解决方案 dodo
  • 原文地址:https://www.cnblogs.com/wuyuxin/p/7017267.html
Copyright © 2020-2023  润新知