zoukankan      html  css  js  c++  java
  • 学习和理解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。
    • 适当的地方调用事件触发方法触发事件。
  • 相关阅读:
    codeforces 616B Dinner with Emma
    codeforces 616A Comparing Two Long Integers
    codeforces 615C Running Track
    codeforces 612C Replace To Make Regular Bracket Sequence
    codeforces 612B HDD is Outdated Technology
    重写父类中的成员属性
    子类继承父类
    访问修饰符
    方法的参数
    实例化类
  • 原文地址:https://www.cnblogs.com/wuyuxin/p/7017267.html
Copyright © 2011-2022 走看看