很多程序都有这样的一个需求,当一个特定的事件发生时,程序的其他部分能够得到通知,并且需要做一些事情。这个时候就需要事件了。
发布者/订阅者模式(publisher/subscriber pattern)就是满足这种需求,设计模式中也叫观察者模式。发布者存储一个方法集合,并且提供一个注册方法,让订阅者把自己的方法注册进去,这样在事件发生的时候,发布者可以调用注册到存储集合中的所有方法。
有以下要点:
代码示例:
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