1 事件
类似于Javasript中的事件,当某个特定的的程序事件发生了,程序的其他部分可以收到该事件已触发的通知进而作出一些 事件处理程序(回调函数)。这种模式就叫做 发布者/订阅者模式。
发布者类 定义一些订阅者可能感兴趣的 事件,订阅者类 通过发布者提供的方法来“注册”以获取通知,当事件发生时,然后会 依次 执行订阅者提交的所有方法(即事件处理程序,回调函数)。
由上述要知道的重要点:
- 发布者:发布者是发布某个事件的 类 或 结构,即是包含事件成员的 类 或 结构,因此事件是类和结构的成员
- 订阅者:订阅者是注册并在事件触发时得到通知的 类 或 结构,即包含回调函数,并常在其构造函数中进行事件绑定注册的 类 或 结构
- 触发(raise)事件:调用(invoke)或触发(fire)事件的术语,当事件触发时,所有注册到它的方法都会被依次调用
“当事件触发时,所有注册到它的方法都会被依次调用”,有这句话可以看出事件和委托很像,像是专门用于特殊用途的委托。而事实上,事件包含一个私有的委托。
2 使用事件过程中的源代码组件
一个事件由 事件创建、被回调函数注册、事件触发、再到执行回调函数 这个过程中,要使用的代码按用处可分为五块:
- 委托类型声明:因为事件包含一个私有的委托,而这个私有委托实际上是用于存储注册的回调函数,所以 事件、事件声明委托与事件处理程序(即回调函数)都必须有 共同的签名和返回类型。
- 事件声明:定义一个发布者类,类中声明一个可以由订阅者类可以注册的事件成员
- 事件处理程序声明(回调函数):订阅者类中回调方法的声明
- 事件注册:订阅者必须订阅了事件,才能在它被触发的时候得到通知
- 触发事件的代码:发布者类中触发事件的代码
由此可见,委托类型由于其本身是一种用户自定义的类型,所以在 订阅者类 和 发布者类 之外定义,而事件的声明和触发事件的代码就需要在 发布者类 中定义,事件的注册和触发事件的代码则需要在 订阅者类 中定义。
2.1 声明事件
发布者必须提供事件对象,创建事件需要 委托类型 和 事件名称:
- 在类中,使用
event
关键字 + 委托类型 + 事件名称 - 它声明为
public
,这样其他类或结构就可以在它上面注册回调方法
2.2 订阅事件
订阅者中定义的回调方法,必须具有与 事件的委托 相同的返回类型和签名。
可以使用 +=
运算符来为事件注册回调方法(同理可以使用 -=
运算符移除),注册时引用回调函数的规范可以是以下一种:
- 实例方法的名称
- 静态方法的名称
- 匿名方法
- lumbda 表达式
例如:
incrementer.CountedADozen += IncrementDozensCount; // 实例方法
incrementer.CountedADozen += ClassB.CountedHandlerB; // 静态方法
incrementer.CountedADozen += new EventHandler(cc.CounterHandlerC); // 委托形式的实例方法
incrementer.CountedADozen += () => DozensCount++; // lambda
incrementer.CountedADozen += delegate { DozensCount++; }; // 匿名表达式
2.3 标准事件
对于事件的使用,.NET 框架提供了一个标准模式。事件使用的标准模式的根本就是 System
命名空间声明的 EventHandler
委托类型。
关于以声明 EventHandler
类型的委托:
public delegate void EventHandler(object sender, EventArgs e);
其中:
- 参数一用于保存触发事件的对象的引用,由于是
object
类型,所以通过隐式转化可以匹配到任何类型的实例 - 参数二用来保存状态信息,是声明在
System
命名空间中的EventArgs
类的对象,然而它被设计用于不需要传递数据的(通常会被忽略)。如果想要传递信息,必须声明一个派生自EventArgs
的类,然后使用合适的字段来保存需要传递的信息。 - 返回的是
void
eg:
发布者:
class Incrementer
{
public event EventHandler CountedADozen; // 声明事件,使用 EventHandler 委托类型
public void DoCount()
{
for(int i=1; i<100; i++)
{
if (i%12==0 && CountedADozen != null)
{
CountedADozen(this, null); // 触发事件
}
}
}
}
订阅者
class Dozens
{
public int DozensCount {get; private set;}
public Dozens(Incrementer incrementer)
{
DozensCount = 0;
incrementer.CountedADozen += IncrementDozensCount; // 构造函数注册回调事件
}
void IncrementDozensCount(object source, EventArgs e)
{
DozensCount ++;
} // 回调方法的签名必须与委托的签名相同
}
主程序:
class Program
{
static void Main()
{
Incrementer incrementer = new Incrementer;
Dozens dozensCounter = new Dozens(incrementer);
incrementer.DoCount(); // 触发事件
Console.WriteLine("Number of dozens = {0}", dozensCounter.DozensCount);
}
}
2.3.1 通过 EventArgs
来传递数据
为了通过 EventArgs
来传递数据,就必须声明派生自 EventArgs
的自定义类
,它可以保存传入的数据,类的名字应该以 EventArgs
结尾,如:
public class IncrementerEventArgs: EventArgs
{
public int IterationCount {get; set;}
}
此外还需要重新定义委托类型,这时可以使用泛型版本的委托 EventHandler<>
,例如:
// EventArgs 的派生类
public class IncrementerEventArgs: EventArgs
{
public int IterationCount {get; set;}
}
// 发布者
class Incrementer
{
public event EventHandler<IncrementerEventArgs> CountedADozen; // 泛型委托
public void DoCount()
{
IncrementerEventArgs args = new IncrementerEventArgs();
for(int i=1; i<100; i++)
{
if (i%12==0 && CountedADozen != null)
{
args.IterationCount = i;
CountedADozen(this, args); // 触发事件
}
}
}
}
// 订阅者
class Dozens
{
public int DozensCount {get; private set;}
public Dozens(Incrementer incrementer)
{
DozensCount = 0;
incrementer.CountedADozen += IncrementDozensCount; // 构造函数注册回调事件
}
void IncrementDozensCount(object source, IncrementerEventArgs args)
{
DozensCount ++;
Console.WriteLine("Iteration: {0}", args.IterationCount); // 传递数据
}
}
// 主程序
class Program
{
static void Main()
{
Incrementer incrementer = new Incrementer;
Dozens dozensCounter = new Dozens(incrementer);
incrementer.DoCount(); // 触发事件
Console.WriteLine("Number of dozens = {0}", dozensCounter.DozensCount);
}
}