C#中的Event(事件)与发布/订阅设计模式详解
很多程序都有这样的一个需求,当一个特定的事件发生时,程序的其他部分能够得到通知,并且需要做一些事情。这个时候就需要事件了。
发布者/订阅者模式
发布者/订阅者模式(publisher/subscriber pattern)就是满足这种需求,设计模式中也叫观察者模式。发布者存储一个方法集合,并且提供一个注册方法,让订阅者把自己的方法注册进去,这样在事件发生的时候,发布者可以调用注册到存储集合中的所有方法。
有以下要点:
- 发布者(publisher)
- 订阅者(subscriber)
- 事件处理程序(event handler)订阅者注册到发布者的方法
- 触发(raise)事件 当事件被调用(invoke)或触发(fire)时,所有注册的事件处理程序会被依次调用
使用步骤
- 声明委托类型
- 声明事件
- 事件处理程序声明
- 订阅事件/注册事件
- 触发事件(调用事件)
代码示例:
using EventDemo;
void Main()
{
Publisher pub = new Publisher(); //发布者对象
Subscriber sub = new Subscriber(); //订阅者对象
pub.BtnPressEvent += sub.ProcBtnPress; //4、订阅事件(或者说给事件添加处理程序)
pub.ButtonPress(); //5、触发事件
}
namespace EventDemo
{
//1、声明委托类型
delegate void DelButton(object sender, int id);
//发布者类型
class Publisher
{
//2、声明一个事件成员变量
public event DelButton BtnPressEvent;
//事件触发方法
public void ButtonPress()
{
Button button1 = new Button();
if (BtnPressEvent != null)
{
BtnPressEvent(button1, 10);
}
}
}
//订阅者类型
class Subscriber
{
public void ProcBtnPress(object sender, int id)
{
Console.WriteLine($"Id of button is {id}");
}
}
}//namespace
1、声明委托类型
//1、声明委托类型
delegate void DelButton(object sender, int id);
事件的声明需要依赖此委托类型,并且事件处理程序的格式要与此委托保持一致。事件与委托很像,它包含了一个私有的内部委托。
- 事件的委托是私有的,无法被直接访问
- 事件提供的操作比委托少,一般只有添加、删除和调用事件处理程序
- 事件触发时,它通过调用内部的委托,来依次调用存放在委托方法列表中的事件处理程序
2、声明事件变量
语法格式:
public event 委托类型 事件名称;
//2、声明一个事件成员变量,并且被隐式自动初始化为null
public event DelButton BtnPressEvent;
public static event DelButton BtnPressEvent; //声明为静态成员,局部于类
public event DelButton Event1, Event2, Event3; //同时声明多个事件
注意,事件是一个成员变量,而不是类型,它可以声明在类和结构中。这里使用public修饰后,外部的程序才可以向该事件注册处理程序。
BCL中有一个EventHandler的委托,专门用于系统事件。
3、事件处理程序
在订阅者类型中,定义了事件处理程序,该方法的格式必须保持和委托类型一致,要有相同的返回值和方法签名。该事件处理程序用于在事件触发时,被事件调用。最典型的就是响应winform或wpf中控件的事件,譬如像按钮按下、下拉框选择更改、文本框内容修改的事件等等。
//订阅者类型
class Subscriber
{
//3、事件处理程序(按钮按下处理)
public void ProcBtnPress(object sender, int id)
{
Console.WriteLine($"Id of button is {id}");
}
}
4、订阅事件
有了事件处理程序后,就可以把我们添加的方法注册到事件上了,或者说订阅该事件。在示例中,我们把自定义的事件处理方法ProcBtnPress通过+=的方式,添加到事件中去,这和委托增加方法的方式一致。
这样一来,可以把多个订阅者对象的自定义方法添加到事件中,又叫做多个订阅者订阅了同一个发布者的事件。
void Main()
{
Publisher pub = new Publisher(); //发布者对象
Subscriber sub = new Subscriber(); //订阅者对象
pub.BtnPressEvent += sub.ProcBtnPress; //4、订阅事件(或者说给事件添加处理程序)
pub.ButtonPress(); //5、触发事件
}
5、事件触发
在示例中,我们在发布者类型中,写了一个事件触发的方法。事件触发其实就是在某个需要的地方,调用该事件,内部会依次执行委托中的方法。
//发布者类型
class Publisher
{
//2、声明一个事件成员变量
public event DelButton BtnPressEvent;
//3、事件触发方法
public void ButtonPress()
{
Button button1 = new Button();
if (BtnPressEvent != null)
{
BtnPressEvent(button1, 10);
}
}
}
然后在外部调用事件触发方法。
void Main()
{
Publisher pub = new Publisher(); //发布者对象
Subscriber sub = new Subscriber(); //订阅者对象
pub.BtnPressEvent += sub.ProcBtnPress; //4、订阅事件(或者说给事件添加处理程序)
pub.ButtonPress(); //5、触发事件
}
标准事件的使用
上面说到BCL中有一个EventHandler的委托,专门用于系统事件,它是.Net框架提供的标准模式。
//sender: 发起事件的对象的引用,类型是object可以兼容所有类型,但是使用的时候需要把object转成对应类型
//s: 触发事件传进来的参数基类引用
public delegate void EvnetHandler(object sender, EventArgs e);
第二个参数要注意, 类型EventArgs是参数基类类型, 并不能直接使用, 要传递参数必须要派生一个参数类继承自EventArgs用来传递参数。
EventArgs类的定义:
// System.EventArgs
using System;
using System.Runtime.CompilerServices;
[Serializable]
[TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
public class EventArgs
{
public static readonly EventArgs Empty = new EventArgs();
}
下面我们使用标准事件委托EventHandler和自定义参数类,来实现事件的使用
using EventDemo;
void Main()
{
CustomButton btn = new CustomButton(); //发布者对象
Subscriber sub = new Subscriber(); //订阅者对象
btn.BtnPressEvent += sub.ProcBtnPress; //订阅事件
btn.Click(); //事件触发
}
namespace EventDemo
{
//发布者类, 这里写了一个简单的自定义按钮类作为发布者
class CustomButton
{
//2、这里我们使用系统的标准事件委托来声明事件变量
//这里的格式是使用了泛型委托,将默认的EventArgs参数类替换成我们自定义的
public event EventHandler<CustomButtonEventArgs> BtnPressEvent;
//自定义参数变量
private CustomButtonEventArgs buttonEventArgs = new CustomButtonEventArgs();
//3、事件触发方法
public void Click()
{
buttonEventArgs.Id = 10;
buttonEventArgs.Value = 255;
if (BtnPressEvent != null)
{
BtnPressEvent(this, buttonEventArgs);
}
}
}
//订阅者类
class Subscriber
{
//4、自定义事件处理程序(按钮按下处理)
//注意,这里的参数要和标准委托EventHandler保持一致
public void ProcBtnPress(object sender, EventArgs e)
{
Console.WriteLine($"The publisher Type is {sender.GetType().Name}");
CustomButtonEventArgs ea = e as CustomButtonEventArgs;
Console.WriteLine($"The eventArgs is {ea.Id} and {ea.Value}");
}
}
//1、自定义按钮的参数类
class CustomButtonEventArgs : EventArgs
{
//这里的参数可以存放一些外部的自定义数据
public int Id { get; set; }
public int Value { get; set; }
}
}//namespace
推荐阅读
-
异步编程RxJava-介绍-前言 前段时间写了一篇对协程的一些理解,里面提到了不管是协程还是callback,本质上其实提供的是一种异步无阻塞的编程模式;并且介绍了java中对异步无阻赛这种编程模式的支持,主要提到了Future和CompletableFuture;之后有同学在下面留言提到了RxJava,刚好最近在看微服务设计这本书,里面提到了响应式扩展(Reactive extensions,Rx),而RxJava是Rx在JVM上的实现,所有打算对RxJava进一步了解。 RxJava简介 RxJava的官网地址:https://github.com/ReactiveX/RxJava, 其中对RxJava进行了一句话描述:RxJava – Reactive Extensions for the JVM – a library for composing asynchronous and event-based programs using observable sequences for the Java VM. 大意就是:一个在Java VM上使用可观测的序列来组成异步的、基于事件的程序的库。 更详细的说明在Netflix技术博客的一篇文章中描述了RxJava的主要特点: 1.易于并发从而更好的利用服务器的能力。 2.易于有条件的异步执行。 3.一种更好的方式来避免回调地狱。 4.一种响应式方法。 与CompletableFuture对比 之前提到CompletableFuture真正的实现了异步的编程模式,一个比较常见的使用场景: CompletableFuture<Integer> future = CompletableFuture.supplyAsync(耗时函数); Future<Integer> f = future.whenComplete((v, e) -> { System.out.println(v); System.out.println(e); }); System.out.println("other..."); 下面用一个简单的例子来看一下RxJava是如何实现异步的编程模式: Observable<Long> observable = Observable.just(1, 2) .subscribeOn(Schedulers.io).map(new Func1<Integer, Long> { @Override public Long call(Integer t) { try { Thread.sleep(1000); //耗时的操作 } catch (InterruptedException e) { e.printStackTrace; } return (long) (t * 2); } }); observable.subscribe(new Subscriber<Long> { @Override public void onCompleted { System.out.println("onCompleted"); } @Override public void onError(Throwable e) { System.out.println("error" + e); } @Override public void onNext(Long result) { System.out.println("result = " + result); } }); System.out.println("other..."); Func1中以异步的方式执行了一个耗时的操作,Subscriber(观察者)被订阅到Observable(被观察者)中,当耗时操作执行完会回调Subscriber中的onNext方法。 其中的异步方式是在subscribeOn(Schedulers.io)中指定的,Schedulers.io可以理解为每次执行耗时操作都启动一个新的线程。 结构上其实和CompletableFuture很像,都是异步的执行一个耗时的操作,然后在有结果的时候主动告诉我结果。那我们还需要RxJava干嘛,不知道你有没有注意,上面的例子中其实提供2条数据流[1,2],并且处理完任何一个都会主动告诉我,当然这只是它其中的一项功能,RxJava还有很多好用的功能,在下面的内容会进行介绍。 异步观察者模式 上面这段代码有没有发现特别像设计模式中的:观察者模式;首先提供一个被观察者Observable,然后把观察者Subscriber添加到了被观察者列表中; RxJava中一共提供了四种角色:Observable、Observer、Subscriber、Subjects Observables和Subjects是两个被观察者,Observers和Subscribers是观察者; 当然我们也可以查看一下源码,看一下jdk中的Observer和RxJava的Observer jdk中的Observer: public interface Observer { void update(Observable o, Object arg); } RxJava的Observer: public interface Observer<T> { void onCompleted; void onError(Throwable e); void onNext(T t); } 同时可以发现Subscriber是implements Observer的: public abstract class Subscriber<T> implements Observer<T>, Subscription 可以发现RxJava中在Observer中引入了2个新的方法:onCompleted和onError onCompleted:即通知观察者Observable没有更多的数据,事件队列完结 onError:在事件处理过程中出异常时,onError会被触发,同时队列自动终止,不允许再有事件发出。 正是因为RxJava提供了同步和异步两种方式进行事件的处理,个人觉得异步的方式更能体现RxJava的价值,所以这里给他命名为异步观察者模式。 好了,下面正式介绍RxJava的那些灵活的操作符,这里仅仅是简单的介绍和简单的实例,具体用在什么场景下,会在以后的文章中介绍 Maven引入
-
C#中的Event(事件)与发布/订阅设计模式详解