**多播委托(multicast delegate):一个委托变量可以引用一系列委托,在这一系列委托中,每个委托都顺序指向一个后续的委托,从而形成一个委托链,或者称为多播委托。使用多播委托,可以通过一个方法对象来调用方法链,创建变量来引用方法链,并将那些数据类型用作参数传递给方法。
在C#中,多播委托是一个通用的模式,这个模式称为“observer ”或者publish_subscribe模式,它要应对的是这样一个情形,你需要将单一事件的通知广播给多个订阅者(subscriber)。
**使用多播委托来编码Observer模式。
例子:自动调温器将温度发动给多个订阅者--加热器(Heater)和冷却器(Cooler)。
//Cooler
class Cooler
{
public Cooler(float temperature)
{
Temperature = temperature;
}
public float Temperature
{
get;
set;
}
public void OnTemperatureChanged(float newTemperature)
{
if (newTemperature > Temperature)
{
Console.WriteLine("Cooer :on");
}
else
{
Console.WriteLine("Cooer :off");
}
}
}
//Heater
class Heater
{
public Heater(float temperature)
{
Temperature = temperature;
}
public float Temperature
{
get;
set;
}
public void OnTemperatureChanged(float newTemperature)
{
if (newTemperature < Temperature)
{
Console.WriteLine("Heater :on");
}
else
{
Console.WriteLine("Heater :off");
}
}
}
//Thermostat负责向heater和cooler发送温度
public class Thermostat
{
//define the delegate data type,与Heater,Cooler中OnTemperatureChanged的签名匹配。
public delegate void TemperatureChangeHander(float newTemperature);
//define the event publisher
//OnTemperatureChange存储了订阅者列表。
public TemperatureChangeHander OnTemperatureChange
{
get;
set;
}
public float CurrentTemperature
{
get { return _CurrentTemperature; }
set
{
if (value != CurrentTemperature)
{
_CurrentTemperature = value;
//向每个订阅者发出一个通知;但是没有检查NULL值
//OnTemperatureChange(value);
//这个简单的赋值修改可以确保在检查空值和发送通知之间,
//假如所有OnTemperatureChange订阅者都被移出(由一个不同的线程),那么不会触发NullReferenceException异常。
TemperatureChangeHander localOnChange = OnTemperatureChange;
if (localOnChange != null)
{
//一定要记住,在调用一个委托之前,一定要检查它的值是否是空值。
localOnChange(value);
}
}
}
}
public float _CurrentTemperature;
}
static void Main(string[] args)
{
//连接发布者和订阅者
Thermostat thermostat = new Thermostat();
Heater heater = new Heater(80);
Cooler cooler = new Cooler(60);
string temperature;
//向OnTemeratureChange委托注册了两个订阅者。
thermostat.OnTemperatureChange += heater.OnTemperatureChanged;
thermostat.OnTemperatureChange += cooler.OnTemperatureChanged;
Console.WriteLine("Enter temperature:");
temperature = Console.ReadLine();
thermostat.CurrentTemperature = int.Parse(temperature);
Console.ReadKey();
}
**将“-=”运算符用于委托会返回一个新实例
委托是一个引用类型,对OnTemperaturechange-=<listener>的任何调用,都不会从OnTemperatureChange删除一个委托而是她的委托比之前少一个。相反,会将一个全新的多播委托指派给他,这不会对原始的多播委托产生任何影响(localOnChange也指向那个原始的多播委托。)
**委托运算符
为了合并Thremostat例子中的两个订阅者,要用“+=”,这样会获取第一个委托,并将第二个委托天机到委托连中,使一个委托指向下一个委托。第一个委托的方法被调用之后,他会调用第二个委托。从委托中删除委托,则要使用“-=”运算符。
**委托运算符的练习
Thermostat thermostat = new Thermostat();
Heater heater = new Heater(80);
Cooler cooler = new Cooler(60);
//string temperature;
Thermostat.TemperatureChangeHander delegate1;
Thermostat.TemperatureChangeHander delegate2;
Thermostat.TemperatureChangeHander delegate3;
delegate1 = heater.OnTemperatureChanged;
delegate2 = cooler.OnTemperatureChanged;
Console.WriteLine("invoke both delegates:");
delegate3 = delegate1;
delegate3 += delegate2;
delegate3(90);
Console.WriteLine("invoke only delegate2");
delegate3 -= delegate1;
delegate3(50);
输出:
invoke both delegates:
Heater :off
Cooer :on
invoke only delegate2
Cooer :off
**运算符+ -的委托运算符,下面是同样成立的。使用赋值运算符,会清除之前的所有订阅者,并允许使用新的订阅者替换他们(不是很理解啊~~)。这是委托很容易让人犯错误的一个设计。应注意的事,无论“+”“-”还是复合版本,在内部都是使用静态方法System.Delegate.Combine() 和System.Delegate.Remove()来实现的。这两个方法都获取delegate类型的两个参数。第一个方法Combine()会连接两个参数,将两个委托的调用列表按照顺序连接到一起。第二个方法Remove()则搜索由一第个参数指定的委托链,并删除第二个参数指定的委托。
对于Combine()的两个参数都可以是null ,且返回null。如果一个null,一个非null,则返回非null 的值,这就是为什么thermostat.OnTemperatureChange没有初值时,thermostat.OnTemperatureChange += heater.OnTemperatureChanged;中没有引发异常的原因。
delegate3 = delegate1 + delegate2;
delegate3 = delegate3 - delegate1;
**委托的顺序调用:因为委托能指向里一个委托,后者又能指向其他委托。(链式)
**错误处理:凸显了顺序通知的重要性。假如一个订阅者引发了一个异常,链中的后续订阅者就接收不到通知。
Thermostat thermostat = new Thermostat();
Heater heater = new Heater(80);
Cooler cooler = new Cooler(60);
string temperature;
thermostat.OnTemperatureChange += heater.OnTemperatureChanged;
//Lamda语句异常会使链发生中断,阻碍cooler对象接收通知。
thermostat.OnTemperatureChange +=
(newTemperature) =>
{
throw new ApplicationException();
};
thermostat.OnTemperatureChange += cooler.OnTemperatureChanged;
Console.WriteLine("Enter temperature:");
temperature = Console.ReadLine();
thermostat.CurrentTemperature = int.Parse(temperature);
为了避免上面的问题,使所有订阅者都能收到通知,必须手动遍历订阅者列表,并单独调用它们。
public float CurrentTemperature
{
get { return _CurrentTemperature; }
set
{
if (value != CurrentTemperature)
{
_CurrentTemperature = value;
if (OnTemperatureChange != null)
{ //处理来自订阅者的异常。
//从一个委托的GetInvocationList()方法获得一份订阅者列表。
foreach (TemperatureChangeHander handler in OnTemperatureChange.GetInvocationList())
{
try
{
handler(value);
}
catch (Exception exception)
{
Console.WriteLine(exception.Message);
}
}
}
}
}
**方法返回值和传引用
涉及的委托要么不返回void,要么有一个ref或out参数,在这种情况下,也有必要遍历委托调用表,而非直接激活一个通知。因为调用了一个通知,就有可能造成通知发送给多个订阅者。假如订阅者会返回值,就无法确定应该使用哪一个订阅者的消息。
为解决上述问题,c#引进了事件。
**事件:
**事件的作用
1.封装订阅:
thermostat.OnTemperatureChange += heater.OnTemperatureChanged; =====》thermostat.OnTemperatureChange = heater.OnTemperatureChanged;
thermostat.OnTemperatureChange += cooler.OnTemperatureChanged; =====》 thermostat.OnTemperatureChange = cooler.OnTemperatureChanged;
如果上述代码便成了右侧的代码,则会将heater的订阅取消掉。为了避免上述情况,可以使用event进行外部的封装,部位包容类外部的对象提供赋值运算符的支持。
2.封装发布:事件确保只有包容类才能触发一个事件通知。
static void Main()
{ ...
//从事件包容者的外部触发事件。
thermostat.OnTemperatureChange(43);
...
}
**事件的声明:C#用event关键字解决了上述两个问题,event定义的是一个新的成员类型。
将thermostat 中的OnTemperatureChange 改成如下:
//fine the enent publisher
//用event修饰以后,会禁止为一个public字段使用赋值运算符。
//只有包容类才能调用向所有订阅者发出通知的委托。
//例如:不能在类的外部执行 thermostat.OnTemperatureChange(43);
public event TemperatureChangeHander OnTemperatureChange =
delegate { };
//通过赋值一个空委托,代表由0个侦听者构成的集合。就可以引发事件而不必检查是或否有任何监听者。
//由于重新对委托进行赋值只能在内部,所以如果没有给委托赋值null,则不必每次调用委托时都检查null值。
由于不是很理解书上讲的东西,于是查了其他资料来学习。
namespace evetEx01
{ //1.定义时间前,定义个委托类型,以用于事件
//使用标准的委托语法,在名称空间中将该委托定义为公共类型。
public delegate void MessageHandler(Connection source,MessageArrivedEventArgs e);
public class Connection
{
//2.定义委托之后,把事件本身定义为类的一个成员。
//也可以使用匿名方法定义事件处理方法。
public event MessageHandler MessageArrived;
public Timer pollTimer;
public Connection()
{
pollTimer = new Timer(1000);
pollTimer.Elapsed+=new ElapsedEventHandler(CheckForMessage);
}
public string Name
{
get;
set;
}
public void Connect()
{
pollTimer.Start();
}
public void Disconnect()
{
pollTimer.Stop();
}
private static Random random = new Random();
private void CheckForMessage(Object source, ElapsedEventArgs e)
{
Console.Write("Check for new Message.");
if ((random.Next(9) == 0) && (MessageArrived != null))
{
//引发一个事件
MessageArrived(this,new MessageArrivedEventArgs("Hello Mum!"));
}
}
}
//订阅事件的类
public class Display
{
public void DispalyMessage(Connection source,MessageArrivedEventArgs e)
{
Console.WriteLine("Message arrived :{0}",source.Name);
Console.WriteLine("Message Text{0}",e.Message);
}
}
public class MessageArrivedEventArgs : EventArgs
{
private string message;
public string Message
{
get
{
return message;
}
}
public MessageArrivedEventArgs()
{
message = "No message sent";
}
public MessageArrivedEventArgs(string newMessage)
{
message = newMessage;
}
}
class Program
{
static void Main(string[] args)
{
Connection myConncetion1 = new Connection();
myConncetion1.Name = "First Connection";
Connection myConncetion2 = new Connection();
myConncetion2.Name = "Second Connection";
Display myDisplay = new Display();
//+=订阅事件
myConncetion1.MessageArrived+=new MessageHandler(myDisplay.DispalyMessage);
myConncetion2.MessageArrived += new MessageHandler(myDisplay.DispalyMessage);
myConncetion1.Connect();
myConncetion2.Connect();
Console.ReadKey();
} }
}
**可以自定义add和remove处理程序
//define the event publish
publish enent TemperatureChangeHandler OnTemperatureChage
{
add
{
System.Delegate.Combine(value,_OnTemperatureChange);
}
remove
{
System.Delegate.Remove(_OnTemperatureChange,value);
}
}
protected TemperatureChangeHander _OnTemperatureChange;