现在让我们来讨论类型中可定义的最后一种成员:事件
类型之所以能提供事件通知功能,是因为类型维护了一个已登记方法的列表。事件发生后,类型将通知列表中所有已登记的方法。
CLR的事件模型建立在委托的基础上。
现在我们来描述一个场景,一个电子邮件系统,当电子邮件到达时,可以将该邮件转发给传真机处理。因此,我们需要先设计一个MailManager类,它负责接受传入的邮件,它维护了一个已登记方法的列表,它公开了一个名为NewMail的事件。还要设计一个Fax类,它的方法可登记或取消登记对这个NewMail事件的关注。当MailManager收到一封电子邮件时,会引发NewMail事件,然后它将该邮件发给Fax,Fax将会按自己的方法处理邮件。
设计MailManager这样一个公开了事件的类需要经过多个步骤②③④
①定义类型来容纳所有需要发送给事件通知接受者(Fax)的附加信息
事件引发时,引发事件的对象可能希望向接受事件的通知的对象传递一些附加的信息。这些附加信息需要封装到它自己的类中,这个类通常包含一组私有字段,以及一些用于公开这些字段的只读公共属性。根据约定,这种类应该从System.EventArgs派生,并且类名应该以EventArgs结束。
internal class NewMailEventArgs:EventArgs
{
private 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; } }
}
internal class MailManager
{
②定义事件成员
几乎肯定是public,这样其他代码才能访问该事件成员;event关键字;一个委托类型,它指出要调用方法的原型; 一个事件名称
public event EventHandler<NewMailEventArgs> NewMail;
EventHandler<NewMailEventArgs>意味着Fax必须提供一个方法和EventHandler<NewMailEventArgs>委 托类型匹配的回调方法,由于泛型System.EventHandler委托类型的定义如下:
public delegate void EventHandler<TEventArgs>(Object sender,TEventArgs e) where TEventArgs:EventArgs;
所以方法原型必须具有以下形式:
void MethodName(Object sender,NewMailEventArgs e) lookhere16
③定义负责引发事件的方法来通知事件的登记对象
根据约定,还需要定义一个受保护的虚方法。要引发事件时,当前类及其派生类中的代码会调用该方法。该方法要获 取一个参数,也就是一个NewMailEventArgs对象。该方法的默认实现只是检查一下是否有对象登记了对事件的关 注。如果有,就引发事件,从而通知事件的登记对象。
protected virtual void OnNewMail(NewMailEventArgs e)
{
EventHandler<NewMailEventArgs> temp = System.Threading.Interlocked.CompareExchange(ref NewMail, null, null);
if (temp != null) { temp(this, e);}
}
④如果需要获取一些用户输入(这里是from,to,subject),可以定义一个方法调用OnNewMail方法从而引发事件
public void WriteMail(string from, string to, string subject)
{
NewMailEventArgs e = new NewMailEventArgs(from, to, subject);
OnNewMail(e);
}
}
public event EventHandler<NewMailEventArgs> NewMail;
C#编译器在编译以上一行代码时,会把它转换为以下3个构造
private EventHandler<NewMailEventArgs> NewMail=null;
public void add_NewMail(EventHandler<NewMailEventArgs> value){...}
public void remove_NewMail(EventHandler<NewMailEventArgs> value){...}
设计侦听事件的类型Fax
internal class Fax
{
public void register(MailManager mm)
{
mm.NewMail+=doFax;
}
public void unregister(MailManager mm)
{
mm.NewMail -= doFax;
}
public void doFax(object sender,NewMailEventArgs e) {
System.Windows.Forms.MessageBox.Show(e.From+" "+e.To+" "+e.Subject+" "+sender.GetType().ToString());
}
}
在Form中测试
public partial class Form1 : Form
{
MailManager mm = new MailManager();
Fax fax = new Fax();
public Form1()
{
InitializeComponent();
}
private void btnRegister_Click(object sender, EventArgs e)
{
fax.register(mm);
}
private void btnUnregister_Click(object sender, EventArgs e)
{
fax.unregister(mm);
}
private void btnSend_Click(object sender, EventArgs e)
{
string from= txtFrom.Text;
string to=txtTo.Text;
string subject = txtSubject.Text;
mm.WriteMail(from,to,subject);
}
}
先点击btnRegister(关注事件)再点击Send,Fax会收到通知 先点击btnUnregister(取消关注)再点击Send,Fax不会收到通知
拿Control类型来讲,它大概定义了70个事件,如果每个事件都是像上面那样实现,那么每个Control对象将会包含70个委托,所以在创建Control对象时这会浪费大量的内存。所以微软通过一个System.ComponentModel.EventHandlerList类型来解决这个问题。现在就让我们写一个类似EventHandlerList的类型--EventSet:
之前先创建一个EventKey类:
这个类的目的是在使用EventSet时,提供多一点的类型安全性和代码可维护性
public sealed class EventKey{}
public sealed class EventSet
{
私有字典用于维护 EventKey --> Delegate 映射
private readonly Dictionary<EventKey, Delegate> m_events = new Dictionary<EventKey, Delegate>();
添加一个EventKey --> Delegate 映射(如果EventKey不存在),或者将一个委托与一个现有的EventKey合并
public void Add(EventKey eventkey, Delegate handler) {
System.Threading.Monitor.Enter(m_events);
Delegate d;
m_events.TryGetValue(eventkey, out d);
m_events[eventkey] = Delegate.Combine(d, handler);
System.Threading.Monitor.Exit(m_events);
}
从EventKey(如果它存在)删除一个委托,并且在删除最后一个委托时删除EventKey --> Delegate 映射
public void Remove(EventKey eventkey, Delegate handler) {
System.Threading.Monitor.Enter(m_events);
Delegate d;
if (m_events.TryGetValue(eventkey,out d))
{
d = Delegate.Remove(d, handler);
如果还有委托,就设置新的头部(地址),否则删除EventKey
if (d != null)
{
m_events[eventkey] = d;
}
else
{
m_events.Remove(eventkey);
}
}
}
为指定的EventKey引发事件
public void Raise(EventKey eventkey, object sender, EventArgs e) {
如果EventKey不在集合中,不抛出一个异常
Delegate d;
System.Threading.Monitor.Enter(m_events);
m_events.TryGetValue(eventkey, out d);
System.Threading.Monitor.Exit(m_events);
if (d!=null)
{
由于字典可能包含几个不同的委托类型,所以无法在编译的时候构造一个类型安全的委托调用。
因此我调用System.Delegate类型的DynamicInvoke方法,以一个对象数组的形式向它传递回调方法的参数。
在内部DynamicInvoke会向调用的回调方法查证参数的类型安全性,并调用方法。如果存在类型不匹配的情况,DynamicInvoke会抛出一个异常。
d.DynamicInvoke(new object[] { sender, e });
}
}
}
我们再用这个EventSet来实现MailManager,现在我们叫它MailManager2:
internal class MailManager2
{
private readonly EventSet m_eventset = new EventSet();
public EventSet Eventset
{
get { return m_eventset; }
}
protected static readonly EventKey m_eventkey = new EventKey();
public event EventHandler<NewMailEventArgs> NewMail {
add { m_eventset.Add(m_eventkey, value); }
remove { m_eventset.Remove(m_eventkey, value); }
}
protected virtual void OnNewMail(NewMailEventArgs e) {
m_eventset.Raise(m_eventkey, this, e);
}
public void WriteMail(string from, string to, string subject)
{
NewMailEventArgs e = new NewMailEventArgs(from, to, subject);
OnNewMail(e);
}
}
在Form中测试
public partial class Form1 : Form
{
MailManager2 mm = new MailManager2();
Fax fax = new Fax();
public Form1()
{
InitializeComponent();
}
private void btnRegister_Click(object sender, EventArgs e)
{
fax.register(mm);
}
private void btnUnregister_Click(object sender, EventArgs e)
{
fax.unregister(mm);
}
private void btnSend_Click(object sender, EventArgs e)
{
string from= txtFrom.Text;
string to=txtTo.Text;
string subject = txtSubject.Text;
mm.WriteMail(from,to,subject);
}
}
会看到相同的效果