委托和事件

委托实现了类型安全的回掉方法,在.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();
}
如果对设计模式有所了解,上述实现过程实质是观察者模式在委托中的应用。
posted @ 2018-03-19 20:35  王小豆又叫小王子  阅读(197)  评论(0编辑  收藏  举报