// 引用声明:本文部分内容参考自 Solis, D.M., Illustrated C# 2012(Fourth Edition). 2013: 人民邮电出版社.
// 版权声明:支持原创,转载引用链接:https://blog.csdn.net/img_Guo/article/details/83047721
一、发布者和订阅者
很多程序都有一个共同的需求,即当一个特定的程序事件发生时,程序的其他部分可以得到该事件已经发生的通知。
发布者/订阅者模式(publisher/subscriber pattern)可以满足这种需求。在这种模式中,发布者类定义了一系列程序的其他部分可能感兴趣的事件。其他类可以“注册”,以便在这些事件发生时发布者可以通知它们。这些订阅者类通过向发布者提供一个方法来“注册”以获取通知。当事件发生时,发布者“触发事件”,然后执行订阅者提交的所有事件。
回调方法/事件处理程序:由订阅者提供的方法称为回调方法,因为发布者通过执行这些方法来“往回调用订阅者的方法”,还可以将它们称为事件处理程序,因为他们是为处理事件而调用的代码。
1. 发布者(publisher) 发布某个事件的类或结构,其他类可以在该事件发生时得到通知;
2. 订阅者(subscriber) 注册并在事件发生时得到通知的类或结构;
3. 事件处理程序(event handler) 由订阅者注册得到事件的方法,在发布者触发事件中执行。事件处理程序方法可以定义在事件所在的类或结构中,也可以定义在不同的类或结构中。
4. 触发(raise)事件 调用(invoke)或触发(fire)事件的术语。当事件触发时,所有注册到它的方法都会被依次调用。
二、快速简介
事件就像是专门用于某种特殊用途的委托(前面已经介绍),原因是事件真的包含了一个被封装的私有的委托,但是这个委托是事件私有的,使用时应该注意:
1、你无法直接访问委托,事件自己提供了对它的结构化访问;
2、针对事件我们能使用的操作较少,对于事件我们只能添加、删除或调用事件处理程序;
3、事件被触发时,它调用委托来依此调用调用列表中的方法。
三、事件源代码组件
事件中使用的代码有下面5个部分:
1、委托类型声明:事件和事件处理程序必须有共同的签名和返回类型,它们通过委托类型进行描述;
2、事件处理程序声明:订阅者类中会在事件触发时执行的方法声明,可以是显式声明或者时Lambda表达式;
3、事件声明:发布者类必须声明一个订阅者类可以注册的事件成员。当声明的事件为public时,成为发布了事件;
4、事件注册:订阅者必须订阅事件才能在它被触发时得到通知;
5、触发事件的代码:发布者类中“触发”事件并导致调用注册的所有事件处理程序的代码。
四、声明事件
发布者类必须提供事件对象。规则如下:
1、事件需要声明在一个类中;
2、需要委托类型的名称(任何附加到事件的处理程序都必须与委托类型签名和返回类型相匹配);
3、它声明为public,这样其他类和结构可以在它上面注册事件处理程序;
4、不能使用对象创建表达式(new表达式)来创建事件的对象;
值得注意的是,事件是类或结构中的成员(和方法、属性一样),不是类型,因此声明事件又有以下的规则:
5、事件必须和其它成员一样声明在一个类中,不能声明在一段可执行代码中;
6、事件成员被隐式自动初始化为null;
/* 代码功能:举例看明白事件声明的用法
* 时间:2018年11月16日20点24分
*/
//声明一个委托
delegate void Handler();
//发布者类
class PublisherClass
{
//声明一个事件
public event Handler MyEvent;
//声明多个事件
public event Handler MyEvent1, MyEvent2, MyEvent3;
//声明静态类型事件
public static event Handler MyEvent4;
}
五、订阅事件
订阅者向事件添加事件处理程序。而对于这个事件处理程序来说,必须要具有与事件的委托相同的返回类型和签名才能添加。
1、使用 += 运算符来为事件增加事件处理程序,事件处理程序应该在运算符的右边;
2、事件处理程序是以下几种都可以:
1)实例方法的名称;2)静态方法的名称;3)匿名方法;4) Lambda表达式。
/* 代码功能:弄懂订阅事件的几种形式
* 时间:2018年11月16日20点48分
*/
//增加实例方法形式的事件处理程序
incrementer.CountedADozon += incrementerDozensCount;
//增加静态方法形式的事件处理程序
incrementer.CountedADozon += MyClass.CounterHandlerB;
//增加委托形式的实例方法
mc.CountedADzon += new EventHandler(cc.CounterHandlerC);
//使用Lambda表达式来添加事件处理程序
incrementer.CountedADzon += () => DozensCount++;
//使用匿名方法来添加事件处理程序
incrementer.CountedADzon += delegate {DozensCount++;};
六、触发事件
事件成员本身知识保存了需要被调用的事件处理程序,如果事件没有被触发,什么都不会发生,因此需要触发事件。
1、在触发事件之前要和null作比较,从而检查是否包含事件处理程序,如果事件是null,则表示什么都没有不能执行。
2、触发事件的语法和调用方法的语法相同:1)使用事件名称,后面跟的参数列表包含在圆括号中;2)参数列表必须和事件的委托类型相匹配。
if(CountedAZon != null)
CountedAZon(source,args);
七、C#事件程序例子
/* 时间:2018年11月16日22点38分
* 功能:提供一个C#事件的例子
*/
using System;
delegate void Hander1();
/// <summary>
/// 发布者类
/// </summary>
class Publisher
{
//创建事件并发布
public event Hander1 CountedADzon;
public void DoCount()
{
for (int i = 0; i < 100; ++i)
{
if (i % 12 == 0 && CountedADzon != null)
CountedADzon(); //每增加12个计数触发事件一次
}
}
}
/// <summary>
/// 订阅者类
/// </summary>
class Subscriber
{
public int DozensCount { get; private set; }
public Subscriber(Publisher publisher)
{
DozensCount = 0;
//订阅事件(就是往发布者事件的委托中插入一个事件处理程序)
//让发布者运行事件的时候运行一下自己类的指定事件处理程序,也就是自己类的本地函数方法)
publisher.CountedADzon += PublisherDozensCount;
}
//声明事件处理程序
private void PublisherDozensCount()
{
DozensCount++;
}
}
/// <summary>
/// 主程序
/// </summary>
class Program
{
static void Main()
{
Publisher publisher = new Publisher();
Subscriber subscriber = new Subscriber(publisher);
//触发事件
publisher.DoCount();
Console.WriteLine("Number of dozens = {0}", subscriber.DozensCount);
Console.ReadKey();
}
}
/* 程序运行结果
-----------------------------------------
Number of dezens = 9
-----------------------------------------
*/