• 委托和事件


          委托是一个类,它定义了方法的类型,使得可以将方法当作另一个方法的参数来进行传递,这种将方法动态地赋给参数的做法,可以避免在程序中大量使用If-Else(Switch)语句,同时使得程序具有更好的可扩展性。

    using System;
    using System.Collections.Generic;
    using System.Text;
    namespace Delegate
    {
    //定义委托,它定义了可以代表的方法的类型
    public delegate void GreetingDelegate(string name);
    class Program
    {
    private static void EnglishGreeting(string name)
    {
    Console.WriteLine("Morning, " + name);
    }
    private static void ChineseGreeting(string name)
    {
    Console.WriteLine("早上好, " + name);
    }
    //注意此方法,它接受一个GreetingDelegate类型的参数,该参数是返回值为空,参数为string类型的方法
    private static void GreetPeople(string name, GreetingDelegate MakeGreeting)
    {
    MakeGreeting(name);
    }
    static void Main(string[] args)
    {
    GreetPeople("Jimmy Zhang", EnglishGreeting);
    GreetPeople("张子阳", ChineseGreeting);
    Console.ReadKey();
    }
    }
    }

    输出:
    Morning, Jimmy Zhang
    早上好, 张子阳 

    可以将多个方法赋给同一个委托,或者叫将多个方法绑定到同一个委托(多播委托)。当调用这个委托的时候,将依次调用其所绑定的方法。在这个例子中,语法如下:

    static void Main(string[] args) {
    GreetingDelegate delegate1;
    delegate1 = EnglishGreeting; // 先给委托类型的变量赋值
    delegate1 += ChineseGreeting; // 给此委托变量再绑定一个方法
    // 将先后调用 EnglishGreeting 与 ChineseGreeting 方法
    GreetPeople("Jimmy Zhang", delegate1);
    Console.ReadKey();
    }
    输出
    Morning, Jimmy Zhang
    早上好, Jimmy Zhang
     
    可以省略GreetPeople方法,通过委托来直接调用EnglishGreeting和ChineseGreeting:
    static void Main(string[] args) {
    GreetingDelegate delegate1;
    delegate1 = EnglishGreeting; // 先给委托类型的变量赋值
    delegate1 += ChineseGreeting; // 给此委托变量再绑定一个方法
    // 将先后调用 EnglishGreeting 与 ChineseGreeting 方法
    delegate1 ("Jimmy Zhang");
    Console.ReadKey();
    }
    注意:这里,第一次用的“=”是赋值的语法;第二次用的“+=”是绑定的语法。第一次就使用“+=”,将出现“使用了未赋值的局部变量”的编译错误。
    可以使用下面的代码来简化这一过程:
    GreetingDelegate delegate1 = new GreetingDelegate(EnglishGreeting);
    delegate1 += ChineseGreeting; // 给此委托变量再绑定一个方法
    应该注意到这段代码第一条语句与实例化一个类何其相似,不禁想:上面第一次绑定委托时不可以使用“+=”的编译错误,或许可以用下面的方法来避免:
    GreetingDelegate delegate1 = new GreetingDelegate();
    delegate1 += EnglishGreeting; // 这次用的是 “+=”,绑定语法。
    delegate1 += ChineseGreeting; // 给此委托变量再绑定一个方法
    但实际会出现编译错误: “GreetingDelegate”方法没有采用“0”个参数的重载。尽管结果让人沮丧,但编译的提示:“没有0个参数的重载”再次让人联想到类的构造函数。再探个究竟之前,需要先把基础知识和应用介绍完。
     
    既然给委托可以绑定一个方法,那么也应该有办法取消对方法的绑定,很容易想到,这个语法是“-=”:
    static void Main(string[] args) {
    GreetingDelegate delegate1 = new GreetingDelegate(EnglishGreeting);
    delegate1 += ChineseGreeting; // 给此委托变量再绑定一个方法
    // 将先后调用 EnglishGreeting 与 ChineseGreeting 方法
    GreetPeople("Jimmy Zhang", delegate1);
    Console.WriteLine();
    delegate1 -= EnglishGreeting; //取消对EnglishGreeting方法的绑定
    // 将仅调用 ChineseGreeting
    GreetPeople("张子阳", delegate1);
    Console.ReadKey();
    }

    输出:

    Morning, Jimmy Zhang
    早上好, Jimmy Zhang
    早上好, 张子阳
     
    -------------------------------------------------------------------------------------------------------
    上面的三个方法都定义在Program类中,这样做是为了理解方便,实际应用中,通常GreetPeople 在一个类中,ChineseGreeting和 EnglishGreeting 在另外的类中。对委托已有初步了解,现在对上面的例子做些改进。假设将GreetingPeople()放在一个叫GreetingManager的类中,那么新程序应该是这个样子的:
    namespace Delegate {
    //定义委托,它定义了可以代表的方法的类型
    public delegate void GreetingDelegate(string name);
    //新建的GreetingManager类
    public class GreetingManager{
    public void GreetPeople(string name, GreetingDelegate MakeGreeting) {
    MakeGreeting(name);
    }
    }
    class Program {
    private static void EnglishGreeting(string name) {
    Console.WriteLine("Morning, " + name);
    }
    private static void ChineseGreeting(string name) {
    Console.WriteLine("早上好, " + name);
    }
    static void Main(string[] args) {
    GreetingManager gm = new GreetingManager();
    gm.GreetPeople("Jimmy Zhang", EnglishGreeting);
    gm.GreetPeople("张子阳", ChineseGreeting);
    }
    }
    }

    输出:

    Morning, Jimmy Zhang

    早上好, 张子阳
     
    使用上一节学到的知识,将多个方法绑定到同一个委托变量,该如何做?再次改写代码:
    static void Main(string[] args) {
    GreetingManager gm = new GreetingManager();
    GreetingDelegate delegate1;
    delegate1 = EnglishGreeting;
    delegate1 += ChineseGreeting;
    gm.GreetPeople("Jimmy Zhang", delegate1);
    }
    输出:
    Morning, Jimmy Zhang
    早上好, Jimmy Zhang
     
    面向对象设计,讲究的是对象的封装,既然可以声明委托类型的变量(在上例中是delegate1),我们何不将这个变量封装到 GreetingManager类中?在这个类的客户端中使用不是更方便么?于是,我们改写GreetingManager类,像这样:
    public class GreetingManager{
    //在GreetingManager类的内部声明delegate1变量
    public GreetingDelegate delegate1;
    public void GreetPeople(string name, GreetingDelegate MakeGreeting) {
    MakeGreeting(name);
    }
    }

    这样使用这个委托变量:

    static void Main(string[] args) {
    GreetingManager gm = new GreetingManager();
    gm.delegate1 = EnglishGreeting;
    gm.delegate1 += ChineseGreeting;
    gm.GreetPeople("Jimmy Zhang", gm.delegate1);
    }
    输出为:
    Morning, Jimmy Zhang
    早上好, Jimmy Zhang
     
    这样做没有任何问题,但在调用gm.GreetingPeople方法的时候,再次传递了gm的delegate1字段:
    gm.GreetPeople("Jimmy Zhang", gm.delegate1);
    何不修改 GreetingManager 类成这样:
    public class GreetingManager{
    //在GreetingManager类的内部声明delegate1变量
    public GreetingDelegate delegate1;
    public void GreetPeople(string name) {
    if(delegate1!=null){ //如果有方法注册委托变量
    delegate1(name); //通过委托调用方法
    }
    }
    }

    这样调用更简洁一些:

    static void Main(string[] args) {
    GreetingManager gm = new GreetingManager();
    gm.delegate1 = EnglishGreeting;
    gm.delegate1 += ChineseGreeting;
    gm.GreetPeople("Jimmy Zhang"); //注意,这次不需要再传递 delegate1变量
    }
    输出:
    Morning, Jimmy Zhang
    早上好, Jimmy Zhang
     
    这样达到需要的效果,但还存在问题:
    delegate1和我们平时用的string类型的变量没什么分别,但并不是所有的字段都应该声明成public,合适的做法该public的时候public,该private的时候private。先看看如果把 delegate1 声明为 private会怎样?结果是:声明委托的目的就是为了把它暴露在类的客户端进行方法的注册,把它声明为private,客户端对它根本不可见,那它还有什么作用?把delegate1 声明为 public 会怎样?结果是:在客户端可以对它进行随意的赋值等操作,严重破坏对象的封装性。最后,第一个方法注册用“=”,是赋值语法,因为要进行实例化,第二个方法注册则用的是“+=”。但是,不管是赋值还是注册,都是将方法绑定到委托上,除了调用时先后顺序不同,再没有任何的分别,这样是不是很别扭?
    如果delegate1不是一个委托类型,而是一个string类型,你会怎么做?答案是使用属性对字段进行封装。
    于是,Event出场了,它封装了委托类型的变量,使得:在类的内部,不管你声明它是public还是protected,它总是private的。在类的外部,注册“+=”和注销“-=”的访问限定符与你在声明事件时使用的访问符限定符相同。
     
    改写GreetingManager类,它变成了这个样子:
    public class GreetingManager{
    //这一次我们在这里声明一个事件
    public event GreetingDelegate MakeGreet;
    public void GreetPeople(string name) {
    MakeGreet(name);
    }
    }
    注意:MakeGreet 事件的声明与之前委托变量delegate1的声明唯一的区别是多了一个event关键字。看到这里,在结合上面的讲解,你应该明白到:声明一个事件类似声明一个进行了封装的委托类型的变量而已。
    改写Main方法:
    static void Main(string[] args) {
    GreetingManager gm = new GreetingManager();
    gm.MakeGreet = EnglishGreeting; // 编译错误1
    gm.MakeGreet += ChineseGreeting;
    gm.GreetPeople("Jimmy Zhang");
    }

    得到编译错误:事件“Delegate.GreetingManager.MakeGreet”只能出现在 += 或 -= 的左边(从类型“Delegate.GreetingManager”中使用时除外)。

    注释掉编译错误的行,重新进行编译,借助Reflector来对 event的声明语句做一探究,看看为什么发生这样的错误:public event GreetingDelegate MakeGreet;可以看到,实际上尽管我们在GreetingManager里将 MakeGreet 声明为public,但是,实际上MakeGreet会被编译成 私有字段,难怪会发生上面的编译错误了,因为它根本就不允许在GreetingManager类的外面以赋值的方式访问,从而验证了我们上面所做的推论。

    进一步看下MakeGreet所产生的代码:
    private GreetingDelegate MakeGreet; //对事件的声明 实际是 声明一个私有的委托变量
    [MethodImpl(MethodImplOptions.Synchronized)]
    public void add_MakeGreet(GreetingDelegate value){
    this.MakeGreet = (GreetingDelegate) Delegate.Combine(this.MakeGreet, value);
    }
    [MethodImpl(MethodImplOptions.Synchronized)]
    public void remove_MakeGreet(GreetingDelegate value){
    this.MakeGreet = (GreetingDelegate) Delegate.Remove(this.MakeGreet, value);
    }
    已经很明确了:MakeGreet事件确实是一个GreetingDelegate类型的委托,只不过不管是不是声明为public,它总是被声明为private。另外,它还有两个方法,分别是add_MakeGreet和remove_MakeGreet,这两个方法分别用于注册委托类型的方法和取消注册。实际上也就是: “+= ”对应 add_MakeGreet,“-=”对应remove_MakeGreet。而这两个方法的访问限制取决于声明事件时的访问限制符。
    在add_MakeGreet()方法内部,实际上调用了System.Delegate的Combine()静态方法,这个方法用于将当前的变量添加到委托链表中。
    我们前面提到过两次,说委托实际上是一个类,在我们定义委托的时候:
    public delegate void GreetingDelegate(string name);
    当编译器遇到这段代码的时候,会生成下面这样一个完整的类:
    public sealed class GreetingDelegate:System.MulticastDelegate{
    public GreetingDelegate(object @object, IntPtr method);
    public virtual IAsyncResult BeginInvoke(string name, AsyncCallback callback, object @object);
    public virtual void EndInvoke(IAsyncResult result);
    public virtual void Invoke(string name);
    }
    关于这个类的更深入内容,可以参阅《CLR Via C#》等相关书籍,这里就不再讨论了。
  • 相关阅读:
    机器学习初篇(0.0)
    MQTT 入门介绍
    《八极拳谱》(李书文)
    Golang实战群:日志的处理机制
    【转】火山引擎 Redis 云原生实践
    【转】7000字前端性能优化总结 | 干货建议收藏
    微信小程序canvas绘制圆角边框
    【转】语义化版本 2.0.0
    Verdaccio私有 npm 服务器搭建及其配置
    【转】根据条件配置多个npm仓库
  • 原文地址:https://www.cnblogs.com/SmileX/p/4403048.html
Copyright © 2020-2023  润新知