zoukankan      html  css  js  c++  java
  • C#委托、事件、消息(入门级)(续)

      上次我们简要且肤浅地介绍了委托,我们复习一下吧:委托是方法的类型,委托是和类同级的概念,使用delegate关键字声明,每一个委托都代表了具有相同参数和返回值的一类方法,委托并不是为了代表某一个方法而存在的,它并不关心其实例的具体实现和造成结果、访问修饰等等。

      我们现在来略微深入地学习一下委托:我们主要介绍委托的可以代表多个同类方法的特性。

      比如以下示例: 

     1 using System;
    2
    3 namespace DelegateEG
    4 {
    5
    6 // 委托
    7 public delegate void ExampleHandler();
    8
    9 class Program
    10 {
    11 // 静态成员
    12 static ExampleHandler example;
    13
    14 static void Main(string[] args)
    15 {
    16 // 引用也是一个类型,为增强代码的刻度性,最好使用写法1
    17        // 写法1:
    18 example = new ExampleHandler(ShowMsg1);
    19
    20 // 写法2:
    21        // example = ShowMsg1;
    22
    23        // 写法3,不写=或者使用构造方法
    24        // example += new ExampleHandler(ShowMsg1);
    25        // 或者
    26        // example += ShowMsg1;
    27
    28        // 在为委托绑定方法
    29 example += ShowMsg2;
    30
    31 // 调用委托
    32 example();
    33 Console.ReadKey();
    34
    35 // 输出:
    36 /*这是测试方法1
    37 *这是测试方法2
    38         */
    39 }
    40
    41 // 测试方法1
    42 static void ShowMsg1()
    43 {
    44 Console.WriteLine("这是测试方法1");
    45 }
    46
    47 // 测试方法2
    48 static void ShowMsg2()
    49 {
    50 Console.WriteLine("这是测试方法2");
    51 }
    52 }
    53 }

      这里有一点需要注意:委托的方法调用是一次调用所有的已绑定方法,而且是同时的。这个可能在事件机制处理上我会举例详细解释。  

      我再最后一次梳理一下委托的概念:委托是一种类型,区分类类型、枚举类型等。委托是方法的类型,就如同类是对象的类型一样。每一种委托都代表了具有某种特定参数类型和数量,具有某种特定返回值的方法。该中委托的实例可以绑定一个或多个方法,方法必须是该委托能代表的,方法的参数和返回值必须受委托的完整约束。委托实例被调用执行时会调用它所绑定的所有方法。

      那么基于已有的关于委托的知识,我们来学习一下事件。

      关于事件的定义,我们先不说,时机到了再说。

      我们先说事件为什么存在,它的理由是什么?

      记住:事件是为了封装委托而存在的! 

      借助下面的几个例子,可能你对事件的理解会更加深入,至少不会停留在只会双击写代码的地步。

      还记得我们我们在学生打招呼中怎么封装委托的吗?(我道歉一下,当时没有考虑,可能初学者不容易懂)

     1      // 委托成员,我们用它来保存具体的打招呼的方法
    2 private SayHiHandler _sayHi;
    3 // 封装委托,让外界通过SayHi访问_syHi
    4      // 由于委托是方法的类型,所以我们可以将它当方法用
    5      // 在外界访问它之前将它绑定到具体方法
    6 public SayHiHandler SayHi
    7 {
    8 get
    9 {
    10 switch (Language)
    11 {
    12 case LanguageType.Chinese:
    13 _sayHi = ChineseSayHi;
    14 break;
    15 case LanguageType.English:
    16 _sayHi = new SayHiHandler(EnglishSayHi);
    17 break;
    18 // ……
    19
    20 default:
    21 break;
    22 }
    23 return _sayHi;
    24 }
    25 }

      那么我们是怎么调用的呢?

     1 public class Program
    2 {
    3 public static void Main(string[] args)
    4 {
    5 // 使用默认值访问打招呼的方法
    6 new Student().SayHi();
    7
    8 // 使用自定义的内容打招呼
    9 new Student { Name = "阿何", Language = LanguageType.English }.SayHi();
    10 Console.ReadKey();
    11
    12 /*
    13 *
    14 * 输出 大家早上好! 我是Johness
    15 * Morning! I'm 阿何
    16 *
    17        */
    18 }
    19 }

      我们说,在委托变量的内部,我们可以set,get,invoke(设置、获得、调用)委托。那么我们如果要求委托变量的访问权限受到限制,在外部不能调用只能设置呢?

      可能有的朋友要问:为什么要让外界不能调用,还有,就算是要使外界不能调用也可以直接用没有get的访问器吧。

      实际上,我们并不希望外界调用不属于它的委托,我们值希望外界将方法绑定到委托,而真正调用委托的对象应该是该委托真正属于的对象,它会在适当的时机调用委托。

      举一个简单的例子:我们在写窗体代码的时候写的是事件,不知道大家有没有观察:我们写好的事件(其实是方法)被绑定到了控件属于的委托,那么,我们在窗体代码写的方法,并不是在方法将要绑定到的控件,而且我们指定了什么时候执行委托吗?没有,我们也不可能执行……

      也就是说:一个带有委托的对象,它希望外界可以向它的委托注册(或绑定方法),但是它会保留什么时候执行该委托的权利,这一点并不为外界所知。原因在于:我们并不知道该委托什么时间该发生(比如鼠标点击,只有Windows才知道)。

      (为了方便讲解事件,上述及即将介绍的都只是委托的使用之一。意思是,委托在做事件这方面才是要求外面只能set)

      我们可以利用普通封装来保证上面要求的实现:

     1 using System;
    2
    3 namespace DelegateEG
    4 {
    5 public class Program
    6 {
    7 public static void Main(string[] args)
    8 {
    9 Student stu = new Student();
    10 // 注意,这里的=即委托的+=
    11 stu.Test = new TestHandler(bMethod);
    12
    13 // 通过调用方法让对象在内部调用Test
    14 stu.OnTest();
    15 Console.ReadKey();
    16 /*
    17 * 输出:
    18 *
    19 * 这是委托所在对象的方法
    20 * 这是Program中的静态方法
    21 */
    22 }
    23
    24 public static void bMethod()
    25 {
    26 Console.WriteLine("这是Program中的静态方法");
    27 }
    28 }
    29
    30
    31 // 委托
    32 public delegate void TestHandler();
    33
    34 class Student
    35 {
    36 // 委托和其封装
    37 private TestHandler _test;
    38 public TestHandler Test
    39 {
    40 // 没有get
    41
    42        // 注意:此处set里默认是但设置,我们略作修改
    43        // 默认为 set { _test = value; }
    44 set { _test += value; }
    45 }
    46
    47 // 调用委托
    48 public void OnTest()
    49 {
    50 this._test();
    51 }
    52
    53 // 测试方法
    54 public void aMethod()
    55 {
    56 Console.WriteLine("这是委托所在对象的方法");
    57 }
    58
    59 // 构造方法
    60 public Student()
    61 {
    62 Test = new TestHandler(aMethod);
    63 }
    64 }
    65 }

      实际上在WinForm事件处理时,真正调用委托的就是On的一类方法,但是这一般也不会被外界处理。

      这样说吧,外界值关心当某个事件发生(在某个情况下)时要委托处理什么,而委托所在对象只关心它什么时候该工作。

      我们就是外界。

      比如,学生看到老师是一个委托,这个委托代表了向老师问好的一类方法,当发生了学生遇到老师,这个时候委托工作。我们可以事先告诉学生:你应该说“老师好”。我们就给学生看到老师这个委托绑定了方法,但是我们不知道学生什么时候会碰到老师,但是学生是关心的,他遇到老师之后就会执行我们的方法。这个时间学生也不能掌握,但是他有执行或者不执行的权利。我们只有简单的权利:教他怎么向老师问好。

      那么以上过程在代码中使用普通委托和封装可以如下写法:

     1 using System;
    2
    3 namespace DelegateEG
    4 {
    5
    6 public delegate void SayHiToTeacherHandler();
    7
    8 class Student
    9 {
    10 private SayHiToTeacherHandler _sayHiToTeacher;
    11 public SayHiToTeacherHandler SayHiToTeacher
    12 {
    13 set { _sayHiToTeacher += value; }
    14 }
    15
    16 public void OnMeetTeacher()
    17 {
    18 if (_sayHiToTeacher != null)
    19 {
    20 _sayHiToTeacher();
    21 }
    22 }
    23 }
    24
    25 public class Program
    26 {
    27 public static void Main(string[] args)
    28 {
    29 Student stu1 = new Student();
    30 stu1.SayHiToTeacher = new SayHiToTeacherHandler(SayHi);
    31
    32 Console.ReadKey();
    33 }
    34
    35 public static void SayHi()
    36 {
    37 Console.WriteLine("遇到老师了,向老师问好:老师好");
    38 }
    39 }
    40 }

      首先,我们定义学生类,该对象拥有向老师问好的方法,但是由于要应付可能的变化,我们不用方法而使用委托。

    1    // 委托
    2 public delegate void SayHiToTeacherHandler();
    3
    4 private SayHiToTeacherHandler _sayHiToTeacher;
    5 public SayHiToTeacherHandler SayHiToTeacher
    6 {
    7 set { _sayHiToTeacher += value; }
    8 }

      我们告诉学生应该怎么向老师问好:

     1 public class Program
    2 {
    3 public static void Main(string[] args)
    4 {
    5 Student stu1 = new Student();
    6 stu1.SayHiToTeacher = new SayHiToTeacherHandler(SayHi);
    7
    8 Console.ReadKey();
    9 }
    10
    11 public static void SayHi()
    12 {
    13 Console.WriteLine("遇到老师了,向老师问好:老师好");
    14 }
    15 }

      遇到老师的时候,问好:

    1      public void OnMeetTeacher() 
    2 {
    3 if (_sayHiToTeacher != null)
    4 {
    5 _sayHiToTeacher();
    6 }
    7 }

      在强调一遍:这个方法发生的时间是学生和我们都没办法知道的。

      那么,以上的写法很烦是吗?我们有简写吗,呵呵,答案是肯定的。

      当当当,Look:

     1 using System;
    2
    3 namespace DelegateEG
    4 {
    5 public delegate void SayHiToTeacherHandler();
    6
    7 class Student
    8 {
    9 // 修改一
    10 public event SayHiToTeacherHandler SayHiToTeacher;
    11
    12 public void OnMeetTeacher()
    13 {
    14 SayHiToTeacher.Invoke(); // 事件调用
    15        // SayHiToTeacher();
    16 }
    17 }
    18
    19 public class Program
    20 {
    21 public static void Main(string[] args)
    22 {
    23 Student stu1 = new Student();
    24 // 修改二
    25 stu1.SayHiToTeacher += new SayHiToTeacherHandler(SayHi);
    26
    27 Console.ReadKey();
    28 }
    29
    30 public static void SayHi()
    31 {
    32 Console.WriteLine("遇到老师了,向老师问好:老师好");
    33 }
    34 }
    35 }

      大家仔细看看,我改的两个地方,自己领悟一下。

      event关键字用于封装委托,封装之后是一个“事件”,事件能在类外部被绑定一个或多个方法,而只能在类内部被调用。

      即离开事件所属的对象,事件只能出现在+=或-=的左边。

      2012-04-03 22:56:03

      上午写了很久,但是不小心点了关闭……我就懒得写很多东西了。直接贴出来一个例子吧。

      热水器的例子(改编自网络)

     1 using System;
    2
    3 namespace EventEG
    4 {
    5 class Program
    6 {
    7 static void Main(string[] args)
    8 {
    9 Heater heater = new Heater();
    10 // 设置报警方式
    11 heater.BoilEvent += new BoilEventHandler(Alarm);
    12 // 我们只能控制烧水(即开始加热)
    13        // 何时报警不是我们掌控的
    14 heater.Boil();
    15
    16 Console.ReadKey();
    17 // 输出
    18 /*
    19 * 水快烧开了!
    20 * 水快烧开了!
    21 * 水快烧开了!
    22 * 水快烧开了!
    23 * 水快烧开了!
    24 * 水快烧开了!
    25         */
    26 }
    27 // 预设的报警方式
    28 public static void Alarm()
    29 {
    30 Console.WriteLine("水快烧开了!");
    31 }
    32 }
    33
    34 // 委托
    35 public delegate void BoilEventHandler();
    36
    37 class Heater
    38 {
    39 // 加热(烧水)事件
    40 public event BoilEventHandler BoilEvent;
    41 // 水温
    42 private int _temperature;
    43 // 加热方法
    44 public void Boil()
    45 {
    46 for (_temperature = 0; _temperature < 101; _temperature++)
    47 {
    48 // 加热使水温升高,当到达一定程度使引发事件
    49 if (_temperature >= 95)
    50 {
    51 OnBoilEvent();
    52 }
    53 }
    54 }
    55
    56 // 引发事件
    57 void OnBoilEvent()
    58 {
    59 // 发生事件
    60 BoilEvent.Invoke();
    61 // BoilEvent();
    62 }
    63 }
    64 }

      我很惆怅啊!得想个办法……

      2012-04-04 11:54:04

     

  • 相关阅读:
    frame、window和dialog区别
    wxWidgets窗口类型
    C++中类与结构体的区别
    c++中explicit关键字用法
    解决error C2011: 'fd_set' : 'struct' type redefinition的方法
    jrtplib源码分析 第一篇 jthread的编译与分析
    详解大端模式和小端模式
    C++——重点复习
    Linux组件封装(九)——EchoLib的一些简单综合
    Linux组件封装(八)——Socket的封装
  • 原文地址:https://www.cnblogs.com/Johness/p/2431485.html
Copyright © 2011-2022 走看看