C#沉淀-事件(发布订阅模式)

C#沉淀-事件(发布订阅模式)
文章图片
认识发布者/订阅者模式
情景:当一个特定的程序事件发生时,程序的其他部分可以得到该事件已经发生的通知
发布者定义一系列事件,并提供一个注册方法;订阅者向发布者注册,并提供一个可被回调的方法,也就是事件处理程序;当事件被触发的时候,订阅者得到通知,而订阅者所提交的所有方法都会被执行

  • 发布者是指拥有某事件的类或者结构
  • 订阅者是指向发布者注册的类或者结构
  • 事件处理程序是指由订阅者注册到事件的方法,在发布者触发事件时执行
从一个示例入手:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace CodeForEvent { //先自定义一个委托 delegate void Hander(); //这是一个发布者 class SayHello { //定义一个事件 //event关键字用来定义事件 //Hander委托类型,事件需要通过委托来调用订阅者注册的方法 public event Hander SayHelloEvent; //每循环10次,调用一次事件 public void DoCount() { for (int i = 0; i < 1000; i++) { if (i % 10 == 0 && SayHelloEvent != null) SayHelloEvent(); //调用事件 //SayHelloEvent != null事件与null的比较,用来判断事件是否被订阅者注册过 } } }//这是一个订阅者 class GetHello { public int count { get; private set; }public GetHello(SayHello sayHello) { count = 0; //向发布者订阅事件,通过+=操作符来注册一个方法 sayHello.SayHelloEvent += sayHello_SayHelloEvent; }//事件处理程序,将count累加1,记录该方法一共被调用了几次 //事件被触发时,sayHello_SayHelloEvent方法将会被调用 void sayHello_SayHelloEvent() { count++; }}class Program { static void Main(string[] args) { //实例化一个发布者类 SayHello sh = new SayHello(); //实例化一个订阅者类,在构造函数中完成了事件的注册 GetHello gh = new GetHello(sh); //激发(调用)事件 //理论上事件会被触发100次 sh.DoCount(); Console.WriteLine("Hello 的次数:{0}", gh.count); Console.ReadKey(); } } }

输出:
Hello 的次数:100

解析
  • 事件声明 使用关键字event来声明一个事件,当声明的事件为public时,称不发布了一个事件
  • 委托类型声明 事件与事件处理程序必须有共同的签名和返回类型,它们通过委托类型进行描述
  • 事件注册 订阅者通过+=操作符来注册事件,并提供一个事件处理程序
  • 事件处理程序 订阅者向事件注册的方法,它可以是显式命名的方法、匿名方法或Lambda表达式
  • 触发事件 发布者用来调用事件的代码
事件声明语法
//声明一个事件 public [static] event EventHandler EventName; //声明多个同类型的事件 public [static] event EventHandler EventName1, EventName2, EventName3;

事件它不是一个类型,它是类或结构的一个成员
所以,事件必须声明在类或结构中
事件成员被隐式初始化为null,所以在事件被触发之前可以通过和null比较来判断是否包含事件处理程序
EventHandler是BLC声明的专门用于事件的委托
【C#沉淀-事件(发布订阅模式)】事件提供了对委托的结构化访问;也就是说,无法直接访问事件中的委托
标准事件的用法
事件的标准模式根本就是System命名空间下声明的EventHanlder委托类型
public delegate void EventHandler(object sender, EventArgs e);

  • object sender用于保存触发事件的对象
  • EventArgs e用于保存状态信息
EventArgs是System下的一个类,如下:
using System.Runtime.InteropServices; namespace System { // 摘要: //System.EventArgs 是包含事件数据的类的基类。 [Serializable] [ComVisible(true)] public class EventArgs { // 摘要: //表示没有事件数据的事件。 public static readonly EventArgs Empty; // 摘要: //初始化 System.EventArgs 类的新实例。 public EventArgs(); } }

可以看出,EventArgs本身无法保存和传递数据
如果想保存和传递数据,可以实现一个EventArgs的派生类,然后定义相关的字段来保存和传递参数
示例:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace CodeForEvent { //这是一个发布者 class SayHello { //使用EventHandler定义一个事件 public event EventHandler SayHelloEvent; //每循环10次,调用一次事件 public void DoCount() { for (int i = 0; i < 1000; i++) { if (i % 10 == 0 && SayHelloEvent != null) SayHelloEvent(this, null); //调用事件 //this表示当前SayHello对象 //null表示没有任何传递信息值 } } }//这是一个订阅者 class GetHello { public int count { get; private set; }public GetHello(SayHello sayHello) { count = 0; //向发布者订阅事件,通过+=操作符来注册一个方法 sayHello.SayHelloEvent += sayHello_SayHelloEvent; }//事件处理程序的签名与返回类型与EventHandler一致 void sayHello_SayHelloEvent(object sender, EventArgs e) { count++; }} class Program { static void Main(string[] args) { //实例化一个发布者类 SayHello sh = new SayHello(); //实例化一个订阅者类,在构造函数中完成了事件的注册 GetHello gh = new GetHello(sh); //激发(调用)事件 sh.DoCount(); Console.WriteLine("Hello 的次数:{0}", gh.count); Console.ReadKey(); } } }

通过扩展EventHanlder来传递数据 System下另有泛型EventHanler类,如下:
namespace System { // 摘要: //表示将处理事件的方法。 // // 参数: //sender: //事件源。 // //e: //一个 System.EventArgs,其中包含事件数据。 // // 类型参数: //TEventArgs: //由该事件生成的事件数据的类型。 [Serializable] public delegate void EventHandler(object sender, TEventArgs e); }

因此,这里可以将派生于EventArgs的类作为类型参数传递过来,这样,即可获得派生类中保存的数据
示例:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace CodeForEvent { class InfoEventArgs : EventArgs { public string Information { get; set; } } //这是一个发布者 class SayHello { //使用EventHandler定义一个事件 public event EventHandler SayHelloEvent; //每循环10次,调用一次事件 public void DoCount() { InfoEventArgs args = new InfoEventArgs(); for (int i = 0; i < 100; i++) { if (i % 10 == 0 && SayHelloEvent != null) { //保存一个数据 args.Information = "Hello World!"; //通过agrs传递参数 SayHelloEvent(this, args); }} } }//这是一个订阅者 class GetHello { public int count { get; private set; }public GetHello(SayHello sayHello) { count = 0; //向发布者订阅事件,通过+=操作符来注册一个方法 sayHello.SayHelloEvent += sayHello_SayHelloEvent; }void sayHello_SayHelloEvent(object sender, InfoEventArgs e) { Console.WriteLine("触发对象:{0}, 保存信息:{1}", sender.ToString(), e.Information); count++; } }class Program { static void Main(string[] args) { //实例化一个发布者类 SayHello sh = new SayHello(); //实例化一个订阅者类,在构造函数中完成了事件的注册 GetHello gh = new GetHello(sh); //激发(调用)事件 sh.DoCount(); Console.WriteLine("Hello 的次数:{0}", gh.count); Console.ReadKey(); } } }

输出:
触发对象:CodeForEvent.SayHello, 保存信息:Hello World! 触发对象:CodeForEvent.SayHello, 保存信息:Hello World! 触发对象:CodeForEvent.SayHello, 保存信息:Hello World! 触发对象:CodeForEvent.SayHello, 保存信息:Hello World! 触发对象:CodeForEvent.SayHello, 保存信息:Hello World! 触发对象:CodeForEvent.SayHello, 保存信息:Hello World! 触发对象:CodeForEvent.SayHello, 保存信息:Hello World! 触发对象:CodeForEvent.SayHello, 保存信息:Hello World! 触发对象:CodeForEvent.SayHello, 保存信息:Hello World! 触发对象:CodeForEvent.SayHello, 保存信息:Hello World! Hello 的次数:10

也可以通过自定义的委托类型来传递数据,示例:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace CodeForEvent { //自定义一个委托类型 delegate void MyDel(object sender, string info); //这是一个发布者 class SayHello { //使用EventHandler定义一个事件 public event MyDel SayHelloEvent; //每循环10次,调用一次事件 public void DoCount() { for (int i = 0; i < 100; i++) { if (i % 10 == 0 && SayHelloEvent != null) { //保存一个数据 string info = "Hello World!"; //通过agrs传递参数 SayHelloEvent(this, info); }} } }//这是一个订阅者 class GetHello { public int count { get; private set; }public GetHello(SayHello sayHello) { count = 0; //向发布者订阅事件,通过+=操作符来注册一个方法 sayHello.SayHelloEvent += sayHello_SayHelloEvent; }//使用参数 void sayHello_SayHelloEvent(object sender, string info) { Console.WriteLine("触发对象:{0}, 保存信息:{1}", info, sender.ToString()); count++; }}class Program { static void Main(string[] args) { //实例化一个发布者类 SayHello sh = new SayHello(); //实例化一个订阅者类,在构造函数中完成了事件的注册 GetHello gh = new GetHello(sh); //激发(调用)事件 sh.DoCount(); Console.WriteLine("Hello 的次数:{0}", gh.count); Console.ReadKey(); } } }

其实,在调用事件的时候,调用了委托的调用列表,将参数传递给了方法,这一点要理解清楚
输出:
触发对象:CodeForEvent.SayHello, 保存信息:Hello World! 触发对象:CodeForEvent.SayHello, 保存信息:Hello World! 触发对象:CodeForEvent.SayHello, 保存信息:Hello World! 触发对象:CodeForEvent.SayHello, 保存信息:Hello World! 触发对象:CodeForEvent.SayHello, 保存信息:Hello World! 触发对象:CodeForEvent.SayHello, 保存信息:Hello World! 触发对象:CodeForEvent.SayHello, 保存信息:Hello World! 触发对象:CodeForEvent.SayHello, 保存信息:Hello World! 触发对象:CodeForEvent.SayHello, 保存信息:Hello World! 触发对象:CodeForEvent.SayHello, 保存信息:Hello World! Hello 的次数:10

移除事件处理程序 使用-=操作来移除事件处理程序
示例:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace CodeForEvent { class Publiser { public event EventHandler SimpleEvent; public void RaiseTheEvent() { SimpleEvent(this, null); } }class Subscriber { public void MethodA(object o, EventArgs e) { Console.WriteLine("A"); } public void MethodB(object o, EventArgs e) { Console.WriteLine("B"); } }class Program { static void Main(string[] args) { Publiser p = new Publiser(); Subscriber s = new Subscriber(); p.SimpleEvent += s.MethodA; p.SimpleEvent += s.MethodB; p.RaiseTheEvent(); Console.WriteLine("\n移除B事件处理程序"); p.SimpleEvent -= s.MethodB; p.RaiseTheEvent(); Console.ReadKey(); } } }

输出:
A B移除B事件处理程序 A

事件访问器
我们可以通过为事件定义事件访问器,来控制事件运算符+=、-=运算符的行为
  • 有两个访问器:add和remove
  • 声明事件的访问器看上去和声明一个属性差不多
下面示例演示了具有访问器的声明.两个访问器都有叫做value的隐式值参数,它接受实例或静态方法的引用
public event EventHandler Elapsed { add { //... 执行+=运算符的代码 }remove { //... 执行-=运算符的代码 }}

声明了事件访问器后,事件不包含任何内嵌委托对象.我们必须实现自己的机制来存储和移除事件的方法
事件访问器表现为void方法,也就是不能使用会返回值的return语句
示例:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace CodeForEvent { //声明一个delegate delegate void EventHandler(); class MyClass { //声明一个成员变量来保存事件句柄(事件被激发时被调用的delegate) private EventHandler m_Handler = null; //激发事件 public void FireAEvent() { if (m_Handler != null) { m_Handler(); } }//声明事件 public event EventHandler AEvent { //添加访问器 add { //注意,访问器中实际包含了一个名为value的隐含参数 //该参数的值即为客户程序调用+=时传递过来的delegate Console.WriteLine("AEvent add被调用,value的HashCode为:" + value.GetHashCode()); if (value != null) { //设置m_Handler域保存新的handler m_Handler = value; } }//删除访问器 remove { Console.WriteLine("AEvent remove被调用,value的HashCode为:" + value.GetHashCode()); if (value =https://www.it610.com/article/= m_Handler) { //设置m_Handler为null,该事件将不再被激发 m_Handler = null; } }}}class Program { static void Main(string[] args) { MyClass obj = new MyClass(); //创建委托 EventHandler MyHandler = new EventHandler(MyEventHandler); MyHandler += MyEventHandle2; //将委托注册到事件 obj.AEvent += MyHandler; //激发事件 obj.FireAEvent(); //将委托从事件中撤销 obj.AEvent -= MyHandler; //再次激发事件 obj.FireAEvent(); Console.ReadKey(); }//事件处理程序 static void MyEventHandler() { Console.WriteLine("This is a Event!"); }//事件处理程序 static void MyEventHandle2() { Console.WriteLine("This is a Event2!"); } } }

输出:
AEvent add被调用,value的HashCode为:2124204842 This is a Event! This is a Event2! AEvent remove被调用,value的HashCode为:2124204842

    推荐阅读