在大家学习与掌握.Net的过程中,理解委托与事件的概念,并恰当的使用它是跨越新手门槛额关键,下面结合我学习时的理解及几个经典的委托与事件的使用例子讲述一下。也算是对自己学习的一个总结。
委托与事件是实现观察者模式的一种非常好的工具,委托充当了抽象的Observer(观察者)的接口,提供事件的对象充当了目标对象。一个对象(目标对象)的状态发生改变,所有依赖对象(观察者对象)都将得到通知(TerryLee)。即当观察者中符合事件声明中委托的类型的方法订阅了目标对象的事件后。当目标对象事件触发时(由于状态改变),会执行观察者中相应的方法(即通知观察者对象)。
下面的例子来自于TerryLee的文章,李哥的代码经典,拿出来分析学习一下J。
using System.Collections.Generic;
using System.Text;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
//目标对象
Stock stock = new Stock("Microsoft", 120.00);
//观察者
Investor investor = new Investor("Jom");
//观察者订阅目标对象的事件
//此处将观察者与目标对象关联了起来
stock.NotifyEvent += new NotifyEventHandler(investor.SendData);
//此操作触发事件 -- 观察者会响应此事件
stock.Update();
Console.ReadLine();
}
}
public delegate void NotifyEventHandler(object sender);
public class Stock
{
//此处声明一个事件(省略了event关键字)
//此为目标对象处
public NotifyEventHandler NotifyEvent;
private String _symbol;
private double _price;
public Stock(String symbol, double price)
{
this._symbol = symbol;
this._price = price;
}
public void Update()
{
OnNotifyChange();
}
//触发一个事件的操作
public void OnNotifyChange()
{
if (NotifyEvent != null)
{
//实际触发的操作 -- 在检测事件(委托对象)不为空后触发
//传给事件(委托对象)的参数是目标对象,最终此对象被传递给观察者处理此事件的方法
NotifyEvent(this);
}
}
public String Symbol
{ get { return _symbol; } }
public double Price
{ get { return _price; } }
}
//观察者
public class Investor
{
private string _name;
public Investor(string name)
{
this._name = name;
}
//观察者中在事件触发时,处理目标对象的函数
//此处接受的参数为触发事件时传入的参数
public void SendData(object obj)
{
if (obj is Stock)
{
Stock stock = (Stock)obj;
Console.WriteLine("Notified {0} of {1}'s " + "change to {2:C}", _name, stock.Symbol, stock.Price);
}
}
}
}
事件详解:
以下伴随一个示例说明定义一个事件的步骤:
示例来自C# via CLR,示例完成的功能:Fax,Pager两个对象订阅MailManager的事件。新邮件抵达时MailManager触发事件,MailManager将此通知送到订阅它的对象,这些对象以自己的方式处理邮件。
第一步:定义一个类型用于存放所有需要发送给事件通知接收者的附加信息
按照约定,所有传递给事件处理程序的用于存放事件信息的类都应该继承自System.EventArgs,并且类的名称应该以EventArgs结束。定义一个不需要任何额外信息的事件(如Button的Click事件)时,可以直接使用EventArgs.Empty,不用构建一个新的EventArgs类。
internal class NewMailEventArgs : EventArgs {
private readonly String m_from, m_to, m_subject;
public NewMailEventArgs(String from, String to, String subject)
{
m_from = from;
m_to = to;
m_subject = subject;
}
public String From
{
get { return m_from; }
}
public String To
{
get { return m_to; }
}
public String Subject
{
get { return m_subject; }
}
}
第二步:定义事件成员
事件成员使用C#关键字event定义。
一个事件的定义包括以下部分:
-
一个给定的可访问性(通常都为public)
-
一个表示即将被调用方法的原型的委托类型(其含义是所有事件通知的接收者都必须提供一个原型和此委托类型相匹配的回调方法)
-
一个事件成员的名称(可以是任意有效的标识符)
代码
public event EventHandler<NewMailEventArgs> NewMail;
几个委托类型的约定定义方法:
-
sender参数的类型为Object
-
派生自EventArgs类的参数命名为e
-
事件处理程序的返回类型都为void
第三步:定义一个负责引发事件的方法来通知已订阅事件的对象事件已经发生
protected virtual void OnNewMail(NewMailEventArgs e) {
// 处于线程安全考虑,将委托字段保存到一个临时字段中
EventHandler<NewMailEventArgs> temp = NewMail;
// 通知所有已订阅事件的对象
if (temp != null) temp(this, e);
}
// 定义了一个方法将输入转化为期望事件
public void SimulateNewMail(String from, String to, String subject) {
// 构建一个对象来存放我们希望传递给通知接收者的信息
NewMailEventArgs e = new NewMailEventArgs(from, to, subject);
// 调用方法以通知对象事件已发生
OnNewMail(e);
监听事件的类型的设计
// 然后对MailManager的NewMail时间注册回调方法
mm.NewMail += FaxMsg;
注以上代码内部使用了委托推断。
以下是注销MailManager的NewMail事件的代码
说明:
事件触发时,订阅该事件的函数会自动执行。事件并不关心有多少函数订阅它。它只管发出这个事件被触发的通知。
private void FaxMsg(Object sender, NewMailEventArgs e) {
Console.WriteLine("Faxing mail message:");
Console.WriteLine(" From={0}, To={1}, Subject={2}", e.From, e.To, e.Subject);
}
参考资料:
TerryLee:.NET设计模式(19):观察者模式(Observer Pattern)
C# via CLR(第二版)- 清华大学出版社