C# 事件(Event)
C# 事件(Event)是一种成员,用于将特定的事件通知发送给订阅者。事件通常用于实现观察者模式,它允许一个对象将状态的变化通知其他对象,而不需要知道这些对象的细节。
事件(Event) 基本上说是一个用户操作,如按键、点击、鼠标移动等等,或者是一些提示信息,如系统生成的通知。应用程序需要在事件发生时响应事件。例如,中断。
C# 中使用事件机制实现线程间的通信。
关键点:
- 声明委托:定义事件将使用的委托类型。委托是一个函数签名。
- 声明事件:使用
event关键字声明一个事件。 - 触发事件:在适当的时候调用事件,通知所有订阅者。
- 订阅和取消订阅事件:其他类可以通过
+=和-=运算符订阅和取消订阅事件。
通过事件使用委托
事件在类中声明且生成,且通过使用同一个类或其他类中的委托与事件处理程序关联。包含事件的类用于发布事件。这被称为 发布器(publisher) 类。其他接受该事件的类被称为 订阅器(subscriber) 类。事件使用 发布-订阅(publisher-subscriber) 模型。
发布器(publisher) 是一个包含事件和委托定义的对象。事件和委托之间的联系也定义在这个对象中。发布器(publisher)类的对象调用这个事件,并通知其他的对象。
订阅器(subscriber) 是一个接受事件并提供事件处理程序的对象。在发布器(publisher)类中的委托调用订阅器(subscriber)类中的方法(事件处理程序)。
声明事件(Event)
在类的内部声明事件,首先必须声明该事件的委托类型。例如:
public delegate void BoilerLogHandler(string status);
然后,声明事件本身,使用 event 关键字:
// 基于上面的委托定义事件 public event BoilerLogHandler BoilerEventLog;
上面的代码定义了一个名为 BoilerLogHandler 的委托和一个名为 BoilerEventLog 的事件,该事件在生成的时候会调用委托。
以下示例展示了如何在 C# 中使用事件:
实例
namespace EventDemo
{
// 定义一个委托类型,用于事件处理程序
public delegate void NotifyEventHandler(object sender, EventArgs e);
// 发布者类
public class ProcessBusinessLogic
{
// 声明事件
public event NotifyEventHandler ProcessCompleted;
// 触发事件的方法
protected virtual void OnProcessCompleted(EventArgs e)
{
ProcessCompleted?.Invoke(this, e);
}
// 模拟业务逻辑过程并触发事件
public void StartProcess()
{
Console.WriteLine("Process Started!");
// 这里可以加入实际的业务逻辑
// 业务逻辑完成,触发事件
OnProcessCompleted(EventArgs.Empty);
}
}
// 订阅者类
public class EventSubscriber
{
public void Subscribe(ProcessBusinessLogic process)
{
process.ProcessCompleted += Process_ProcessCompleted;
}
private void Process_ProcessCompleted(object sender, EventArgs e)
{
Console.WriteLine("Process Completed!");
}
}
class Program
{
static void Main(string[] args)
{
ProcessBusinessLogic process = new ProcessBusinessLogic();
EventSubscriber subscriber = new EventSubscriber();
// 订阅事件
subscriber.Subscribe(process);
// 启动过程
process.StartProcess();
Console.ReadLine();
}
}
}
说明
1、定义委托类型:
public delegate void NotifyEventHandler(object sender, EventArgs e);
这是一个委托类型,它定义了事件处理程序的签名。通常使用 EventHandler 或 EventHandler<TEventArgs> 来替代自定义的委托。
2、声明事件:
public event NotifyEventHandler ProcessCompleted;
这是一个使用 NotifyEventHandler 委托类型的事件。
3、触发事件:
protected virtual void OnProcessCompleted(EventArgs e)
{
ProcessCompleted?.Invoke(this, e);
}这是一个受保护的方法,用于触发事件。使用 ?.Invoke 语法来确保只有在有订阅者时才调用事件。
4、订阅和取消订阅事件:
process.ProcessCompleted += Process_ProcessCompleted;
订阅者使用 += 运算符订阅事件,并定义事件处理程序 Process_ProcessCompleted。
实例
实例 1
namespace SimpleEvent
{
using System;
/***********发布器类***********/
public class EventTest
{
private int value;
public delegate void NumManipulationHandler();
public event NumManipulationHandler ChangeNum;
protected virtual void OnNumChanged()
{
if ( ChangeNum != null )
{
ChangeNum(); /* 事件被触发 */
}else {
Console.WriteLine( "event not fire" );
Console.ReadKey(); /* 回车继续 */
}
}
public EventTest()
{
int n = 5;
SetValue( n );
}
public void SetValue( int n )
{
if ( value != n )
{
value = n;
OnNumChanged();
}
}
}
/***********订阅器类***********/
public class subscribEvent
{
public void printf()
{
Console.WriteLine( "event fire" );
Console.ReadKey(); /* 回车继续 */
}
}
/***********触发***********/
public class MainClass
{
public static void Main()
{
EventTest e = new EventTest(); /* 实例化对象,第一次没有触发事件 */
subscribEvent v = new subscribEvent(); /* 实例化对象 */
e.ChangeNum += new EventTest.NumManipulationHandler( v.printf ); /* 注册 */
e.SetValue( 7 );
e.SetValue( 11 );
}
}
}
当上面的代码被编译和执行时,它会产生下列结果:
event not fire event fire event fire
本实例提供一个简单的用于热水锅炉系统故障排除的应用程序。当维修工程师检查锅炉时,锅炉的温度和压力会随着维修工程师的备注自动记录到日志文件中。
实例 2
using System.IO;
namespace BoilerEventAppl
{
// Boiler 类
class Boiler
{
public int Temp { get; private set; }
public int Pressure { get; private set; }
public Boiler(int temp, int pressure)
{
Temp = temp;
Pressure = pressure;
}
}
// 事件发布器
class DelegateBoilerEvent
{
public delegate void BoilerLogHandler(string status);
// 基于上面的委托定义事件
public event BoilerLogHandler BoilerEventLog;
public void LogProcess()
{
string remarks = "O.K.";
Boiler boiler = new Boiler(100, 12);
int temp = boiler.Temp;
int pressure = boiler.Pressure;
if (temp > 150 || temp < 80 || pressure < 12 || pressure > 15)
{
remarks = "Need Maintenance";
}
OnBoilerEventLog($"Logging Info:\nTemperature: {temp}\nPressure: {pressure}\nMessage: {remarks}");
}
protected void OnBoilerEventLog(string message)
{
BoilerEventLog?.Invoke(message);
}
}
// 该类保留写入日志文件的条款
class BoilerInfoLogger : IDisposable
{
private readonly StreamWriter _streamWriter;
public BoilerInfoLogger(string filename)
{
_streamWriter = new StreamWriter(new FileStream(filename, FileMode.Append, FileAccess.Write));
}
public void Logger(string info)
{
_streamWriter.WriteLine(info);
}
public void Dispose()
{
_streamWriter?.Close();
}
}
// 事件订阅器
public class RecordBoilerInfo
{
static void Logger(string info)
{
Console.WriteLine(info);
}
static void Main(string[] args)
{
using (BoilerInfoLogger fileLogger = new BoilerInfoLogger("e:\\boiler.txt"))
{
DelegateBoilerEvent boilerEvent = new DelegateBoilerEvent();
boilerEvent.BoilerEventLog += Logger;
boilerEvent.BoilerEventLog += fileLogger.Logger;
boilerEvent.LogProcess();
}
Console.ReadLine();
}
}
}
当上面的代码被编译和执行时,它会产生下列结果:
Logging info: Temperature 100 Pressure 12 Message: O. K
qqqqqqqqqqq
264***7645@qq.com
写一个最简单的使用事件的代码。帮助理解。
public class Car { // 这个委托用来与Car事件协作 public delegate void CarEngineHandler(string msg); // 这种汽车可以发送这些事件 public event CarEngineHandler Exploded; public event CarEngineHandler AboutToBlow; public int CurrentSpeed { get; set; } public int MaxSpeed { get; set; } public string PetName { get; set; } private bool CarIsDead; public Car() { MaxSpeed = 100; } public Car(string name, int maxSp, int currSp) { CurrentSpeed = currSp; MaxSpeed = maxSp; PetName = name; } public void Accelerate(int delta) { // 如果Car无法使用了,触发Exploded事件 if (CarIsDead) { if (Exploded != null) { Exploded("sorry,this car is dead"); } } else { CurrentSpeed += delta; // 确认已无法使用,触发AboutToBlow事件 if ((MaxSpeed - CurrentSpeed) == 10 && AboutToBlow != null) { AboutToBlow("careful buddy ! gonna blow !"); } if (CurrentSpeed >= MaxSpeed) { CarIsDead = true; } else { Console.WriteLine(@"$CurrentSpeed={CurrentSpeed}"); } } } }qqqqqqqqqqq
264***7645@qq.com
坛子
113***6278@qq.com
一个最简单的例子,不带参数的事件。是实例 2 的一个简化版:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace BoilerEvent_tz { class DelegateTest { public delegate void delegate_tz(); public event delegate_tz delegate_tz0; public void start() { Console.WriteLine("启动事件"); delegate_tz0(); // 得调用该事件呀 Console.ReadKey(); } } class Program { static void Main(string[] args) { DelegateTest DelegateTest0 = new DelegateTest(); //DelegateTest0.delegate_tz0 += DelegateTest.delegate_tz(test); // 必须new一下才行,因为它是另外一个类呀 DelegateTest0.delegate_tz0 += new DelegateTest.delegate_tz(test); DelegateTest0.start(); // 启动事件 } static public void test() { Console.WriteLine("这是一个被注册的函数,按任意键继续..."); Console.ReadKey(); } } }坛子
113***6278@qq.com
crqq123
ays***q@163.com
就第一篇笔记的具体实现:
using System; namespace CarEvent { public class Car { // 申明委托 public delegate void CarEngineHandler(string msg); // 创建委托实例Exploded和AboutToBlow事件 public event CarEngineHandler Exploded; public event CarEngineHandler AboutToBlow; //设置属性 public int CurrentSpeed { get; set; } public int MaxSpeed { get; set; } public string PetName { get; set; } public bool CarIsDead;//用于判断是否超速 public Car()//构造函数 { MaxSpeed = 100; } public Car(string name, int maxSp, int currSp)//构造函数重载 { CurrentSpeed = currSp; MaxSpeed = maxSp; PetName = name; } public void Accelerate(int delta)//用于触发Exploded和AboutToBlow事件 { CurrentSpeed += delta;//"踩油门"加速 if (CurrentSpeed >= MaxSpeed)//判断时速 CarIsDead = true; else CarIsDead = false; if (CarIsDead)// 如果Car超速了,触发Exploded事件 { if (Exploded != null)//判断是否被委托联系起来 { Exploded("sorry,this car is dead");//调用CarDead事件 } } else { //如果没有超速,则提示快要超速并显示实时车速 if ((MaxSpeed - CurrentSpeed) > 0 && (MaxSpeed - CurrentSpeed) <= 10 && AboutToBlow != null)//判断是否被委托联系起来且速度是否接近临界值 { AboutToBlow("careful buddy ! gonna blow !");//调用NearDead事件 Console.WriteLine("CurrentSpeed={0}",CurrentSpeed);//显示实时车速 } } } } //订阅类书写举例 public class Answer { public void CarDead(string msg)//汽车已爆缸事件 { Console.WriteLine("sorry,this car is dead"); } public void NearDead(string msg)//汽车快要爆缸事件 { Console.WriteLine("careful buddy ! gonna blow !"); } } //主函数书写 public class test { static void Main(string[] args) { Car c = new Car("奔驰",100,93);//创建实例并初始化,初始速度为93 Answer an = new Answer(); c.Exploded += new Car.CarEngineHandler(an.CarDead);//Exploded"绑定"CarDead c.AboutToBlow += new Car.CarEngineHandler(an.NearDead);//AboutToBlow"绑定"NearDead c.Accelerate(6);//第一次加速,时速小于100,引发的事件为"快要爆缸"并显示实时车速为99 Console.ReadLine();//等待回车键已启动第二次加速 c.Accelerate(2);//第二次加速,时速超过100,引发的事件为"已爆缸",不显示车速 Console.ReadKey(); } } }结果为:
crqq123
ays***q@163.com
樱花树
100***2797@qq.com
一个事件调用委托的简单例子:
using System; /*功能:当起床铃声响起,就引发学生起床/厨师做早餐两个事件 */ // 定义一个委托(也可以定义在Ring类里面) public delegate void DoSomething(); // 产生事件的类 public class Ring { // 声明一个委托事件 public event DoSomething doIt; // 构造函数 public Ring() { } // 定义一个方法,即"响铃" 引发一个事件 public void RaiseEvent() { Console.WriteLine("铃声响了......."); // 判断事件是否有调用委托(是不是要求叫学生起床,叫厨师做饭) if (null != doIt) { doIt(); // 如果有注册的对象,那就调用委托(叫学生起床,叫厨师做饭) }else{ Console.WriteLine("无事发生......."); //没有注册,事件没有调用任何委托 } } } // 学生类( 处理事件类一) public class HandleEventOfStudents { // 默认构造函数 public HandleEventOfStudents() { } //叫学生起床 public void GetUp() { Console.WriteLine("[学生]:听到起床铃声响了,起床了。"); } } // 校园厨师类(处理事件类二) public class HandleEventOfChefs { // 默认构造函数 public HandleEventOfChefs() { } //叫厨师做早餐 public void Cook() { Console.WriteLine("[厨师]:听到起床铃声响了,为学生做早餐。"); } } // 主类 public class ListenerEvent { public static void Main(String[] args) { Ring ring = new Ring(); // 实例化一个铃声类[它是主角,都是因为它才牵连一系列的动作] ring.doIt += new HandleEventOfStudents().GetUp; // 注册,学生委托铃声类,铃声响起的时候叫我起床. ring.doIt += new HandleEventOfChefs().Cook; // 注册,厨师告诉铃声类,我也委托你叫我什么时候做早餐 ring.RaiseEvent(); // 铃声响起来了,它发现学生和厨师都拜托(注册)了自己,然后它就开始叫学生起床,叫厨师做早餐(一个事件调用了两个委托) } }你可能发现,上面的注册代码和前面的例子都不一样。这是因为 ring.doIt 本来就是 DoSomething 类型的,C# 会自动把学生类方法转换成相同的类型(猜测,但是上面的代码可以完美运行)。当然上面的注册代码也可以写成和文章例子的一样。 改成这样 ring.doIt += new Ring.DoSomething(new HandleEventOfStudents().GetUp); 这样也可以实现,当然这样的话定义委托的语句就要写在 Ring 类里面了。
樱花树
100***2797@qq.com
dzb
duz***o157@163.com
delegate 相当于定义一个函数类型。
event 相当于定义一个 delegate 的函数指针(回调函数指针)。
这样就好理解了。
dzb
duz***o157@163.com
Heavy
178***58425@163.com
实例:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace BoilerEvent_tz { class DelegateTest // 发布器类 { public delegate void delegate_tz(); public event delegate_tz delegate_tz0; public static void get() { Console.WriteLine("这是触发事件的第一个方法,此方法在事件类中"); } public void inter() { Console.WriteLine("开始调用事件方法"); delegate_tz0(); } } class Test // 订阅器类 { public Test() { } public void TryEvent() { Console.WriteLine("这是触发事件的第三个方法,在订阅器中,这才是正宗的订阅器类"); Console.ReadKey(); } } class Program //主体函数 程序入口 { static void Main(string[] args) { DelegateTest DelegateTest0 = new DelegateTest(); //主体函数中根据需求组装事件,组装过程类似委托多播 DelegateTest0.delegate_tz0 += new DelegateTest.delegate_tz(DelegateTest.get); DelegateTest0.delegate_tz0 += new DelegateTest.delegate_tz(Method1); DelegateTest0.delegate_tz0 += new DelegateTest.delegate_tz(new Test().TryEvent); DelegateTest0.inter(); // 执行操作,以触发事件 } static public void Method1() { Console.WriteLine("这是触发事件的第二个方法,在主体Main函数中"); } } }执行输出结果为:
Heavy
178***58425@163.com
bai
229***8988@qq.com
事件的运用总结
事件的整个过程是:订阅 -> 发布 -> 执行。
bai
229***8988@qq.com
咚伊夏
416***884@qq.com
明明可以很简单(对实例1的简单修改):
using System; namespace SimpleEvent { /***********发布器类***********/ public class EventTest { public delegate void NumManipulationHandler(); //声明委托 public event NumManipulationHandler ChangeNum; //声明事件 public void OpenDoor() { ChangeNum(); //事件触发 } } /***********订阅器类***********/ public class subscribEvent { public void printf() { Console.WriteLine( "The door is opened." ); } } /***********触发***********/ public class MainClass { public static void Main() { EventTest e = new EventTest(); /* 实例化事件触发对象 */ subscribEvent v = new subscribEvent(); /* 实例化订阅事件对象 */ /* 订阅器的printf()在事件触发对象中注册到委托事件中 */ e.ChangeNum += new EventTest.NumManipulationHandler( v.printf ); e.OpenDoor(); /* 触发了事件 */ } } }咚伊夏
416***884@qq.com
zy
353***928@qq.com
一个很简单的例子:当宠物店有新狗时通知订阅客户。
using System; namespace BoilerEvent { class Dog { private String name; private String color; public delegate void Handeler(); //定义委托 public static event Handeler NewDog; //定义事件 public Dog(String Name, String Color) { name = Name; color = Color; if (NewDog != null) { Console.WriteLine("新进了一只狗"); NewDog(); //调用事件 } } } class Test { public static void Main(String[] args) { Dog d = new Dog("Tony","yellow"); //因为还没有添加订阅,所以不能触发事件 Dog.NewDog += new Dog.Handeler(Client1); //Client1添加订阅 Dog.NewDog += new Dog.Handeler(Client2);//Client2添加订阅 Dog d2 = new Dog("Tom", "white"); } static void Client1() { Console.WriteLine("我喜欢这条狗!"); } static void Client2() { Console.WriteLine("我非常想要!"); } } }zy
353***928@qq.com
Oldyoung
yya***lu@163.com
using System; /***********发布器类***********/ public class EventTest { public delegate void NumManipulationHandler(string name, int a); //声明委托 public event NumManipulationHandler ChangeNum; //声明事件 public void OpenDoor(string name, int a)//模拟事件 { ChangeNum(name, a); //事件触发 } } /***********订阅器类***********/ public class subscribEvent { public void result(string name, int a) { Console.WriteLine("{0} is arriving at Airport Terminal{1}", name, a); } } /***********触发***********/ public class MainClass { public static void Main() { EventTest e = new EventTest(); /* 实例化事件触发对象 */ subscribEvent v = new subscribEvent(); /* 实例化订阅事件对象 */ /* 订阅器的printf()在事件触发对象中注册到委托事件中 */ e.ChangeNum += new EventTest.NumManipulationHandler(v.result); e.OpenDoor("Yang", 3); /* 模拟事件 */ } }Oldyoung
yya***lu@163.com
mmcike
271***7143@qq.com
事件是拥有可以注册和解绑方法(函数)的功能。
虽然事件和委托看起来有点绕,只要捋清楚事件和委托的关系,就会很容易理解。
委托是一个类,事件则是委托类中的一个对象,该对象是能够把其他方法注册到委托类中的一个事件(如果觉得有点绕,可以忽略这句话)。
事件和函数的关系:事件具有可以注册多个函数(和解绑函数)的功能,而函数如果要注册和解绑其他在其主体上运行的函数则需要改动该函数本体的代码,这就是区别。
以下代码的大致流程:定义一个新类(事件类)--》类中声明委托--》由委托类又声明事件--》再定义触发事件的函数--》函数主体中执行事件--》在主函数中实例化事件类--》进而调用事件类中的事件对象--》事件对象再注册(+=)两个方法--》再执行事件类中触发事件的那个函数--》再解绑其中一个方法--》再次执行事件类中触发事件的函数。
由此可见:事件是拥有可以注册和解绑方法(函数)的功能。
using System; namespace DelegateAndEvent { //定义一个事件类 public class MyEvent { //定义一个委托 public delegate void MyDelegate(); //定义一个事件 public MyDelegate MyDelegateEvent; //定义一个触发事件的函数 public void OnMyDelegateEvent() { //判断事件是否非空 if (MyDelegateEvent != null) { //执行事件 MyDelegateEvent(); } //MyDelegateEvent?.Invoke(); //简化的判断和执行 } } class Program { //输出一串字符 public static void putOutChar() { Console.WriteLine("I was fired"); } //输出第二串字符 public static void putOutChar2() { Console.WriteLine("I was fired22222"); } static void Main(string[] args) { //实例化MyEvent2类 MyEvent myEvent = new MyEvent(); //注册一个事件 myEvent.MyDelegateEvent += new MyEvent.MyDelegate(putOutChar); myEvent.MyDelegateEvent += new MyEvent.MyDelegate(putOutChar2); //执行触发事件的函数 Console.WriteLine("执行绑定了两个事件后的函数"); myEvent.OnMyDelegateEvent(); //解绑一个事件 myEvent.MyDelegateEvent -= new MyEvent.MyDelegate(putOutChar); //再次执行触发事件的函数 Console.WriteLine("执行解绑了一个事件后的函数"); myEvent.OnMyDelegateEvent(); Console.ReadKey(); } } }mmcike
271***7143@qq.com
cedar_forest
190***9203@qq.com
50 行简单例子:女朋友要求男朋友做的事。
using System; namespace Practice { public delegate void Job(); //任务列表委托 class She { private event Job BoyFriendJobs = null; //男朋友任务列表 public void command() { if (BoyFriendJobs != null) { //有男朋友的时候,发号施令 BoyFriendJobs(); } else { Console.WriteLine("原来我还没有男朋友啊..."); } } public void addJob(Job job) { //动态添加男朋友的任务,注意传入一个Job类型的方法 BoyFriendJobs += job; } } class He { public void buyTrainTicket() { //男朋友的任务 Console.WriteLine("买车票"); } public void buyCinemaTicket() { Console.WriteLine("买电影票"); } public void buyFood() { Console.WriteLine("买吃的"); } } class Program { public static void Main(string[] args) { She she = new She(); He he = new He(); she.addJob(he.buyTrainTicket); //添加男朋友任务 she.addJob(he.buyFood); she.addJob(he.buyCinemaTicket); she.command(); } } }输出:
cedar_forest
190***9203@qq.com
Gowcage
gow***e@163.com
为啥都写长篇大论呢,我来个简单的:
class Program { //定义委托和事件 delegate void MyEvent(); event MyEvent event = delegate { }; public void A() { } public void B() { } public void C() { } public static void Main(string[] args) { //订阅事件,当event事件触发时,A、B、C三个函数都将被调用 event += A; event += B; event += new MyEvent(C);//效果同上 event.Invoke();//效果同event(); 都是触发事件 } }Gowcage
gow***e@163.com
touchme
lab***_01@163.com
一个“有趣的”例子来理解事件(Event)
using System; /***异世界冒险者公会日常***/ namespace AdventurerGuild { class Client // 定义委托人类 { public delegate void PunishGoblins(); // 声明委托:讨伐哥布林 public event PunishGoblins CrusadeEvent; /* 声明事件: Publish:委托人发布委托(声明事件后被隐性初始化) Subscribe:冒险者接受委托 Perform:冒险者执行委托 */ public void TriggerEvent() // 事件触发器 { Console.WriteLine("委托人已将委托任务发布"); Console.WriteLine("(一段时间过去了...)"); if (CrusadeEvent != null) // 如果有Subscribe { Console.WriteLine("冒险者队伍接受了该委托"); CrusadeEvent(); // Perform } else // 如果没有Subscribe,则无事发生 { Console.WriteLine("目前没有冒险者队伍接受委托"); } } } class AdventurerTeam // 定义冒险者队伍类(接受委托的人) { private string name; public AdventurerTeam(string name) { this.name = name; } public void AcceptCommission() { Console.WriteLine($"冒险者队伍 {name} 前去讨伐哥布林..."); } } class Program { static void Main() { Client villager = new Client(); // 实例化一个委托人:村民 AdventurerTeam black = new AdventurerTeam("黑"); // 实例化一个接受委托的人:冒险者队伍“黑” AdventurerTeam white = new AdventurerTeam("白"); // 实例化一个接受委托的人:冒险者队伍“白” villager.CrusadeEvent += black.AcceptCommission; // Subscribe villager.CrusadeEvent += white.AcceptCommission; // Subscribe villager.TriggerEvent(); Console.ReadKey(); } } }touchme
lab***_01@163.com