(七)CSharp

您所在的位置:网站首页 csharp与c的关系 (七)CSharp

(七)CSharp

2023-06-12 17:57| 来源: 网络整理| 查看: 265

一、发布者和订阅者

发布者/订阅者模式(publish/subscriber pattern): 很多程序都有一个共同的需求,即当一个特定的程序事件发生时,程序的其他部分可以得到该事件已经发生的通知。

发布者:

发布者类定义了一系列程序的其他部分可能感兴趣的事件。发布某个事件的类或结构,其他类可以在该事件发生时得到通知。

订阅者:

订阅者类可以“注册”,以便在这些事件发生时收到发布者的通知。这些订阅者类通过向发布者提供一个方法来“注册”以获取通知。注册并在事件发生时得到通知的类或结构。

事件:

当事件发生时,发布者“触发事件”,然后执行订阅者提交的所有事件。调用(invoke)或触发(fire)事件的术语。当事件被触发时,所有注册到它的方法都会被依次调用。

事件是一种特殊的多播委托。(术语定义来源:Microsoft 开发文档:事件) 事件是类或结构的成员。

事件处理程序:

回调方法。由订阅者提供的方法称为回调方法,因为发布者通过执行这些方法来“往回调用订阅者的方法”。它们是为处理事件而调用的代码。由订阅者注册到事件的行为,在发布者出发事件时执行。

请添加图片描述

事件包含了一个私有的委托。

请添加图片描述

有关事件的私有委托:

事件提供了对它的私有控制委托的结构化访问。也就是说,你无法直接访问委托。事件中可用的操作比委托要少,对于事件我们只可以添加、删除或者调用事件处理程序。事件被触发时,它调用委托来依次调用调用列表中的方法。

图15-3演示:

请添加图片描述

Incrementer 定义了一个 CountedADozen 事件。订阅者类 Dozens 和 SomeItherClass 各有一个注册到 CountedADozen 事件的事件处理程序。每当触发事件时,都会调用这些处理程序。 二、源代码组件概览

源代码组件:

委托类型声明: 事件和事件处理程序必须有共同的签名和返回类型,它们通过委托类型进行描述。事件处理程序声明: 订阅者类中会在事件触发时执行的方法声明。它们不一定是显式命名的方法,还可以是匿名方法或 Lambda 表达式。事件声明: 发布者类必须声明一个订阅者可以注册的事件成员。当类声明的事件为 public 时,称为发布了事件。事件注册: 订阅者必须注册事件才能在事件被触发时得到通知。这是将事件处理程序与事件相连的代码。触发事件的代码: 发布者类中的”触发“事件并导致调用注册的所有事件处理器的代码。

请添加图片描述

三、声明事件 public event EventHandler CountedADpzen; //声明多个事件 public event EventHandler MyEvent1,MyEvent2,OtherEvent; //静态事件 public static event EventHandler CountedADozen;

事件是类或结构的成员。 由于事件是成员:

我们不能在一段可执行代码中声明事件;它必须声明在类或结构中,和其他成员一样。

事件成员被隐式自动初始化为 null;

四、订阅事件

订阅者向事件添加事件处理程序。

使用 += 运算符来为事件添加事件处理程序。

事件处理程序的规范可以是以下任意一种:

实例方法的名称;静态方法的名称;匿名方法;Lambda 表达式。 ```c# class Incrementer { public event EventHandler CountedADpzen; } class ClassB { public static CounterHnadlerb(){} } class ClassC { public static CounterHnadlerC(){} } class Pargam { static void Main() { Incrementer incrementer = new Incrementer(); //添加实例方法 incrementer.CountedADozen += IncrementDzensCount; //添加静态方法 incrementer.CountedADozen += ClassB.CounterHnadlerb; ClassC cc = new ClassC(); //以委托形式添加实例方法 incrementer.CountedADozen += new EventHandler(cc.CounterHandlerC); //Lambda 表达式 int DozensCount = 0; incrementer.CountedADozen += ()=> DozensCount++; //匿名方法 incrementer.CountedADozen += delegate { DozensCount++; }; } } 五、触发事件 if(CountedADozen != null) { CountedADozen(source,args) } //CountedADozen:事件名称 //source,args:参数列表

整个程序的代码:

//1、声明委托 delegate void Handler(); //发布者 class Incrementer { //2、创建事件并发布 public event Handler CountedADozen; public void DoCount() { for(int i = 1; i < 100; i++) { if(i %12 ==0 && CountedADozen != null) { //3、每增加12个计数触发事件一次 CountedADozen(); } } } } //订阅者 class Dozens { public int DozensCount { get; private set; } public Dozens(Incrementer incrementer) { DozensCount = 0; //5、订阅事件 incrementer.CountedADozen += IncrementDozensCount; } //4、声明事件处理程序 void IncrementDozensCount() { DozensCount++; } } class Program { static void Main(string[] args) { Incrementer incrementer = new Incrementer(); Dozens dozensCounter = new Dozens(incrementer); incrementer.DoCount(); Console.WriteLine("Number of dozens = {0}", dozensCounter.DozensCount); Console.ReadKey(); } }

输出结果:

Number of dozens = 8

六、标准事件的用法

在程序需要处理事件然后继续作其他事情时,就要对程序事件进行异步处理。Windows GUI 编程广泛使用例如事件。

对事件的使用,.NET 框架提供了一个标准模式,Ssytem命名空间中声明的 EventHandler 委托类型。

EventHandler的声明:

第一个参数,用来保存触发事件的对象的引用。第二参数用来保存状态信息,指明什么类型适用于该应用程序。返回类型是 void。 public delegate void EventHandler(object sender,EventArgs e);

EventArgs 参数的作用:

EventArgs 不能传递任何数据。它用于不需要传递数据的事件处理程序——通常会被忽略。(比如EventArgs 可以传递状态:左键鼠标事件的是释放状态还是按下状态)如果你希望传递数据,必须声明一个派生自 EventArgs 的类,并使用合适的字段来保存需要传递的数据。 七、通过扩展 EventArgs 来传递数据

为了能够通过事件参数的 EventArgs 来传递数据,我们需要声明一个派生自 EventArgs 的自定义类。

public class IncrementerEventArgs : EventArgs { public int IterationCount{get;set;} }

实现代码:

//自定义EventArgs public class IncrementerEventArgs : EventArgs { public int IterationCount { get; set; } } //发送者 public class Incrementer { public event EventHandler CountedDozen; public void DoCount() { IncrementerEventArgs args = new IncrementerEventArgs(); for(int i = 1; i < 100;i++) { if(i % 12 == 0 && CountedDozen != null) { args.IterationCount = i; CountedDozen(this, args); } } } } //订阅者 class Dozens { public int DozensCount { get; private set; } public Dozens(Incrementer incrementer) { DozensCount = 0; incrementer.CountedDozen += IncrementDozensCount; } void IncrementDozensCount(object source, IncrementerEventArgs e) { Console.WriteLine($"Incremented at iteration:{ e.IterationCount } in { source.ToString() }"); DozensCount++; } } class Program { static void Main(string[] args) { Incrementer incrementer = new Incrementer(); Dozens dozensCounter = new Dozens(incrementer); incrementer.DoCount(); Console.WriteLine($"Number of dozens = { dozensCounter.DozensCount }"); Console.ReadKey(); } }

输出结果:

Incremented at iteration:12 in ConsoleApplication2.Incrementer Incremented at iteration:24 in ConsoleApplication2.Incrementer Incremented at iteration:36 in ConsoleApplication2.Incrementer Incremented at iteration:48 in ConsoleApplication2.Incrementer Incremented at iteration:60 in ConsoleApplication2.Incrementer Incremented at iteration:72 in ConsoleApplication2.Incrementer Incremented at iteration:84 in ConsoleApplication2.Incrementer Incremented at iteration:96 in ConsoleApplication2.Incrementer Number of dozens = 8

八、移除事件处理程序 incrementer.CountedDozen -= IncrementDozensCount;

如果一个处理程序向事件注册了多次,那么当执行命令移除处理程序时,将只移除列表中该处理程序的最后一个实例。(如果一个处理程序多次重复了注册事件,移除时,只移除最后一个相同的处理程序实例,而其他相同的事件处理程序仍然可被回调。)

九、事件访问器

一般情况下,事件只能许 += 和 -= 运算符。但是我们可以修改这两个运算符的行为,在使用它们时让事件执行任何我们希望执行的自定义代码。

事件访问器: 为了改变这两个运算符的操作而定义。

有两个访问器:add 和 remove。声明事件的访问器看上去和声明一个属性差不多。 public event EventHandler CountedADozen { add{...} //执行 += remove{...} //执行 -= }

声明了事件访问器之后,事件不包含任何内嵌委托对象。我们必须实现自己的机制来存储和移除事件注册的方法。(就是说在事件访问器里来编写“存储和移除事件注册的方法”的其他代码逻辑)

事件访问器表现为 void 方法,也就是不能使用返回值的 return 语句。(此处跟属性有不同的是,属性 get 是有对应类型的返回值的。而事件访问器 get 和 set 都有 value。)

书上没有提供事件访问器的代码例子,但我们也不能只学理论而不亲自动手去实现这个代码逻辑吧。

所以还是要动手实现一下事件访问器的代码例子(模仿按钮触发事件的例子):

根据已学知的识点,以下有使用到:

扩展 EventArgs:用来描述点击按钮后鼠标的行为状态,比如鼠标被按下或被释放的状态。测试代码例子之后,关于委托与事件之间的关系。

1、自定义鼠标行为状态和鼠标 EventArgs 事件参数:

//枚举鼠标的行为状态 public enum MouseState { LeftDown, LeftUp, RightDown, RightUp, } //自定义按钮含有鼠标状态的EventArgs public class BtnEventArgs : EventArgs { public MouseState BtnClickMouseState { get; private set; } public BtnEventArgs(MouseState mouseState) { BtnClickMouseState = mouseState; } }

2、发布者类:

//发布者类 class ButtonPublisher { private event EventHandler _tnEvent; public event EventHandler BtnEvent { //若add 和 remove 访问器内不写任何代码,则添加移除事件注册无效。 add { //加锁:避免在该事件实例正处理其他事情时, //同时执行该段代码,可能会产生某些问题 lock (this) { //在事件的注册列表里是否存在已注册的方法 bool isHavedEvent = false; if (_tnEvent != null) { //遍历事件的注册列表 foreach (var en in _tnEvent.GetInvocationList()) { var btnEventHandler = en as EventHandler; if (btnEventHandler == null || btnEventHandler.Method == null) continue; var method = btnEventHandler.Method; //对比事件处理程序是否相同 if (method == value.Method) { isHavedEvent = true; break; } } } //若还没有注册,就注册;否则,不执行重复注册 if (isHavedEvent == false) { _tnEvent += value; } } } remove { lock (this) { _tnEvent -= value;//移除事件注册 } } } public void RaiseBtnEvent(MouseState mouseState) { BtnEventArgs args = new BtnEventArgs(mouseState); if(_tnEvent != null) _tnEvent(this, args); } }

3、订阅者类

//订阅者类 class Subscriber { public void MethodMouse(object o, BtnEventArgs e) { string str = Enum.GetName(typeof(MouseState), e.BtnClickMouseState); Console.WriteLine("{0}", str); } }

4、测试代码:

class Program { static void Main(string[] args) { ButtonPublisher p = new ButtonPublisher(); Subscriber s = new Subscriber(); //注册了一次 p.BtnEvent += s.MethodMouse; //由于add 访问器里有做了判断:相同的事件处理程序不能再被关联一遍 p.BtnEvent += s.MethodMouse; Console.WriteLine("注册BtnEvent事件后,准备触发该事件:"); //触发事件 p.RaiseBtnEvent(MouseState.LeftUp); p.RaiseBtnEvent(MouseState.LeftDown); p.BtnEvent -= s.MethodMouse; Console.WriteLine("移除事件注册后,没法触发该事件"); //因移除了该事件的注册,无法触发该事件 p.RaiseBtnEvent(MouseState.LeftUp); p.RaiseBtnEvent(MouseState.LeftDown); Console.ReadKey(); } }

输出结果:

注册事件后,触发事件: LeftUp LeftDown 移除事件注册后,不触发事件

加强理解委托和事件之间的关系

记住以下几点: 事件是类或结构提供具有通知能力的成员。

事件是一种特殊的多播委托。

事件包含了一个私有的委托。

事件成员被隐式自动初始化为 null;

委托是一个类,它封装了一个调用列表。使用到事件是因为要把委托包装起来,为了避免委托的滥用,使得保障使用委托的安全性。同时,事件还起到因隐藏对委托字段的访问限制作用,仅仅提供添加和移除事件处理程序的功能。于是事件作为发送者的成员,发送者调用它就不会对委托里所有功能都能操作,因为委托在事件里是私有的。

关于扩展 EventArgs,实际上是根据事件里私有委托已经设计好的泛型来实现的:

//在源代码中,有一个泛型委托,TEventArgs 是一个泛型参数 public delegate void EventHandler(object sender, TEventArgs e);

所以如果自定义一个委托,这时仅仅是一个委托,而不是事件,但也可以输出同样的结果。

//自定义一个委托 public delegate void CustomEventHandler(Object obj, BtnEventArgs e); //把以上代码例子中的所有EventHandler替换为: CustomEventHandler //执行的功能和输出的结果一样。


【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3