委托与事件_从有趣的例子出发去解析

曾经自以为掌握了委托与事件的关系及区别,然而看起来并没有。其实有很多知识,只是我们觉得自己掌握了,但是事实上我们真正的掌握了吗?
本文会从有趣的比喻出发来理解委托与事件的关系:
首先,先把官方的概念贴出来:
事件(Event) 基本上说是一个用户操作,如按键、点击、鼠标移动等等,或者是一些提示信息,如系统生成的通知。应用程序需要在事件发生时响应事件。例如,中断。
C# 中使用事件机制实现线程间的通信。
事件在类中声明且生成,且通过使用同一个类或其他类中的委托与事件处理程序关联。包含事件的类用于发布事件。这被称为 发布器(publisher) 类。其他接受该事件的类被称为 订阅器(subscriber) 类。事件使用 发布-订阅(publisher-subscriber) 模型。
发布器(publisher) 是一个包含事件和委托定义的对象。事件和委托之间的联系也定义在这个对象中。发布器(publisher)类的对象调用这个事件,并通知其他的对象。
订阅器(subscriber) 是一个接受事件并提供事件处理程序的对象。在发布器(publisher)类中的委托调用订阅器(subscriber)类中的方法(事件处理程序)。
来看看事件编程有哪些好处。
在以往我们编写这类程序中,往往采用等待机制,为了等待某件事情的发生,需要不断地检测某些判断变量,而引入事件编程后,大大简化了这种过程:
使用事件,可以很方便地确定程序执行顺序。
当事件驱动程序等待事件时,它不占用很多资源。事件驱动程序与过程式程序最大的不同就在于,程序不再不停地检查输入设备,而是呆着不动,等待消息的到来,每个输入的消息会被排进队列,等待程序处理它。如果没有消息在等待,则程序会把控制交回给操作系统,以运行其他程序。
事件简化了编程。操作系统只是简单地将消息传送给对象,由对象的事件驱动程序确定事件的处理方法。操作系统不必知道程序的内部工作机制,只是需要知道如何与对象进行对话,也就是如何传递消息。
委托可以理解成为函数指针,不同的是委托是面向对象,而且是类型安全的。
我们可以把事件编程简单地分成两个部分:事件发生的类(书面上叫事件发生器)和事件接收处理的类。事件发生的类就是说在这个类中触发了一个事件,但这个类并不知道哪个对象或方法将会接收到并处理它触发的事件。所需要的是在发送方和接收方之间存在一个媒介。这个媒介在.NET Framework中就是委托(delegate)。在事件接收处理的类中,我们需要有一个处理事件的方法。
我们知道, Windows是一个消息(Message)驱动系统。Windows的消息提供了应用程序之间、应用程序与Windows系统之间进行通信的手段。应用程序想要实现的功能由消息来触发,并且靠对消息的响应和处理来完成。必须注意的是,消息并非是抢占性的,无论事件的缓急,总是按照到达的先后派对,依次处理(一些系统消息除外),这样可能使一些实时外部事件得不到及时处理。
Windows的应用程序一般包含窗口(Window),它主要为用户提供一种可视化的交互方式,窗口是总是在某个线程(Thread)内创建的。Windows系统通过消息机制来管理交互,消息(Message)被发送,保存,处理,一个线程会维护自己的一套消息队列(Message Queue),以保持线程间的独占性。队列的特点无非是先进先出,这种机制可以实现一种异步的需求响应过程。
那么,我们如何去理解委托与事件的关系,举个上世纪邮局邮递报纸的例子,或许可以让你豁然开朗去理解委托与事件。
上世纪还没有互联网的时候,我们都需要邮递员帮我们送报纸,那么在事件的发布–订阅模型中,我们普通居民就是事件的订阅者,报社就是事件的发布者,邮局就是帮我们送报纸的,是我们委托邮局的邮递员帮我们送报纸。
那么如何委托呢,我们需要签一份订阅报纸的纸面的东西,并支付钱。那么这就是 订阅 ,这就代表我们已经委托让邮递员送报纸了,我们不必再去邮局取了。
那么,邮局可以给很多人送报纸,意思就是可以有很多人是订阅。那么很多人订阅之后,拿到报纸,接下来的动作都是不一样的,这叫事件处理方法不同。
就是,很多人签订合同订报纸(订阅事件)—委托邮递员送报纸(委托delegate)—报刊发布报纸(发布者发布事件)–不同的人收到报纸进行下一步动作(事件响应程序)
下面把上面所举的例子一个demo表示出来:
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace 事件测试Demo { public class Post { public delegate void EventHandler(object sender); public event EventHandler Evt; public void Run() { Console.WriteLine("邮局:新报纸出来喽"); Evt(this); } } public class PersonA { public void PersonAReaction(object sender) //A的事件处理程序 { Console.WriteLine("PersonA:新报纸来啦,赶紧用来垫东西!"); } } public class PersonB { public void PersonBReaction(object sender) //B的事件处理程序 { Console.WriteLine("PersonB:新报纸来啦,赶紧阅读阅读看看有啥新闻!"); } } public class PersonC { public void PersonCReaction(object sender)//C的事件处理程序 { Console.WriteLine("PersonC:新报纸来啦,下一年的报纸费用还得付,怎么办,快没钱了!"); } } class Program { static void Main(string[] args) { Post p = new Post(); //邮局 PersonA a = new PersonA(); //订阅者A PersonB b = new PersonB(); //订阅者B PersonC c = new PersonC(); //订阅者C p.Evt += new Post.EventHandler(a.PersonAReaction); //订阅报纸 p.Evt += new Post.EventHandler(b.PersonBReaction); //订阅报纸 p.Evt += new Post.EventHandler(c.PersonCReaction); //订阅报纸 //触发事件,发报纸喽 p.Run(); Console.ReadKey(); } } }
以下为运行结果:可以看到事件只需要触发一次(发报纸喽!),每个订阅者都会随之反应,而且,每个人的反应情况(心路历程)都是不一样的。

基本上核心都是以下步骤:
C#中使用事件需要的步骤:
1.创建一个委托,声明一个事件
2.编写事件处理程序
3.将创建的委托与声明的事件关联,又叫订阅事件,委托后面跟上你编写的事件处理程序。订阅事件可以有多个动作订阅该事件。
4.触发事件
5.响应事件,运行响应程序。
接下来,会给出更多事件相关的demo。
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace 事件测试1 { 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 Program { 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 ); } } }
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.IO; namespace 事件测试2 { // boiler 类 class Boiler { private int temp; private int pressure; public Boiler(int t, int p) { temp = t; pressure = p; } public int getTemp() { return temp; } public int getPressure() { return pressure; } } // 事件发布器 class DelegateBoilerEvent { public delegate void BoilerLogHandler(string status); // 基于上面的委托定义事件 public event BoilerLogHandler BoilerEventLog; public void LogProcess() { string remarks = "O. K"; Boiler b = new Boiler(100, 12); int t = b.getTemp(); int p = b.getPressure(); if(t > 150 || t < 80 || p < 12 || p > 15) { remarks = "Need Maintenance"; } OnBoilerEventLog("Logging Info:\n"); OnBoilerEventLog("Temparature " + t + "\nPressure: " + p); OnBoilerEventLog("\nMessage: " + remarks); } protected void OnBoilerEventLog(string message) { if (BoilerEventLog != null) { BoilerEventLog(message); } } } // 该类保留写入日志文件的条款 class BoilerInfoLogger { FileStream fs; StreamWriter sw; public BoilerInfoLogger(string filename) { fs = new FileStream(filename, FileMode.Append, FileAccess.Write); sw = new StreamWriter(fs); } public void Logger(string info) { sw.WriteLine(info); } public void Close() { sw.Close(); fs.Close(); } } // 事件订阅器 public class RecordBoilerInfo { static void Logger(string info) { Console.WriteLine(info); }//end of Logger static void Main(string[] args) { BoilerInfoLogger filelog = new BoilerInfoLogger("boiler.txt"); DelegateBoilerEvent boilerEvent = new DelegateBoilerEvent(); boilerEvent.BoilerEventLog += new DelegateBoilerEvent.BoilerLogHandler(Logger); boilerEvent.BoilerEventLog += new DelegateBoilerEvent.BoilerLogHandler(filelog.Logger); boilerEvent.LogProcess(); Console.ReadLine(); filelog.Close(); }//end of main }//end of RecordBoilerInfo }
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading; namespace 事件测试3 { //事件发送者 class Dog { //1.声明关于事件的委托; public delegate void AlarmEventHandler(object sender, EventArgs e); //2.声明事件; public event AlarmEventHandler Alarm; //3.编写引发事件的函数; public void OnAlarm() { if (this.Alarm != null) { Console.WriteLine("\n狗报警: 有小偷进来了,汪汪汪汪汪汪汪汪~~~~~~~"); this.Alarm(this, new EventArgs()); //发出警报 } } } //事件接收者 class Host { //4.编写事件处理程序 void HostHandleAlarm(object sender, EventArgs e) { Console.WriteLine("主人: 抓小偷!冲啊啊啊啊啊啊~~~~~~~~~"); } //5.注册事件处理程序 public Host(Dog dog) { dog.Alarm += new Dog.AlarmEventHandler(HostHandleAlarm); } } //6.现在来触发事件 class Program { static void Main(string[] args) { Dog dog = new Dog(); Host host = new Host(dog); //当前时间,从2008年12月31日23:59:50开始计时 DateTime now = new DateTime(2015, 12, 31, 23, 59, 50); DateTime midnight = new DateTime(2016, 1, 1, 0, 0, 0); //等待午夜的到来 Console.WriteLine("时间一秒一秒地流逝... "); while (now < midnight) { Console.WriteLine("当前时间: " + now); Thread.Sleep(1000); //程序暂停一秒 now = now.AddSeconds(1); //时间增加一秒 } //午夜零点小偷到达,看门狗引发Alarm事件 Console.WriteLine("\n月黑风高的午夜: " + now); Console.WriteLine("小偷悄悄地摸进了主人的屋内... "); dog.OnAlarm(); Console.ReadLine(); } } }
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace 事件测试4 { internal class KeyEventArgs : EventArgs { private char keychar; public KeyEventArgs(char keychar) : base() { this.keychar = keychar; } public char Keychar { get { return keychar; } } } internal class KeyInputMonitor { // 创建一个委托,返回类型为void,两个参数 public delegate void KeyDownHandler(object sender, KeyEventArgs e); // 将创建的委托和特定事件关联,在这里特定的事件为KeyDown public event KeyDownHandler KeyDown; public void Run() { bool finished = false; do { Console.WriteLine("Input a char"); string response = Console.ReadLine(); char responsechar = (response == "") ? ' ' : char.ToUpper(response[0]); switch (responsechar) { case 'X': finished = true; break; default: // 得到按键信息的参数 KeyEventArgs keyEventArgs = new KeyEventArgs(responsechar); // 触发事件 KeyDown(this, keyEventArgs); break; } } while (!finished); } } internal class EventReceiver { public EventReceiver(KeyInputMonitor monitor) { // 产生一个委托实例并添加到KeyInputMonitor产生的事件列表中 monitor.KeyDown += new KeyInputMonitor.KeyDownHandler(this.OnKeyDown); } private void OnKeyDown(object sender, KeyEventArgs e) { // 真正的事件处理函数 Console.WriteLine("Capture key: {0}", e.Keychar); } } class Program { static void Main(string[] args) { // 实例化一个事件发送器 KeyInputMonitor monitor = new KeyInputMonitor(); // 实例化一个事件接收器 EventReceiver eventReceiver = new EventReceiver(monitor); // 运行 monitor.Run(); } } }
本文部分内容来自:
https://blog.csdn.net/Evankaka/article/details/44456661
https://blog.csdn.net/sinat_23338865/article/details/83348633
https://www.runoob.com/csharp/csharp-event.html
https://www.jb51.net/article/133032.htm
浙公网安备 33010602011771号