事件为委托提供了一种发布/订阅机制。声明事件的类被称为发行者类,其他类可以订阅发行者类中的事件。当发行者类触发其中的事件时,所有订阅该事件的类都会收到这个变化。在图形界面框架中,这种情况非常常见。
事件发布者
首先需要创建一个事件发布者类。该类包含一个委托,并有一个基于该委托的事件,还应该有一个可以触发事件的函数,以便订阅者可以在其他地方接收到这个事件。事件常常定义为一个包含事件发布者和事件消息传递的委托。
//事件发布者
class NewsPublisher
{
//定义事件的委托
public delegate void NewsEventHandler(NewsPublisher sender, NewsEventArgs args);
//用委托定义的事件
public event NewsEventHandler NewsEvent;
//构造函数,初始化一个发布者的名称
public NewsPublisher(string name)
{
Name = name;
}
//发布事件的函数
public void PublishNews(string News)
{
//当事件有订阅者的时候
if (NewsEvent != null)
{
NewsEvent(this, new NewsEventArgs(News));
}
}
public string Name { set; get; }
}
事件传递的消息
跟随事件传递的消息类,既可以是一个包含很多信息的复杂类,也可以是一个仅仅包含一个字符串的简单类。在这里简单的定义一个只包含一条字符串信息的类来传递消息。
//定义需要随事件传递的信息
class NewsEventArgs
{
public NewsEventArgs(string News)
{
this.News = News;
}
public string News
{
get; set;
}
}
事件订阅者
有了事件发布者和事件消息之后,就可以定义事件订阅者了。订阅者需要有一个和发布者事件匹配的方法,用来接收事件响应。
//订阅事件的类
class NewsReader
{
//在构造函数中订阅一个事件
public NewsReader(string name, NewsPublisher publisher)
{
Name = name;
publisher.NewsEvent += HandleNewsEvent;
}
public string Name { get; set; }
//订阅的方法需要和事件类型匹配
public void HandleNewsEvent(NewsPublisher sender, NewsEventArgs args)
{
Console.WriteLine($"{Name} 收到 {sender.Name} 发来的消息:{args.News}");
}
}
事件的使用
有了事件发布者和事件订阅者,就可以来看看事件是怎么运作的了。
NewsPublisher publisher = new NewsPublisher("新闻发布者");
NewsReader reader1 = new NewsReader("读者1", publisher);
NewsReader reader2 = new NewsReader("读者2", publisher);
NewsReader reader3 = new NewsReader("读者3", publisher);
Console.WriteLine($"{publisher.Name}发布了一则新闻。");
publisher.PublishNews("早间新闻");
Console.WriteLine($"{publisher.Name}发布了一则新闻。");
publisher.PublishNews("晚间新闻");
这段程序的运行结果如下。从结果可以看出,事件其实就是一个多播委托。同样的,事件订阅者收到事件的顺序和它们订阅的顺序不一定相同,所以不要编写依赖于它们之间顺序的代码。
新闻发布者发布了一则新闻。
读者1 收到 新闻发布者 发来的消息:早间新闻
读者2 收到 新闻发布者 发来的消息:早间新闻
读者3 收到 新闻发布者 发来的消息:早间新闻
新闻发布者发布了一则新闻。
读者1 收到 新闻发布者 发来的消息:晚间新闻
读者2 收到 新闻发布者 发来的消息:晚间新闻
读者3 收到 新闻发布者 发来的消息:晚间新闻
预定义的事件
事件非常地常用,因此微软也为我们预定义了一个泛型委托EventHandler。它的形式如下:
public delegate void EventHandler<TEventArgs>(object sender, TEventArgs e) where TEventArgs: EventArgs;
从这个委托的定义可以看出,这里需要的事件消息类需要继承EventArgs。由于这个委托已经定义在System命名空间了,所以不需要再自己手动定义委托了。上面的例子也可以改写一下了。
首先,事件消息类需要继承EventArgs,除此之外不需要做其他的变动。
class NewsEventArgs : EventArgs
{
public NewsEventArgs(string News)
{
this.News = News;
}
public string News
{
get; set;
}
}
然后,事件发布者类需要删掉自定义的委托,并把事件类型改为泛型委托EventHandler。
class NewsPublisher
{
//使用泛型委托EventHandler
public event EventHandler<NewsEventArgs> NewsEvent;
//构造函数,初始化一个发布者的名称
public NewsPublisher(string name)
{
Name = name;
}
//发布事件的函数
public void PublishNews(string News)
{
//当事件有订阅者的时候
if (NewsEvent != null)
{
NewsEvent(this, new NewsEventArgs(News));
}
}
public string Name { set; get; }
}
事件订阅者里面也要做一些改动,让方法匹配EventHandler的定义。
class NewsReader
{
//在构造函数中订阅一个事件
public NewsReader(string name, NewsPublisher publisher)
{
Name = name;
publisher.NewsEvent += HandleNewsEvent;
}
public string Name { get; set; }
//订阅的方法需要和事件类型匹配
public void HandleNewsEvent(object sender, NewsEventArgs args)
{
Console.WriteLine($"{Name} 收到 {((NewsPublisher)sender).Name} 发来的消息:{args.News}");
}
}
这样,就可以和原来一样的使用事件了。EventHandler非常常用,在Windows图形界面编程里,会经常看到这样的方式。