首页 > 代码库 > 从多播委托到事件
从多播委托到事件
一、多播委托
前文提到的委托只是在一个委托类型中存储了一个方法(函数),实际上一个委托变量可以同时绑定多个方法,这些委托形成了一个委托链,每一个委托(实际上是方法)都顺序指向下一个委托,这个委托链就是多播委托。
每一个绑定的方法就像是订阅者一样,等着发布者的消息,而触发委托变量的那个就像是发布者,将出发的信号传给所有的订阅者。
1、订阅者
考虑一个温度控制器的例子,这个控制器拥有两个调温器,一个加热器,当温度低于指定的值时,启动,一个冷却器,当温度高于指定的温度时,启动。二者的类设计如下:
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 //订阅者 6 namespace DuoBoEvent 7 { 8 class Heater 9 {10 public Heater(float temperature)//设定启动加热器的临界11 {12 Temperature = temperature; 13 }14 private float _Temperature;15 16 public float Temperature17 {18 get { return _Temperature; }19 set { _Temperature = value; }20 }21 public void OnTemperatureChanged(float newTemperature)22 {23 if (newTemperature < Temperature)24 {25 Console.WriteLine("Heater start");26 }27 else28 {29 Console.WriteLine("Heater stop");30 }31 }32 }33 }
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 //订阅者 6 namespace DuoBoEvent 7 { 8 class Cooler 9 {10 private float _Temperature;//启动Cooler的临界温度11 12 public float Temperature13 {14 get { return _Temperature; }15 set { _Temperature = value; }16 }17 public Cooler(float temperature)18 {19 Temperature = temperature;20 }21 22 public void OnTemperatureChanged(float newTemperature)//传入的为当前温度23 {24 if (newTemperature > Temperature)25 {26 Console.WriteLine("Cooler start");27 }28 else29 {30 Console.WriteLine("Cooler stop");31 }32 }33 }34 }
可以看出,这两个类除了温度比较外,几乎完全一直,温度比较的那个函数,形式也是一致的。OnTemperatureChanged都属于订阅者方法,他们的参数类型与发布者的委托类型一致,这样才可以绑定到发布者的委托变量上。
2、发布者
发布者代码如下:
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 6 namespace DuoBoEvent 7 {//发布者 8 class Thermostat 9 {10 public delegate void TemperatureChangedHandler(float newTemperature);//定义一个委托,他与订阅者的函数签名一致11 12 //定义一个委托类型的成员,用来发布“消息”13 private TemperatureChangedHandler _OnTemperatureChange;14 15 public TemperatureChangedHandler OnTemperatureChange16 {17 get { return _OnTemperatureChange; }18 set { _OnTemperatureChange = value; }19 }20 //定义这个属性,在其访问器内出发委托,作为发布21 private float _CurrentTemperature;22 23 public float CurrentTemperature24 {25 get { return _CurrentTemperature; }26 set 27 {28 if (value != CurrentTemperature)29 {30 //其实,_CurrentTemperature已经是输入新的温度后的前温度了,用来与新的温度Value比较,下一句就是更新前温度为现温度31 _CurrentTemperature = value;//用作一个参数,类似 public void OnTemperatureChanged(float newTemperature)里面的参数32 //调用委托33 OnTemperatureChange(value);//如果温度不一致再调用34 } 35 36 }37 }38 39 }40 }
可以看出,在_CurrentTemperature的访问器内调用了这个委托变量,如果我在主函数中将两个调节器的OnTemperatureChanged与Thermostat的委托变量绑定后,当执行到_CurrentTemperature的访问器后,通过判定,会调用委托。
3、主函数:
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 6 namespace DuoBoEvent 7 { 8 class Program 9 {10 static void Main(string[] args)11 {12 Heater heater = new Heater(60);13 Cooler cooler = new Cooler(80);14 Thermostat thermostat = new Thermostat();15 string temperature;16 //订阅者与发布者绑定17 thermostat.OnTemperatureChange += heater.OnTemperatureChanged;18 thermostat.OnTemperatureChange += cooler.OnTemperatureChanged;19 //读入当前温度,60及其以下会出发Heater,80及其以上,会出发Cooler20 Console.WriteLine("Enter the current temperature");21 temperature = Console.ReadLine();22 thermostat.CurrentTemperature = int.Parse(temperature);23 }24 }25 }
在最后一句,在赋值的同时(实际上是晚于复制),调用了委托,便可以看到两个调节器的状态。
二、对发布者调用委托变量的考虑
如果我在主函数中没有给Thermostat的委托变量指定任何方法呢?会提示委托对象未实例化,所以在调用前必须要进行空值检查,代码如下:
1 if (value != CurrentTemperature) 2 { 3 //其实,_CurrentTemperature已经是输入新的温度后的前温度了,用来与新的温度Value比较,下一句就是更新前温度为现温度 4 _CurrentTemperature = value;//用作一个参数,类似 public void OnTemperatureChanged(float newTemperature)里面的参数 5 //调用委托 6 TemperatureChangedHandler localOnChange = OnTemperatureChange; 7 if (localOnChange != null) 8 { 9 OnTemperatureChange(value);//如果温度不一致再调用10 } 11 }
需要注意的是,并没有直接检查OnTemperatureChange,而是将其赋值给了localOnChange,因为可能在判断的时候,其他线程置空了OnTemperatureChange,造成错误。
既然委托是引用类型,那么为什么置空OnTemperatureChange不会影响到localOnChange呢?因为C#中,删除订阅者通过-=运算符,而调用-=来处理OnTemperatureChange-=<listener>,不会从OnTemperatureChange上删除,而是生成了一个全新的委托指向他,所以localOnChange是安全的。
从多播委托到事件