第10讲事件2
现在继续讲解事件。我们首先回顾一下定义一个完整事件的四个步骤:
在事件发行者中定义一个事件
声明一个事件首先要声明一个准备跟事件关联的委托类型,接下来根据前面所声明的委托类型声明自己的事件,声明事件用even关键字。(上节代码所示)
在事件发行者中触发事件
事件的触发必须通过一个过程,这个过程首先判断事件是否为空,如果不为空,就直接触发事件。它的本质就是触发一个委托链。
在事件订阅者中定义事件处理程序
这个事件处理的程序,必须跟委托类型相一致,也就是说它的返回值跟参数类型都必须相同。
向事件发行者订阅一个事件
订阅一个事件使用+=关键字,取消订阅使用-=。这个跟委托链是一致的。
接下来我们来讲一下.NET Framework事件设计准则,.NET对于事件的设计有他的一套规范,我们应该遵守这个规范。
首先,事件的命名准则应使用PascalCasing命名方式。
什么是PascalCasing命名方式呢?我们来看一下EventName这个单词,首先Event是一个单独的英文单词,它的意思是事件,Name也是一个单独的英文单词,它的意思是名称。我们一看到这2个单词组合在一次我们就可以联想到它是事件的名称,而这2个英文单词的首字母都是用大写的,通过首字母大写的命名方式,我们就可以很容易区分出2个英文单词,而这个呢就是PascalCasing的命名方式。
(声明一个事件呢要声明它对应的委托链)声明delegate时,使用void类型当作返回值(是必须使用void当作返回值,为什么呢?因为我们讲过事件它的本质是一个委托链这个委托链也包含多个代理,如果每个代理都有返回值,那它返回的是哪一个返回值呢,只能是最后一个。所以呢事件拥有返回值这也就失去了它的意义),EventName事件的事件委托是EventNameEventHandler,事件的接受两个传入参数,一律命为sender与e。
定义一个提供事件数据的类,对类以EventNameEventArgs进行命名,从System.EventArgs派生该类,然后添加所有事件特定的成员。
上面是一个委托的声明,sender的类型必须是object类型,而e的类型呢它可以为EventArgs,也可以自己声明一个类,而这个类呢是从System.EventArgs派生而来,如果我们的事件需要传输一个或多个参数,这个时候我们就可以System.EventArgs派生一个类,把这些参数全部打包与EventNameEventArgs这个类里成为这个类的一个成员。当然这个类也有他的命名规范就是事件名称加上EventArgs(EventNameEventArgs)。
我们看,这里有个事件声明的例子。
如果事件不需要传递任何数据,这个时候也需要传递两个参数(sender,e),e参数可以直接使用系统提供的System.EventArgs类。也就是说你不需要再创建新类了。但是如果需要传递数据,则要从System.EventArgs继承一个类,并把数据放在里面。
下面呢我们就把上节课的所做的例子按照规范进行下改写。在修改中,还有个设计准则:
在引发事件的类中提供一个受保护的方法。以OnEventName进行命名。在该方法中引发该事件
virtual表示一个虚方法,它的子类可以重写这个方法。在方法体内直接引发事件。
修改代码如下:
执行我们规范过的代码,效果如下:
好,正常显示,和上节课的基本相同,只是添加了出版时间的显示。我们花了一节课的时间把我们第一节课所做这个事件的程序做了一个规范化。可能有些人会有疑问了,有没有必要花那么多的时间去规范化的去编写呢?这是非常有用的,而且也非常值得。如果所有程序员都按照一个规范来写程序,那么在你看别人代码的时候就会变的非常容易,所以呢我们要尽量按照规范写程序。
在事件发行者中定义一个事件
声明一个事件首先要声明一个准备跟事件关联的委托类型,接下来根据前面所声明的委托类型声明自己的事件,声明事件用even关键字。(上节代码所示)
- //委托类型
- public delegate void PubComputer(string magazineName);
- //事件
- public event PubComputer OnPubComputer;
事件的触发必须通过一个过程,这个过程首先判断事件是否为空,如果不为空,就直接触发事件。它的本质就是触发一个委托链。
- //过程
- public void issueComputer()
- {
- if (OnPubComputer != null)
- {
- Console.WriteLine("发行《电脑》杂志");
- //触发事件
- OnPubComputer("电脑杂志");
- }
- }
这个事件处理的程序,必须跟委托类型相一致,也就是说它的返回值跟参数类型都必须相同。
- //事件处理程序
- public void Receive(string magazineName)
- {
- Console.WriteLine(name + "已经收到" + magazineName);
- }
订阅一个事件使用+=关键字,取消订阅使用-=。这个跟委托链是一致的。
- //李四订阅了电脑杂志,还订阅了生活杂志
- Pub.OnPubComputer += new Publisher.PubComputer(ls.Receive);
- //李四不再订阅电脑杂志了
- Pub.OnPubComputer -= new Publisher.PubComputer(ls.Receive);
首先,事件的命名准则应使用PascalCasing命名方式。
什么是PascalCasing命名方式呢?我们来看一下EventName这个单词,首先Event是一个单独的英文单词,它的意思是事件,Name也是一个单独的英文单词,它的意思是名称。我们一看到这2个单词组合在一次我们就可以联想到它是事件的名称,而这2个英文单词的首字母都是用大写的,通过首字母大写的命名方式,我们就可以很容易区分出2个英文单词,而这个呢就是PascalCasing的命名方式。
(声明一个事件呢要声明它对应的委托链)声明delegate时,使用void类型当作返回值(是必须使用void当作返回值,为什么呢?因为我们讲过事件它的本质是一个委托链这个委托链也包含多个代理,如果每个代理都有返回值,那它返回的是哪一个返回值呢,只能是最后一个。所以呢事件拥有返回值这也就失去了它的意义),EventName事件的事件委托是EventNameEventHandler,事件的接受两个传入参数,一律命为sender与e。
- public delagate void EventNameEventHandler(object sender, EventNameEventArgs e);
上面是一个委托的声明,sender的类型必须是object类型,而e的类型呢它可以为EventArgs,也可以自己声明一个类,而这个类呢是从System.EventArgs派生而来,如果我们的事件需要传输一个或多个参数,这个时候我们就可以System.EventArgs派生一个类,把这些参数全部打包与EventNameEventArgs这个类里成为这个类的一个成员。当然这个类也有他的命名规范就是事件名称加上EventArgs(EventNameEventArgs)。
我们看,这里有个事件声明的例子。
- public delegate void EventNameEventHandler(object sender, EventArgs e);
- public event EventNameEventHandler EventName;
下面呢我们就把上节课的所做的例子按照规范进行下改写。在修改中,还有个设计准则:
在引发事件的类中提供一个受保护的方法。以OnEventName进行命名。在该方法中引发该事件
- protected virtual void OnEventName(EventArgs e)
- {
- if(EventName != null)
- {
- EventName(this, e);
- }
- }
修改代码如下:
- using System;
- //PubEventArgs类继承EventArgs类
- class PubEventArgs : EventArgs
- {
- //声明一个私有只读的成员m_magazineName杂志名称,把它提供给属性使用
- private readonly string m_magazineName;
- //声明一个出版日期
- private readonly DateTime m_pubDate;
- //声明构造函数将其赋值
- public PubEventArgs(string magazineName, DateTime pubDate)
- {
- m_magazineName = magazineName;
- m_pubDate = pubDate;
- }
- //给私有成员添加属性
- public string magazineName
- {
- //它是只读的所以只提供get方法
- get { return m_magazineName; }
- }
- public DateTime pubDate
- {
- get { return m_pubDate; }
- }
- }
- class Publisher //出版社
- {
- //委托名称规范,PubEventArgs类需要定义,就在上面第一个PubEventArgs类
- public delegate void PubComputerEventHandler(object sender, PubEventArgs e);
- public delegate void PubLifeEventHandler(object sender, PubEventArgs e);
- //事件的名称规范
- public event PubComputerEventHandler PubComputer;
- public event PubLifeEventHandler PubLife;
- //受保护的方法
- protected virtual void OnPubComputer(PubEventArgs e)
- {
- //声明一个临时的委托handler,为什么要用一个临时的变量来操作呢?
- //这样做主要是为了防止可能存在的线程同步问题
- PubComputerEventHandler handler = PubComputer;
- if (handler != null)
- {
- //触发事件
- handler(this, e);
- }
- }
- protected virtual void OnPubLife(PubEventArgs e)
- {
- PubLifeEventHandler handler = PubLife;
- if (handler != null)
- {
- handler(this, e);
- }
- }
- //发行方法加入参数,一个是杂志名称还有一个是杂志日期
- public void issueComputer(string magazineName, DateTime pubDate)
- {
- Console.WriteLine("发行" + magazineName);
- //调用受保护虚方法,触发事件
- OnPubComputer(new PubEventArgs(magazineName, pubDate));
- }
- public void issueLife(string magazineName, DateTime pubDate)
- {
- Console.WriteLine("发行" + magazineName);
- //调用受保护虚方法,触发事件
- OnPubLife(new PubEventArgs(magazineName, pubDate));
- }
- }
- class Subcriber //订阅者
- {
- private string name;
- public Subcriber(string name)
- {
- this.name = name;
- }
- //修改Receive方法,其类型要与委托一致
- public void Receive(object sender, PubEventArgs e)
- {
- Console.WriteLine(e.pubDate + " " + name + "已经收到" + e.magazineName);
- }
- }
- class Story
- {
- //主函数修改
- static void Main()
- {
- Publisher Pub = new Publisher();
- Subcriber zs = new Subcriber("张三");
- Pub.PubComputer += new Publisher.PubComputerEventHandler(zs.Receive);
- Subcriber ls = new Subcriber("李四");
- Pub.PubComputer += new Publisher.PubComputerEventHandler(ls.Receive);
- Pub.PubLife += new Publisher.PubLifeEventHandler(ls.Receive);
- Pub.issueComputer("电脑杂志", Convert.ToDateTime("2006-1-1"));
- Pub.issueLife("生活杂志", Convert.ToDateTime("2006-1-1"));
- Console.WriteLine();
- Console.WriteLine("一年以后");
- Pub.PubComputer -= new Publisher.PubComputerEventHandler(ls.Receive);
- Pub.issueComputer("电脑杂志", Convert.ToDateTime("2007-1-1"));
- Pub.issueLife("生活杂志", Convert.ToDateTime("2007-1-1"));
- }
- }

好,正常显示,和上节课的基本相同,只是添加了出版时间的显示。我们花了一节课的时间把我们第一节课所做这个事件的程序做了一个规范化。可能有些人会有疑问了,有没有必要花那么多的时间去规范化的去编写呢?这是非常有用的,而且也非常值得。如果所有程序员都按照一个规范来写程序,那么在你看别人代码的时候就会变的非常容易,所以呢我们要尽量按照规范写程序。