zoukankan      html  css  js  c++  java
  • 委托和事件

    委托实现了类型安全的回掉方法,在.NET中回调无处不在,所以委托也无处不在,事件模型建立在委托机制上,本文将完成一次关于委托的旅行,全面阐述委托及其核心话题,逐一梳理委托、委托链、事件等。
    关于委托
    了解委托先从其定义开始,通常一个委托被声明为:
    public delegate void CalculateDelegate(int x,int y);
    
    关键字delegate用于声明一个委托类型CalculateDelegate,可以对其添加访问修饰符,默认其返回类型为void,接受两个int类型的参数x和y,但是委托并不等于方法,而是一种引用类型,类似c++中的函数指针,指向一个方法。
    下面的示例将介绍如何通过委托来实现一个计算器模拟程序,在基础上来了解关于委托的定义、创建和应用:
    public class DelegateDemo
    {
      // 声明委托类型
      public delegate void CalculateDelegate(Int32 x,Int32 y);
     
      public void Add(Int32 x, Int32 y)
      {
        Console.WriteLine(x + y);
      }
     
      // 定义委托类型的变量
      private CalculateDelegate myDelegate;
     
      public void BeginCalcualte()
      {
        // 委托绑定
        myDelegate = new CalculateDelegate(Add);
        //调用委托
        myDelegate(1,2);
      }
    }

      上述示例中,在DelegateDemo类中声明了一个CalculateDelegate的委托类型,它具有和绑定方法Add完全相同的返回类型和参数,否则无法通过编译,将方法传给CalculateDelegate的构造器,也就是将方法指派给委托CalculateDelegate委托,并将该引用赋给myDelegate变量,也就表示myDelegate变量保存指向了Add方法的引用,以此实现对Add的回调。

    由此可见,委托表示了对其回调方法的签名,可以将方法当作参数来传递,并根据传入的方法来动态的调用方法,所以,只要提供和委托具有相同签名的方法就可以与委托绑定,例如:
    public void Sub(Int32 x, Int32 y)
    {
        Console.WriteLine(x - y);
    }
    
    同样可以将Sub分配给委托,如下:
    // 委托绑定
    myDelegate = new CalculateDelegate(Sub);
    //调用委托
    myDelegate(2, 1);
    
    多播委托委托链
    在上述委托的实现中,Add方法和Sub方法可以绑定到同一个类型的委托上,那么它们可不可以绑定到同一个委托变量上呢?答案是可以,多个方法可以绑定到同一个委托变量上,在委托变量做回调的时候可以依次执行其绑定的方法,这种技术称为多播委托。在.NET中提供了相当简洁的语法类创建委托链,以+=和-=操作符来分别进行绑定和解除绑定操作,多个方法绑定到同一个委托变量就形成了一条委托链,对其调用时会以此调用所有绑定的回调方法。例如:
    public void BeginCalcualte()
    {
        // 委托绑定
        myDelegate = new CalculateDelegate(Add);
        myDelegate += new CalculateDelegate(Sub);
        //调用委托
        myDelegate(211, 99);
    }
    
    计算结果为 :210 112
    再以-=来解除绑定
        myDelegate -= new CalculateDelegate(Sub);
      //调用委托
      myDelegate(211, 99);
    计算结果为:310,可见通过-=操作,解除了Sub方法。
    事实上+=和-=操作分别调用了Delegate.Combine和Delegate.Remove方法,委托本质上仍然是一个类,如此简洁的语法正式因为CLR和编译在后台完成了一系列操作。
    .NET的事件模型建立在委托的机制上,可以说事件是对委托的封装,从委托的示例中可知,在客户端可以随意对委托进行操作,一定程度上破化了面向对象的封装机制,因此事件实现了对委托的封装。
    下面通过将委托的示例进行改造,来完成一个事件的定义过程:
    public class Calculator
    {
        // 用来存放事件引发时向处理程序传递的状态信息
        public class CalculateEventArgs : EventArgs
        {
            public readonly Int32 x, y;
     
            public CalculateEventArgs(Int32 x, Int32 y)
            {
                this.x = x;
                this.y = y;
            }
        }
     
        //声明事件委托
        public delegate void CalculateEventHander(object sender,CalculateEventArgs e);
     
        //定义事件成员 提供外部绑定
        public event CalculateEventHander myCalculate;
     
        protected virtual void OnCalculate(CalculateEventArgs e)
        {
            if(myCalculate!=null)
            {
                myCalculate(this,e);
            }
        }
     
        //进行计算,调用该方法表示有新的计算发生
        public void Calculate(Int32 x, Int32 y)
        {
            CalculateEventArgs e = new CalculateEventArgs(x,y);
            //通知所有事件 的注册者
            OnCalculate(e);
        }
    }                
    
    示例中,对计算器模拟程序做了简要修改,从二者的对比中可以体会出事件的完整定义过程,主要包括:
    • 定义一个内部事件参数类型,用于存放事件引发时向事件处理程序传递的状态信息,EventArgs是事件数据类的基类。
    • 声明事件委托,主要包括两个参数:一个表示事件的发送者对象,一个表示时间参数类对象。
    • 定义事件成员。
    • 定义负责通知事件引发的方法,它被实现为Protected virtual方法,目的是可以在派生类中覆写该方法来拒绝监听事件。
    • 定义一个触发事件的犯法,例如Calculate被调用时,表示有新的计算发生。
    一个事件的完整程序就这样定义好了,然后还需要定义一个事件触发程序用来监听事件:
    public class CalculateManager
    {
        public void Add(object sender, Calculator.CalculateEventArgs e)
        {
            Console.WriteLine(e.x+e.y);
        }
     
        public void Sub(object sender, Calculator.CalculateEventArgs e)
        {
            Console.WriteLine(e.x - e.y);
        }
    }
    
    最后,在客户端 实现事件处理程序:
    static void Main(string[] args)
    {
     
        Calculator calculator = new Calculator();
     
        //事件监听者
        CalculateManager cm = new CalculateManager();
     
        //事件绑定
        calculator.myCalculate += cm.Add;
        calculator.Calculate(200,100);
     
        calculator.myCalculate += cm.Sub;
        calculator.Calculate(200, 100);
     
        calculator.myCalculate -= cm.Sub;
     
        Console.ReadLine();
    }
    
    如果对设计模式有所了解,上述实现过程实质是观察者模式在委托中的应用。
  • 相关阅读:
    【转】SpringCloud学习
    Springboot中配置druid
    阿里云sql监控配置-druid
    Linux中Java开发常用的软件总结:
    java 搞笑注释
    Python之路-pandas包的详解与使用
    Python之路-numpy模块
    Python之路-Python中的线程与进程
    Python之路-Python常用模块-time模块
    Python之路-Python中文件和异常
  • 原文地址:https://www.cnblogs.com/mohanchen/p/8604567.html
Copyright © 2011-2022 走看看