多播委托有几个问题 (委托链中的异常,委托链中的返回值,委托空值的检查)
这里可以通过分析事件怎样解决他们来了解事件与委托的区别,使用当然看具体问题来定未必事件就是比委托好的解决方式。
事件的使用大致分为这样几步:
1) 定义一个委托类型
2) 定义一个事件类型(在委托类型前加上关键字 event)
3) 为委托链中添加处理函数
4) 在运行时触发事件
Sample Code: 这段代码和多播委托的问题中的代码是一致的,只不过把委托用事件的方式实现了。
using System;
using System.Collections.Generic;
using System.Text;
namespace event_delegate
{
class Cooler
{
private float m_fTemperature;
public Cooler(float fTemperature)
{
m_fTemperature = fTemperature;
}
public float Temperature
{
get { return m_fTemperature; }
set { m_fTemperature = value; }
}
public void OnTemperatureChaged(float fNewTemperature)
{
if (fNewTemperature > m_fTemperature)
{
Console.WriteLine("Cooler : ON");
}
else
{
Console.WriteLine("Cooler : OFF");
}
}
}
class Heater
{
private float m_fTemperature;
public Heater(float fTemperature)
{
m_fTemperature = fTemperature;
}
public float Temperature
{
get { return m_fTemperature; }
set { m_fTemperature = value; }
}
public void OnTemperatureChaged(float fNewTemperature)
{
if (fNewTemperature < m_fTemperature)
{
Console.WriteLine("Heater : ON");
}
else
{
Console.WriteLine("Heater : OFF");
}
}
}
public class TemperatureHandler
{
public TemperatureHandler(Thermostat temperaturestat)
{
temperaturestat.eventOnTemperatureChanged += new Thermostat.TemperatureChangedHandler(HandlerTemperature);
}
void HandlerTemperature(object sender, Thermostat.TemperatureArgs fe)
{
Cooler cooler = new Cooler(60);
Heater heater = new Heater(60);
Console.WriteLine("TemperatureHandler new value = {0}", fe.NewTemperature);
if (fe.NewTemperature > 60)
{
Console.WriteLine("Cooler Start");
cooler.OnTemperatureChaged(fe.NewTemperature);
}
else
{
Console.WriteLine("Heater Start");
heater.OnTemperatureChaged(fe.NewTemperature);
}
((Thermostat)sender).eventOnTemperatureChanged -= new Thermostat.TemperatureChangedHandler(HandlerTemperature);
}
}
public class Thermostat
{
public class TemperatureArgs : System.EventArgs
{
public TemperatureArgs(float newTemperature)
{
_newTemperature = newTemperature;
}
public float NewTemperature
{
get { return _newTemperature;}
set { _newTemperature = value; }
}
private float _newTemperature;
}
public delegate void TemperatureChangedHandler(object sender, TemperatureArgs newTemperature);
public event TemperatureChangedHandler eventOnTemperatureChanged = delegate { };
private float m_fCurrentTemperature;
public float CurrentTemperature
{
get { return m_fCurrentTemperature; }
set
{
if (m_fCurrentTemperature != value)
{
m_fCurrentTemperature = value;
if (eventOnTemperatureChanged != null)
{
eventOnTemperatureChanged(this, new TemperatureArgs(value));
}
}
}
}
}
class Program
{
static void Main(string[] args)
{
Thermostat thermostat = new Thermostat();
TemperatureHandler handler = new TemperatureHandler(thermostat);
string strTemp = "";
Console.WriteLine("Enter temperature: ");
strTemp = Console.ReadLine();
thermostat.CurrentTemperature = float.Parse(strTemp);
// 在委托处理函数执行完成后调用 "-="后委托链为空了,这时在触发一下event来测试委托链为NULL的情况
thermostat.CurrentTemperature = 10;
Console.ReadKey();
}
}
}
代码中解决委托链问题的分析:
1) 委托链中的异常,委托链中的返回值
程序中还是存在着委托链,但这时的委托链中只有一个处理函数 += new Thermostat.TemperatureChangedHandler(HandlerTemperature)。
与原来在委托链中添加两个函数 (+= Heater.OnTemperatureChange() 和 += Cooler.OnTemperatureChange()) 相比可以看作是一种封装。
这样在封装中可以任意调整实际的工作类(这里指的是Heater 和 Cooler) 可以是顺序的链,也可以根据条件执行。
处理异常和返回值时与单纯的委托链相比空间更大了。
2) 触发方式
在委托链方式中,触发方式就是通过调用委托来执行,在事件方式中变成了调用事件进行触发。这里最大的区别是它多了两个参数 Sender和Arguments
eventOnTemperatureChanged(this, new TemperatureArgs(value));
Sender 表示是那个对象触发了事件。
Arguments 表示执行委托需要的参数。
这个是委托链做不到的,事件又为我们解决问题增加了一条途径。
3) 委托空值的检查
在定义事件的代码中,同时还有一个赋值的操作。
public event TemperatureChangedHandler eventOnTemperatureChanged = delegate { };
它与NULL是不同的,它表示有委托但没有执行的实体。这也就解决了空引用的问题。
测试也很简单
在执行了事件处理函数后直接调用 “-=”,这样委托链就被清空了。
-= new Thermostat.TemperatureChangedHandler(HandlerTemperature);
然后在程序结束前再次触发事件,单步执行的话程序会运行到delegate { }中,但没有异常。
这可以看作是事件为委托加了一层保护吧。
如果代码不够直观,时序图也许能清楚点
这里红框部分更像是把委托链进行了封装,暴露在外的是event,大家可通过event来触发。
但触发时必须告诉 event 是谁触发的,参数是什么。