zoukankan      html  css  js  c++  java
  • 从多播委托到事件

    一、多播委托

    前文提到的委托只是在一个委托类型中存储了一个方法(函数),实际上一个委托变量可以同时绑定多个方法,这些委托形成了一个委托链,每一个委托(实际上是方法)都顺序指向下一个委托,这个委托链就是多播委托。

    每一个绑定的方法就像是订阅者一样,等着发布者的消息,而触发委托变量的那个就像是发布者,将出发的信号传给所有的订阅者。

    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 Temperature
    17         {
    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             else
    28             {
    29                 Console.WriteLine("Heater stop");
    30             }
    31         }
    32     }
    33 }
    Heater
     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 Temperature
    13         {
    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             else
    29             {
    30                 Console.WriteLine("Cooler stop");
    31             }
    32         }
    33     }
    34 }
    Cooler

    可以看出,这两个类除了温度比较外,几乎完全一致,温度比较的那个函数,形式也是一致的。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 OnTemperatureChange
    16         {
    17             get { return _OnTemperatureChange; }
    18             set { _OnTemperatureChange = value; }
    19         }
    20         //定义这个属性,在其访问器内出发委托,作为发布
    21         private float _CurrentTemperature;
    22 
    23         public float CurrentTemperature
    24         {
    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 }
    Thermostat

    可以看出,在_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及其以上,会出发Cooler
    20             Console.WriteLine("Enter the current temperature");
    21             temperature = Console.ReadLine();
    22             thermostat.CurrentTemperature = int.Parse(temperature);
    23         }
    24     }
    25 }
    main

    在最后一句,在赋值的同时(实际上是晚于复制),调用了委托,便可以看到两个调节器的状态。

    二、对发布者调用委托变量的考虑

    如果我在主函数中没有给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                 } 
    Update

    需要注意的是,并没有直接检查OnTemperatureChange,而是将其赋值给了localOnChange,因为可能在判断的时候,其他线程置空了OnTemperatureChange,造成错误。

    既然委托是引用类型,那么为什么置空OnTemperatureChange不会影响到localOnChange呢?因为C#中,删除订阅者通过-=运算符,而调用-=来处理OnTemperatureChange-=<listener>,不会从OnTemperatureChange上删除,而是生成了一个全新的委托指向他,所以localOnChange是安全的。

    三、BUG的处理。

    如果同时绑定了2个方法,但是第一个方法在执行的出现了BUG导致不能正常运行,系统如何处理呢?

    解决方法是,在Thermostat中调用委托的时候,对其进行遍历,便可以继续通知后续的方法。

    代码如下:

     1    if (localOnChange != null)
     2                     {
     3                         foreach (TemperatureChangedHandler handler in OnTemperatureChange.GetInvocationList())
     4                         {
     5                             try
     6                             {
     7                                 handler(value);
     8                             }
     9                             catch (System.Exception ex)
    10                             {
    11                                 Console.WriteLine(ex.Message);
    12                             }
    13                         }
    14                        // OnTemperatureChange(value);//如果温度不一致再调用
    15 
    16                     }   
    ThermostatUpdate2

    若委托有返回值,也需要用遍历循环的方法来处理返回值。

    四、事件

    从上文可以看到,委托在Main里面可以用+=,也可以用-=,但是有些程序员会不小心将+= -=写成=,而=的机制是,先将左侧的委托变量清空,再将右侧的方法复制给左侧,这样会造成严重的错误。所以有必要对委托进行另一次封装,阻止其在Mian中的=操作。

    另外一个问题是,不仅可以在Thermostat中触发事件,也可以main函数中显式的触发事件,如下语句:Thermostat.OnTemperature(42); 这样完全越过了发布者里面的各种判断,容易造成错误。而事件是不允许这样做的。

    使用了事件,就成了事件-编码模式,发布者代码改成如下:

     1 using System;
     2 using System.Collections.Generic;
     3 using System.Linq;
     4 using System.Text;
     5 
     6 namespace DuoBoEvent
     7 {
     8     class ThermostatEvent
     9     {
    10         //此类是自定义的参数类
    11         public class TemperatureAtgs : System.EventArgs
    12         {
    13             public TemperatureAtgs(float newTemperature)
    14             {
    15                 NewTemperature = newTemperature;
    16             }
    17             private float _newTemperature;
    18 
    19             public float NewTemperature
    20             {
    21                 get { return _newTemperature; }
    22                 set { _newTemperature = value; }
    23             }
    24         }
    25         //声明一个委托,符合标准的参数列表
    26         public delegate void TemperatureChangedHandler(object sender, TemperatureAtgs newTemperature);
    27         //用事件字段封装一个事件属性
    28         public event TemperatureChangedHandler OnTemperatureChange = delegate { };//与委托不同,此处声明为了public,看似减弱封装,实际上event给了更强的限制。
    29         //同多播
    30         private float _CurrentTemperature;
    31 
    32         public float CurrentTemperature
    33         {
    34             get { return _CurrentTemperature; }
    35             set
    36             {
    37                 if (value != CurrentTemperature)
    38                 {
    39                    
    40                     _CurrentTemperature = value;
    41                     //调用事件
    42                     TemperatureChangedHandler localOnChange = OnTemperatureChange;
    43                     if (localOnChange != null)
    44                     {                 
    45                         OnTemperatureChange(this,new TemperatureAtgs(value));//在主函数里绑定了之后,便可以调用Cooler与Heater的指定的函数了
    46                     }
    47                 }
    48 
    49             }
    50         }
    51     }
    52 }
    ThermostatEvent

    定义与调用不同,其中都用注释标明了。当然,调用的时候应该把响应的订阅者的函数形式改成与事件一致,即形式如下:

    1   public void OnTemperatureChanged(object sender, ThermostatEvent.TemperatureAtgs newTemperature)

    主函数可以不变。主要是拒绝了在包容类外的=操作与触发操作。

    五、泛型事件

    如果事件的第二个参数发生了改变,是不是需要另外定义一个事件呢?当然不用,微软提供了泛型委托,只要指明类型就好了。

    原型定义如下:

    public delegate void EventHandler<T>(object sender,T e)where T:EventArgs 
    where T:EventArgs 保证了T只能继承自EventArgs 
    1   //声明一个委托,符合标准的参数列表
    2     //    public delegate void TemperatureChangedHandler(object sender, TemperatureAtgs newTemperature);
    3         //用事件字段封装一个事件属性
    4      //   public event TemperatureChangedHandler OnTemperatureChange = delegate { };//与委托不同,此处声明为了public,看似减弱封装,实际上event给了更强的限制。
    5         //用泛型委托
    6         public event EventHandler<TemperatureAtgs> OnTemperatureChange;
    泛型委托

    定义委托的两句就可以变成了一句,如上。

    至于判断中的那个

     //TemperatureChangedHandler localOnChange = OnTemperatureChange;

    可以注释掉,因为不存在TemperatureChangedHandler 了,其他错误提示很容易改掉,不再赘述。

  • 相关阅读:
    eclipse、idea安装lombok插件
    ContextLoaderListener加载过程
    web.xml 文件中一般包括 servlet, spring, filter, listenr的配置的加载顺序
    springboot
    root cause org.apache.ibatis.ognl.OgnlException: source is null for getProperty(null, "XXX")
    Java MyBatis 插入数据库返回主键
    Mybatis异常There is no getter for property named 'XXX' in 'class com.xxx.xxx.UserAccountDTO
    web项目,ftl文件中的路径引入问题
    mybatis No enum const class org.apache.ibatis.type.JdbcType.Integer
    Restful、Jersey和JAX-RS
  • 原文地址:https://www.cnblogs.com/tntboom/p/4007114.html
Copyright © 2011-2022 走看看