C#教程 - 事件类型(Event Type)
2022/9/17 14:16:15
本文主要是介绍C#教程 - 事件类型(Event Type),对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
更新记录
转载请注明出处:https://www.cnblogs.com/cqpanda/p/16690975.html
2022年9月17日 发布。
2022年9月10日 从笔记迁移到博客。
发布者和订阅者模式
发布者和订阅者(publisher/subscriber pattern)
即:当一个特定的程序事件发生时,程序的其他部分可以得到该事件已经发生的通知
发布者类定义一系列程序其他部分可能感兴趣的事件,其他类可以注册这些事件
当发布者触发事件后,其他类可以得到通知并执行代码
Event Model(事件模型)
事件发生到响应中的5个动作
(1)我有一个事件
(2)一个人或者一群人关心我的这个事件
(3)我的这个事件发生了
(4)关心这个事件的人会被依次通知到
(5)被通知到的人根据事件信息(事件数据、事件参数)对事件进行响应(又称处理事件)。
发布者(publisher):发布某个事件的类或结构
订阅者(subscriber):注册并在事件发生时得到通知的类或结构
事件处理程序(event handler):订阅者注册到事件的方法,在发布者触发事件时执行
触发事件(raise event):当事件触发时,所有注册到它的方法都会被依次执行
订阅者提供的方法也叫回调方法或事件处理程序
事件不是一种单独的类型,而是类或结构的一个成员,默认初始化为null
事件可以是static的
实例:发布订阅者模式
using System; namespace ConsoleApp2 { /// <summary> /// 事件发布者 /// </summary> public class Sender { /// <summary> /// 事件定义 /// </summary> public event Func<int, int, string> ValueChange; /// <summary> /// 触发事件 /// </summary> public void ActiveValueChange(int i, int j) { if(this.ValueChange != null) { this.ValueChange(i,j); } } } /// <summary> /// 事件订阅者1 /// </summary> public class Geter1 { public string DoSomething(int i, int j) { Console.WriteLine($"Geter1-DoSomething-{i} - {j}"); return (i + j).ToString(); } } /// <summary> /// 事件订阅者2 /// </summary> public class Geter2 { public string DoSomething(int i, int j) { Console.WriteLine($"Geter2-DoSomething-{i} - {j}"); return (i + j).ToString(); } } class Program { static void Main(string[] args) { //实例化对象 Sender sender = new Sender(); Geter1 geter1 = new Geter1(); Geter2 geter2 = new Geter2(); //绑定监听 sender.ValueChange += geter1.DoSomething; sender.ValueChange += geter2.DoSomething; //触发事件 sender.ActiveValueChange(1, 3); //wait Console.ReadKey(); } } }
事件与委托对比
事件的功能是委托类型的子集(subset)
事件很多部分与委托类型相似,实际上事件就是专门用于特殊用途的简单委托
事件提供了对它内部委托的访问接口,但开发者无法访问事件内部的委托
事件中可用的操作比委托要少,只能添加、删除、调用事件处理程序
事件被触发时,调用委托来依次调用其调用列表中的方法
为什么需要事件
事件是委托的封装(语法糖)
使委托更安全
调试更加简单
易于维护
减少代码量
事件限制
事件订阅和取消只能使用+=和-=
只能在类内部触发
事件代码结构
委托类型声明:事件类型的签名,事件和事件处理程序必须由共同的签名和返回类型
事件处理程序声明:订阅者类中在事件触发时执行的方法声明
事件声明:发布者类必须声明一个订阅者类可以注册事件成员,当声明的事件为public时,称为发布了事件
事件注册:订阅者必须订阅事件才能在事件触发时得到通知
触发事件的代码:发布者类中触发事件并导致调用注册的所有事件处理程序的代码
声明事件
发布者类必须提供事件对象,事件是发布者类的成员
注意:
事件声明在一个类/结构中,是一个类/结构成员,不可以在其他地方声明事件
需要一个委托类型描述事件的签名
将事件声明为public,使在发布者类外可以访问
不能使用对象创建表达式来创建对象
事件被隐式自动初始化为null
BCL中预定义了一个叫做EventHandler的委托,专门用于系统事件
代码:
一次性声明多个同类型的事件
静态事件
.NET标准发布者内事件成员声明:
public event EvenHandler<EventArgs> 事件名;
.NET标准事件订阅者内事件处理函数:
public void MyDeal(object sender, EventArgs e){}
订阅事件
订阅者向事件添加事件处理程序,对于一个要添加到事件的事件处理程序来说,必须与事件委托相同签名和返回值
使用+=为事件添加事件处理程序
使用-=为事件移除事件处理程序
事件处理程序可以是以下一种:
实例方法名称
静态方法名称
匿名方法
Lambda表达式
添加事件处理程序:
移除事件处理程序:
触发事件
在触发事件之前记得和null进行比较,从而查看事件是否包含事件处理程序,如果事件是null,则表示没有内容,不可以执行
触发事件的语法和调用方法一样:
.NET标准事件模式(Standard Event Pattern)
System.EventHandler委托类型
.NET中自带标准事件,System.EventHandler委托类型
该委托类型返回void类型
该委托类型的第一个参数是object类型,用于保存触发事件的对象的引用(event broadcaster)
该委托类型的第二个参数是System.EventArgs类型,保存事件信息,一般派生该类作为传递数据使用
该委托的返回类型是void
EventHandler定义在System命名空间下
public delegate void EventHandler(object sender, EventArgs e);
其泛型实现为:
public delegate void EventHandler<TEventArgs>(object source, TEventArgs e) where TEventArgs : EventArgs;
但为了兼容.NET 2.0以前的用户,一般写作:
public delegate void PriceChangedHandler(object sender, PriceChangedEventArgs e);
实例:使用EventHandler<>()泛型委托
public class Stock { public event EventHandler<PriceChangedEventArgs> PriceChanged; }
Microsoft事件处理方法名约定
对象名_事件名
定义事件
实例:定义事件
public class PriceChangedEventArgs : System.EventArgs { public readonly decimal LastPrice; //注意是readonly的 public readonly decimal NewPrice; public PriceChangedEventArgs (decimal lastPrice, decimal newPrice) { LastPrice = lastPrice; NewPrice = newPrice; } }
传递参数
通过继承System.EventArgs生成自己的参数类,通过该类的实例传递数据
把自定义的参数类放入到System.EventHandler<自定义参数类>来生成事件
实例:
注意:一般继承自EventArg的自定义参数对象的成员声明为readonly类型
实例:
public class PriceChangedEventArgs : System.EventArgs { public readonly decimal LastPrice; public readonly decimal NewPrice; public PriceChangedEventArgs (decimal lastPrice, decimal newPrice) { LastPrice = lastPrice; NewPrice = newPrice; } }
实例1
实例2:检测CPU价格变动
using System; using System.Drawing; namespace PandaTest { public class PriceChangeEventArgs: EventArgs { public decimal NowPrice { get; set; } public decimal BeforePrice { get; set; } public PriceChangeEventArgs(decimal nowPrice, decimal beforePrice) { this.NowPrice = nowPrice; this.BeforePrice = beforePrice; } } public class CPU { public event EventHandler<PriceChangeEventArgs> OnPriceChange; public void AcivePriceChange() { this.OnPriceChange?.Invoke(this, new PriceChangeEventArgs(666.66M, 888.88M)); } } public class Person { public void CpuPriceChange(object sender,PriceChangeEventArgs priceChangeEventArgs) { Console.WriteLine($"Before Price {priceChangeEventArgs.BeforePrice}"); Console.WriteLine($"Now Price {priceChangeEventArgs.NowPrice}"); } } class Program { static void Main() { CPU cPU = new CPU(); Person Panda = new Person(); cPU.OnPriceChange += Panda.CpuPriceChange; cPU.AcivePriceChange(); //wait Console.ReadKey(); } } }
实例:.NET标准事件
using System; public class PriceChangedEventArgs : EventArgs { public readonly decimal LastPrice; public readonly decimal NewPrice; public PriceChangedEventArgs (decimal lastPrice, decimal newPrice) { LastPrice = lastPrice; NewPrice = newPrice; } } public class Stock { string symbol; decimal price; public Stock (string symbol) => this.symbol = symbol; public event EventHandler<PriceChangedEventArgs> PriceChanged; protected virtual void OnPriceChanged (PriceChangedEventArgs e) { PriceChanged?.Invoke (this, e); } public decimal Price { get => price; set { if (price == value) return; decimal oldPrice = price; price = value; OnPriceChanged (new PriceChangedEventArgs (oldPrice, price)); } } } class Test { static void Main() { Stock stock = new Stock ("THPW"); stock.Price = 27.10M; // Register with the PriceChanged event stock.PriceChanged += stock_PriceChanged; stock.Price = 31.59M; } static void stock_PriceChanged (object sender, PriceChangedEventArgs e) { if ((e.NewPrice - e.LastPrice) / e.LastPrice > 0.1M) { Console.WriteLine ("Alert, 10% stock price increase!"); } } }
事件访问器
事件预定义情况下只能 添加、移除事件处理程序 和 执行事件
为了改变事件的预定义行为,可以使用add和remove访问器
注意:
改变预定义行为后,事件不再包含内部的委托对象,需要自己创建和维护委托对象
实例:
//声明委托类型,用于事件内部的委托类型 private EventHandler innerDelegate; //声明事件变量 public event EventHandler MyEvent { add { if (innerDelegate != null) { innerDelegate += value; } else { innerDelegate = value; } } remove { if(innerDelegate != null) { innerDelegate -= value; } } }
事件本质
是一个简化版的委托
编译器将事件转换为:
事件内部封装维护一个私有委托(A private delegate field)
生成的CIL中本质是add_事件名和remove_事件名两个方法,方法中维护私有委托
A public pair of event accessor functions (add_PriceChanged and remove_PriceChanged) whose implementations forward the += and -= operations to the private delegate field
比如:
public class Broadcaster { public event PriceChangedHandler PriceChanged; }
编译器会将PriceChanged事件成员转为:
PriceChangedHandler priceChanged; // private delegate public event PriceChangedHandler PriceChanged { add { priceChanged += value; } remove { priceChanged -= value; } }
也可以自己手动实现私有委托和存取器
实例:
private EventHandler priceChanged; // Declare a private delegate public event EventHandler PriceChanged { add { priceChanged += value; } remove { priceChanged -= value; } }
自己实现事件存取器的使用场景:
1、 当类有很多公开事件时,大多数情况下只有很少的订阅,例如Windows控件。在这种情况下,最好将订阅者的委托实例存储在字典中,因为字典所包含的存储开销比几十个空委托字段引用要少
2、 当事件访问器仅仅是广播事件的另一个类的中继时
3、 显式实现声明事件的接口时
显式实现接口中定义的事件
public interface IFoo { event EventHandler Ev; } class Foo : IFoo { private EventHandler ev; event EventHandler IFoo.Ev { add { ev += value; } remove { ev -= value; } } }
事件访问器(Event Accessors)
An event’s accessors are the implementations of its += and -= functions
By default,accessors are implemented implicitly by the compiler
比如:
public event EventHandler PriceChanged;
编译器会将其转换为
A private delegate field A public pair of event accessor functions (add_PriceChanged and remove_PriceChanged)
whose implementations forward the += and -= operations to the private delegate field
也可以手动实现事件访问器
实例:
private EventHandler priceChanged; // Declare a private delegate public event EventHandler PriceChanged { add { priceChanged += value; } remove { priceChanged -= value; } }
事件访问器适合场景:
When the event accessors are merely relays for another class that is broadcasting the event
When the class exposes many events, for which most of the time very few sub‐scribers exist, such as a Windows control
In such cases, it is better to store the subscriber’s delegate instances in a dictionary because a dictionary will contain less storage overhead than dozens of null delegate field references
When explicitly implementing an interface that declares an event
实例:自定义事件访问器
public event EventHandler<TemperatureArgs>OnTemperatureChange { add { _OnTemperatureChange = (TemperatureChangeHandler)System.Delegate.Combine(value,_OnTemperatureChange); } remove { _OnTemperatureChange = (TemperatureChangeHandler?)System.Delegate.Remove(_OnTemperatureChange, value); } }
事件修饰符(Event Modifiers)
events can be virtual, overridden, abstract, or sealed
实例:
public class Foo { public static event EventHandler<EventArgs> StaticEvent; public virtual event EventHandler<EventArgs> VirtualEvent; }
事件实例
自己发布事件自己处理实例
using System; using System.Threading; namespace PandaNamespace { class EventTestClass { public delegate void TestDelegate(object sender, EventArgs args); public event TestDelegate TestEvent; //激活事件 public void RiseEvnet() { for (int i = 0; i < 10; i++) { this.TestEvent(this, null); Thread.Sleep(1000); } } //处理事件 public void DoSomeThing(object sender, EventArgs args) { Console.WriteLine("I'm Doing Now"); } } class PandaClass { static void Main(string[] args) { EventTestClass testObj = new EventTestClass(); //绑定事件处理 testObj.TestEvent += testObj.DoSomeThing; //执行事件 testObj.RiseEvnet(); Console.ReadKey(); } } }
一个类发布事件多个类接收事件实例
using System; using System.Threading; namespace PandaNamespace { class SenderClass { public delegate void delegateName(object sender, EventArgs args); //定义事件 public event delegateName UpdateMessage; //执行事件 public void UpdateNow() { for (int i = 0; i < 10; i++) { this.UpdateMessage(this, null); Thread.Sleep(1000); } } } class ReciveClass1 { public void ReciveMessage(object sender, EventArgs args) { Console.WriteLine("ReciveClass1 Recive Message Successfully!"); } } class ReciveClass2 { public void ReciveMessage(object sender, EventArgs args) { Console.WriteLine("ReciveClass2 Recive Message Successfully!"); } } class PandaClass { static void Main(string[] args) { SenderClass senderObj = new SenderClass(); ReciveClass1 reciveObj1 = new ReciveClass1(); ReciveClass2 reciveObj2 = new ReciveClass2(); //绑定事件处理 senderObj.UpdateMessage += reciveObj1.ReciveMessage; senderObj.UpdateMessage += reciveObj2.ReciveMessage; //触发事件 senderObj.UpdateNow(); Console.ReadKey(); } } }
事件传送数据实例
using System; using System.Threading; namespace PandaNamespace { //定义事件参数 class PandaEvnetArgs : EventArgs { public string arg1 { get; set; } public int arg2 { get; set; } public PandaEvnetArgs(string arg1, int arg2) { this.arg1 = arg1; this.arg2 = arg2; } } class SenderClass { public delegate void delegateName(object sender, EventArgs args); //定义事件 public event delegateName UpdateMessage; //执行事件 public void UpdateNow(string arg1, int arg2) { for (int i = 0; i < 10; i++) { this.UpdateMessage(this, new PandaEvnetArgs(arg1, arg2)); Thread.Sleep(1000); } } } class ReciveClass1 { public void ReciveMessage(object sender, EventArgs args) { Console.WriteLine($"ReciveClass1 Recived Message Successfully!"); Console.WriteLine($"{((PandaEvnetArgs)args).arg1}"); Console.WriteLine($"{((PandaEvnetArgs)args).arg2}"); } } class ReciveClass2 { public void ReciveMessage(object sender, EventArgs args) { Console.WriteLine("ReciveClass2 Recive Message Successfully!"); Console.WriteLine($"{((PandaEvnetArgs)args).arg1}"); Console.WriteLine($"{((PandaEvnetArgs)args).arg2}"); } } class PandaClass { static void Main(string[] args) { SenderClass senderObj = new SenderClass(); ReciveClass1 reciveObj1 = new ReciveClass1(); ReciveClass2 reciveObj2 = new ReciveClass2(); //绑定事件处理 senderObj.UpdateMessage += reciveObj1.ReciveMessage; senderObj.UpdateMessage += reciveObj2.ReciveMessage; //触发事件 senderObj.UpdateNow("Panda",666); Console.ReadKey(); } } }
这篇关于C#教程 - 事件类型(Event Type)的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2022-03-01沐雪多租宝商城源码从.NetCore3.1升级到.Net6的步骤
- 2024-12-06使用Microsoft.Extensions.AI在.NET中生成嵌入向量
- 2024-11-18微软研究:RAG系统的四个层次提升理解与回答能力
- 2024-11-15C#中怎么从PEM格式的证书中提取公钥?-icode9专业技术文章分享
- 2024-11-14云架构设计——如何用diagrams.net绘制专业的AWS架构图?
- 2024-05-08首个适配Visual Studio平台的国产智能编程助手CodeGeeX正式上线!C#程序员必备效率神器!
- 2024-03-30C#设计模式之十六迭代器模式(Iterator Pattern)【行为型】
- 2024-03-29c# datetime tryparse
- 2024-02-21list find index c#
- 2024-01-24convert toint32 c#