广播和订阅

使用委托的时候,通常会出现两个角色,一个广播者,一个订阅者
广播者这个类型包含一个委托字段,广播者通过调用委托来决定什么时候进行广播。
订阅者是方法目标的接受者,订阅者可以决定何时开始或结束监听,方式是通过在委托上调用+=和-=
一个订阅者不知道和不干扰其它的订阅者。

Event事件

事件就是将上述模式正式化的一个语言特性。
事件是一种结构,为了实现广播者/订阅者模型,它只暴露了所需要的委托特性的部分子集。
事件的主要目的就是防止订阅者之间互相干扰。

声明事件

最简单的声明事件的方式就是委托前面加上event关键字

内外区别对待

未命名图片.png
加上event之后,(对于Broadcaster)只能对PriceChangedHandler进行+=和-=操作了。而对Broadcaster内部,还只是一个委托。

事件在内部是如何工作的

未命名图片.png
未命名图片.png
add和remove关键字代表着显式的事件访问器,有点像属性访问器。
然后编译器会查看Broadcaster内部对PriceChanged的引用,如果不是+=或-的操操作,那就直接把它们定向到底层的委托字段priceChanged。
第三点,编译器把作用在event上的+=和-=操作翻译成调用add或remove访问器

标准的事件模式

1.(传递信息的类有了)为编写事件, .NET定义了一个标准的模式
System.EventArgs,一个预定义的框架类,除了静态的Empty属性之外,它没有其它成员。
Eventrgs是为事件传递信息的类的基类。
未命名图片.png
通常是根据所含有信息进行命名(比如含有价格,某一属性,点击某个按钮或其它控件的信息),而不是所使用的事件。
通常通过属性或只读字段来暴露数据。

为事件选择或定义委托

返回类型必须是void;
接收两个参数,第一个参数类型是object,第二参数类型是EventArgs的子类。第一个参数表示事件的广播者(比如button),第二个参数包含需要传递的信息(比如传递的参数);
名称必须以EventHandler结尾(方便查看和区分)。

System.EventHandler
.NET Framework定义了一个泛型委托System.EventHandler,它满足上述的所有规则。

  1. public delegate void EventHandler<TEventArgs>(object sender, TEventArgs e) where TEventArgs : EventArgs;

2.(委托也有了)

针对选择的委托定义事件

很好定义,就是在委托前面加上event
未命名图片.png

可触发事件的protected virtual方法

3.(事件就应该有一个可以触发事件的方法)
方法名必须和事件一致,前面再加上On,接受一个EventArgs参数
未命名图片.png

完整的例子

未命名图片.png
未命名图片.png
未命名图片.png

注意

多线程的场景下,你需要在测试或调用前,把委托赋给一个临时变量,来避免线程安全相关的错误。

  1. var temp = PriceChanged;
  2. if(temp != null) temp(this,e);

在c#6之后,可以这样写:

  1. priceChanged?.Invoke(this,e);


非泛型的EventHandler
当事件不携带多余信息的时候(常见的Button按钮事件),可以使用非泛型的EventHandler委托。
EventArgs.Empty属性
未命名图片.png

事件访问器

事件访问器事件的+=和-=函数的实现。
public event EventHandler PriceChanged;
编译器会把它转化为:
一个私有的委托字段
一对公共的事件访问器函数(add_PriceChanged和remove_priceChanged),这两个函数的实现会把+=和-=操作交给私有的委托字段。
也可以显式的定义事件访问器

显示定义的事件访问器

  1. private EventHandler priceChanged;
  2. public event Eventandler PriceChanged
  3. {
  4. add { priceChanged += value; }
  5. remove { priceChanged -= value; }
  6. }

使用显示访问器:
当事件访问器仅仅是另一个广播事件的类的中继。
当类暴露大量event,但是大部分时候都只有少数的订阅者存在(Winfrom各种控件事件)。
显示实现一个声明了事件的接口。

事件修饰符

virtual,可以被重写;abstract,sealed,static

事件:

  • 一个传递信息(参数)的类(xxxEventArgs:EventArgs) 也可以不传递
  • 一个委托,一般使用预定义委托EventHandler,不是约定义命名规则xxxEventHandler.
  • 一个事件,使用预定义委托的直接event EventHandler。如果传递参数EventHandler
  • 一个可触发事件的方法(protected virtual修饰)
  1. protected virtual void Onxxx(xxx是事件的名称)(xxxEventArgs e)
  2. {
  3. // 触发的事件
  4. xxxEventHandlerName?.Invoke(触发的事件(xx,e))
  5. }