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

曾经自以为掌握了委托与事件的关系及区别,然而看起来并没有。其实有很多知识,只是我们觉得自己掌握了,但是事实上我们真正的掌握了吗?

本文会从有趣的比喻出发来理解委托与事件的关系:
首先,先把官方的概念贴出来:

事件(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

猜你喜欢

转载自blog.csdn.net/m0_47472749/article/details/120635524