(七)CSharp-CSharp图解教程版-事件

news/2024/10/30 17:20:15/

一、发布者和订阅者

发布者/订阅者模式(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;}
}

实现代码:

 //自定义EventArgspublic class IncrementerEventArgs : EventArgs{public int IterationCount { get; set; }}//发送者public class Incrementer{public event EventHandler<IncrementerEventArgs> 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,}//自定义按钮含有鼠标状态的EventArgspublic class BtnEventArgs : EventArgs{public MouseState BtnClickMouseState { get; private set; }public BtnEventArgs(MouseState mouseState){BtnClickMouseState = mouseState;}}

2、发布者类:

  //发布者类class ButtonPublisher{private event EventHandler<BtnEventArgs> _tnEvent;public event  EventHandler<BtnEventArgs> BtnEvent{//若add 和 remove 访问器内不写任何代码,则添加移除事件注册无效。add{//加锁:避免在该事件实例正处理其他事情时,//同时执行该段代码,可能会产生某些问题lock (this){//在事件的注册列表里是否存在已注册的方法bool isHavedEvent = false;if (_tnEvent != null){//遍历事件的注册列表foreach (var en in _tnEvent.GetInvocationList()){var btnEventHandler = en as EventHandler<BtnEventArgs>;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<TEventArgs>(object sender, TEventArgs e);

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

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

http://www.ppmy.cn/news/318199.html

相关文章

android服务框架下载,一加移动服务框架

一加移动服务框架最新版是一款手机辅助框架软件&#xff0c;一加移动服务框架安装包无需root&#xff0c;点击下载到手机上即可使用&#xff0c;一加移动服务框架最新版各种类型的小功能也是非常方便的&#xff0c;可以满足你的需求。 软件介绍 一加移动服务框架最新版提供的新…

一加指南针问题解答

2021年5月31日 16:47分更新 1.真北和磁北的区别? 真北: 地理位置上的正北方向 磁北:磁场北极的方向 地球磁场的两级与地理位置上的两极并不重合&#xff0c;磁北会随着时间而有所变化&#xff0c;指南针指向的是磁北。 2.指南针如何正确使用? a. 确保手机硬件具备相关传感器…

一加手机,迟到的惊喜

Android出来之初&#xff0c;几乎所有的厂商都妄图打造自己特有的界面甚至所谓的深度定制&#xff0c;这种情况我很想不通。手机厂商玩了这么多年&#xff0c;把操作系统玩好的就只有苹果。可能大多数手机厂的老板都认为谷歌把系统都做好了&#xff0c;只是拿来改一改&#xff…

存量混战 一加如何打天下?

对手机厂商而言&#xff0c;2021年是逆境中求生的一年。 这一年&#xff0c;在2020年中国手机市场出货量同比减少11%&#xff08;Canalys统计数据&#xff09;的基础上&#xff0c;大部分人预期中的回升行情没有出现&#xff0c;出货量反而还在进一步下滑&#xff0c;三季度同…

一加7pro系统更新android10,一加OnePlus7T Pro官方安卓10.0稳定版出厂系统固件升级更新包...

咱们的这个一加OnePlus7T Pro手机的最新稳定版系统包也是在这里来分享一下了&#xff0c;这个稳定版本的系统包是安卓10稳定版的&#xff0c;也是第一个版本的&#xff0c;系统包大小是3.2G&#xff0c;系统方面主要是全新的UI设计&#xff0c;轻快流畅操作体验&#xff0c;更多…

一加8t可以用鸿蒙系统吗,一加8T采用什么系统-一加8T系统有哪些方面的升级

一加8T是一加本年度最后一款旗舰手机&#xff0c;那么这款手机的采用什么系统&#xff1f;系统的性能怎么样&#xff1f;小编为大家带来最新的手机系统评测&#xff0c;感兴趣的小伙伴&#xff0c;快来看看吧。 一加8T搭载什么系统&#xff1f; 一加8T为用户带来氢OS11系统&…

一加9pro 鸿蒙系统,一加9pro是什么系统_一加9pro系统介绍

现在各个厂商都开始更新自家的手机系统了&#xff0c;那么作为国产手机里面的好学生一加9pro用的是什么系统呢&#xff1f;我们一起来了解一下一加9pro的手机系统吧。 1.一加9用的是什么系统 一加9系列的两款机型&#xff0c;一加9和一加9Pro&#xff0c;都将会出厂搭载 ColorO…

一加8pro可以升级鸿蒙系统吗,去年的一加7Pro差一点登顶机皇,这一次一加8Pro怎么样?...

去年的一加7Pro2K90Hz直接让一加做到了一线旗舰的位置&#xff0c;甚至有点登顶机皇的意思&#xff0c;在今年虽然没了首发120Hz&#xff0c;但一加8Pro手机依旧是机圈的大关注&#xff0c;它的实际数据表现如何&#xff1f;我们来一起看一看。 这次发布的一加8Pro手机继续在屏…