曾经自以为掌握了委托与事件的关系及区别,然而看起来并没有。其实有很多知识,只是我们觉得自己掌握了,但是事实上我们真正的掌握了吗?
本文会从有趣的比喻出发来理解委托与事件的关系:
首先,先把官方的概念贴出来:
事件(Event) 基本上说是一个用户操作,如按键、点击、鼠标移动等等,或者是一些提示信息,如系统生成的通知。应用程序需要在事件发生时响应事件。例如,中断。
C# 中使用事件机制实现线程间的通信。
事件在类中声明且生成,且通过使用同一个类或其他类中的委托与事件处理程序关联。包含事件的类用于发布事件。这被称为 发布器(publisher) 类。其他接受该事件的类被称为 订阅器(subscriber) 类。事件使用 发布-订阅(publisher-subscriber) 模型。
发布器(publisher) 是一个包含事件和委托定义的对象。事件和委托之间的联系也定义在这个对象中。发布器(publisher)类的对象调用这个事件,并通知其他的对象。
订阅器(subscriber) 是一个接受事件并提供事件处理程序的对象。在发布器(publisher)类中的委托调用订阅器(subscriber)类中的方法(事件处理程序)。
来看看事件编程有哪些好处。
在以往我们编写这类程序中,往往采用等待机制,为了等待某件事情的发生,需要不断地检测某些判断变量,而引入事件编程后,大大简化了这种过程:
-
使用事件,可以很方便地确定程序执行顺序。
-
当事件驱动程序等待事件时,它不占用很多资源。事件驱动程序与过程式程序最大的不同就在于,程序不再不停地检查输入设备,而是呆着不动,等待消息的到来,每个输入的消息会被排进队列,等待程序处理它。如果没有消息在等待,则程序会把控制交回给操作系统,以运行其他程序。
-
事件简化了编程。操作系统只是简单地将消息传送给对象,由对象的事件驱动程序确定事件的处理方法。操作系统不必知道程序的内部工作机制,只是需要知道如何与对象进行对话,也就是如何传递消息。
扫描二维码关注公众号,回复: 13490325 查看本文章
委托可以理解成为函数指针,不同的是委托是面向对象,而且是类型安全的。
我们可以把事件编程简单地分成两个部分:事件发生的类(书面上叫事件发生器)和事件接收处理的类。事件发生的类就是说在这个类中触发了一个事件,但这个类并不知道哪个对象或方法将会接收到并处理它触发的事件。所需要的是在发送方和接收方之间存在一个媒介。这个媒介在.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