广播和订阅
使用委托的时候,通常会出现两个角色,一个广播者,一个订阅者
广播者这个类型包含一个委托字段,广播者通过调用委托来决定什么时候进行广播。
订阅者是方法目标的接受者,订阅者可以决定何时开始或结束监听,方式是通过在委托上调用+=和-=。
一个订阅者不知道和不干扰其它的订阅者。
Event事件
事件就是将上述模式正式化的一个语言特性。
事件是一种结构,为了实现广播者/订阅者模型,它只暴露了所需要的委托特性的部分子集。
事件的主要目的就是防止订阅者之间互相干扰。
声明事件
内外区别对待
加上event之后,(对于Broadcaster)只能对PriceChangedHandler进行+=和-=操作了。而对Broadcaster内部,还只是一个委托。
事件在内部是如何工作的
add和remove关键字代表着显式的事件访问器,有点像属性访问器。
然后编译器会查看Broadcaster内部对PriceChanged的引用,如果不是+=或-的操操作,那就直接把它们定向到底层的委托字段priceChanged。
第三点,编译器把作用在event上的+=和-=操作翻译成调用add或remove访问器
标准的事件模式
1.(传递信息的类有了)为编写事件, .NET定义了一个标准的模式
System.EventArgs,一个预定义的框架类,除了静态的Empty属性之外,它没有其它成员。
Eventrgs是为事件传递信息的类的基类。
通常是根据所含有信息进行命名(比如含有价格,某一属性,点击某个按钮或其它控件的信息),而不是所使用的事件。
通常通过属性或只读字段来暴露数据。
为事件选择或定义委托
返回类型必须是void;
接收两个参数,第一个参数类型是object,第二参数类型是EventArgs的子类。第一个参数表示事件的广播者(比如button),第二个参数包含需要传递的信息(比如传递的参数);
名称必须以EventHandler结尾(方便查看和区分)。
System.EventHandler
.NET Framework定义了一个泛型委托System.EventHandler
public delegate void EventHandler<TEventArgs>(object sender, TEventArgs e) where TEventArgs : EventArgs;
2.(委托也有了)
针对选择的委托定义事件
可触发事件的protected virtual方法
3.(事件就应该有一个可以触发事件的方法)
方法名必须和事件一致,前面再加上On,接受一个EventArgs参数
完整的例子
注意
多线程的场景下,你需要在测试或调用前,把委托赋给一个临时变量,来避免线程安全相关的错误。
var temp = PriceChanged;
if(temp != null) temp(this,e);
在c#6之后,可以这样写:
priceChanged?.Invoke(this,e);
非泛型的EventHandler
当事件不携带多余信息的时候(常见的Button按钮事件),可以使用非泛型的EventHandler委托。
EventArgs.Empty属性
事件访问器
事件访问器事件的+=和-=函数的实现。
public event EventHandler PriceChanged;
编译器会把它转化为:
一个私有的委托字段
一对公共的事件访问器函数(add_PriceChanged和remove_priceChanged),这两个函数的实现会把+=和-=操作交给私有的委托字段。
也可以显式的定义事件访问器
显示定义的事件访问器
private EventHandler priceChanged;
public event Eventandler PriceChanged
{
add { priceChanged += value; }
remove { priceChanged -= value; }
}
使用显示访问器:
当事件访问器仅仅是另一个广播事件的类的中继。
当类暴露大量event,但是大部分时候都只有少数的订阅者存在(Winfrom各种控件事件)。
显示实现一个声明了事件的接口。
事件修饰符
virtual,可以被重写;abstract,sealed,static
事件:
- 一个传递信息(参数)的类(xxxEventArgs:EventArgs) 也可以不传递
- 一个委托,一般使用预定义委托EventHandler,不是约定义命名规则xxxEventHandler.
- 一个事件,使用预定义委托的直接event EventHandler。如果传递参数EventHandler
- 一个可触发事件的方法(protected virtual修饰)
protected virtual void Onxxx(xxx是事件的名称)(xxxEventArgs e)
{
// 触发的事件
xxxEventHandlerName?.Invoke(触发的事件(xx,e))
}