当前位置: 首页 > news >正文

.NET delegate 委托 、 Event 事件,接口回调

// 定义委托
delegate void WorkDone();

class Program
{
	static void Main(string[] args)
	{
		Do();

		Console.ReadLine();
	}

	public static void Do()
	{
		// 首先给callback委托赋值
		WorkDone callback = new WorkDone(WorkDoneHandler);
		// 将callback作为参数
		Working(callback);
	}

	public static void Working(WorkDone callBack)
	{
		// 当工作完成的时候执行这个委托
		callBack();
	}

	public static void WorkDoneHandler()
	{
		Console.WriteLine(DateTime.Now);
	}
}
/*上面的代码中,将方法WorkDoneHandler()作为参数,传递给了另一个方法Working(WorkDone callBack),这样做的好处在于,可以动态的指定执行哪个方法。比如在Do()方法中,我们指定的callback 是WorkDoneHandler 当然也可以是其它匹配的方法。而Working()方法根本不需要知道自己最后执行的是哪个Handler。*/
二、	接口回调
通常情况下,我们创建一个对象,并马上直接去使用它的方法。然而,在有些情况下,希望能在某个场景出现后或条件满足时才调用此对象的方法。回调就可以解决这个“延迟调用对象方法”的问题。这个被调用方法的对象称为回调对象。
实现回调的原理简介如下: 
首先创建一个回调对象,然后再创建一个控制器对象,将回调对象需要被调用的方法告诉控制器对象。控制器对象负责检查某个场景是否出现或某个条件是否满足。当此场景出现或此条件满足时,自动调用回调对象的方法。

以下为C#实现回调的一个小例子:
using System; 
using System.Collections.Generic; 
using System.Text; 
namespace ConsoleApplication1 
{ 
     class Program 
     { 
         static void Main(string[] args) 
         { 
             //创建一个控制器对象,将提供给它的回调对象传入 
             Controller obj = new Controller(new CallBack()); 
             //启动 
             obj.Star(); 
         } 
     } 
     public interface IBack 
     { 
         void run(); 
     } 
     public class CallBack : IBack 
     { 
         public void run() 
         { 
             //为了简洁这里只是显示下时间 
             System.Console.WriteLine(DateTime.Now); 
         } 
     } 
     public class Controller 
     { 
         public IBack CallBackObj = null;   //这里引用回调对象 
         public Controller(IBack obj) 
         { 
             this.CallBackObj = obj; 
         } 
         public void Star() 
         { 
             Console.WriteLine("敲键盘任意键就显示当前的时间,直到按ESC退出...."); 
             while (Console.ReadKey(true).Key != ConsoleKey.Escape) 
             { 
                 CallBackObj.run(); 
             } 
         } 
     } 
} 
     可以看到,当示例程序运行时,何时调用CallBack对象的run()方法是由用户决定的,用户每敲一个键,控制器对象就调用一次CallBack的run()方法。这个示例中实现回凋的关键在于IBack接口的引入。 

     如果不用IBack接口,而直接使用 CallBack对象,一样可以实现同样的效果,如下: 
       public class Controller 
     { 
         public CallBack CallBackObj = null;   //回调对象方法的引用 
         public Controller(CallBack obj) 
         { 
             this.CallBackObj = obj; 
         } 
         public void Star() 
         { 
             Console.WriteLine("敲键盘任意键就显示当前的时间,直到按ESC退出...."); 
             while (Console.ReadKey(true).Key != ConsoleKey.Escape) 
             { 
                 CallBackObj.run(); 
             } 
         } 
     } 
   
   但仔细思考,这样做的结果就使Controller类与CallBack对象绑定在一起,万一如果需要调用其他类型的对象,则必须修改Controller类的代码。 
   如果Controller类接收的是一个抽象的接口变量Iback,则任何实现了该接口的对象都可以被Controller类对象所回调,Controller类的代码就再不用被修改,保证了代码对环境的适应性,无疑是一个很好的解决方案。

通过加入event关键字,在编译的时候编译器会自动针对事件生成一个私有的字段(与此事件相关的委托),以及add_xxx和remove_xxx两个访问器方法。其它类只能+=、-= 操作,不能直接 xx.eventName;
1.委托允许直接通过委托去访问相应的处理函数,而事件只能通过公布的回调函数去调用
2.事件只能通过“+=”,“-=”方式注册和取消订户处理函数,而委托除此之外还可以使用“=”直接赋值处理函数。

在这里插入图片描述

.NET 编译成
private event delegate evntname;
public event delegate EventName{
add__xxx 用+= 调用
remove_xxx 用-=调用
}

 //定义委托,它定义了可以代表的方法的类型 
    public delegate void GreetingDelegate(string name);
    class Program
    {
        private static void EnglishGreeting(string name)
        {
            Console.WriteLine("Morning, " + name);
        }
        private static void ChineseGreeting(string name)
        {
            Console.WriteLine("早上好, " + name);
        }
        //注意此方法,它接受一个GreetingDelegate类型的方法作为参数 
        private static void GreetPeople(string name, GreetingDelegate MakeGreeting) {
            MakeGreeting(name);
        }

    static void Main(string[] args)
        {
            GreetPeople("XXXXXX", EnglishGreeting);
            GreetPeople("YYYYY", ChineseGreeting); 
            Console.ReadLine();

			GreetingDelegate delegate1; 
			// 先给委托类型的变量赋值 
			delegate1 = EnglishGreeting; //等价于delegate1=new GreetingDelegate(EnglishGreeting)			
			// 给此委托变量再绑定一个方法
			delegate1 += ChineseGreeting; 		
			//等价于------------------------
			//GreetingDelegate delegate1 = new GreetingDelegate (EnglishGreeting); 
			//delegate1 += ChineseGreeting; // 给此委托变量再绑定一个方法
			//--------------------------------------	
			// 将先后调用 EnglishGreeting 与 ChineseGreeting 方法
			GreetPeople("XXXX", delegate1); 
			Console.ReadKey();
			注意这里,第一次用的“=”,是赋值的语法;
			第二次,用的是“+=”,是绑定的语 法。
			如果第一次就使用“+=”,将出现“使用了未赋值的局部变量”的编译错误。
			//-------------------------------------------
			GreetingDelegate delegate1 = new GreetingDelegate(); 
			delegate1 += EnglishGreeting; // 这次用的是 “+=”,绑定语法。
			delegate1 += ChineseGreeting; // 给此委托变量再绑定一个方法		
			但实际上,这样会出现编译错误: “GreetingDelegate”方法没有采用“0”个参数 的重载。
			尽管这样的结果让我们觉得有点沮丧,但是编译的提示:“没有0个参数的重 载”再次让我们联想到了类的构造函数。
			但事件就可以了。
			//------------------------------------------------------------------	
			
        }


    }

委托是一个类,它定义了方法的类型,使得可以将方法当作另一个方法的参数 来进行传递,这种将方法动态地赋给参数的做法,可以避免在程序中大量使用If-Else (Switch)语句,同时使得程序具有更好的可扩展性。
使用委托可以将多个方法绑定到同一个委托变量,当调用此变量时(这里用“调
用”这个词,是因为此变量代表一个方法),可以依次调用所有绑定的方法。

事件 其实没什么不好理解的,声明一个事件不过类似于声明一个进行了封装的委托类型 的变量而已。
在类的内部,不管你声明它是public还是protected,它总是private的。
在类的外部,注册“+=”和注 销“-=”的访问限定符与你在声明事件时使用的访问符相同。

public class GreetingManager{ 
		//这一次我们在这里声明一个事件 
		public event GreetingDelegate MakeGreet;
		public void GreetPeople(string name) { 
			MakeGreet(name);
		} 
}
static void Main(string[] args) { 
	GreetingManager gm = new GreetingManager();
	//实际上尽管我们在GreetingManager里将 MakeGreet 声明为public,但是,实际上MakeGreet会被编译成私有字段,
	gm.MakeGreet = EnglishGreeting;   // 编译错误1     +=,-=  编译成功
	gm.MakeGreet += ChineseGreeting;
	gm.GreetPeople("Jimmy Zhang");
}

在这里插入图片描述

现在已经很明确了:MakeGreet事件确实是一个GreetingDelegate类型的委 托,只不过不管是不是声明为public,它总是被声明为private。另外,它还有两个 方法,分别是add_MakeGreet和remove_MakeGreet,这两个方法分别用于注册委 托类型的方法和取消注册。实际上也就是: “+= ”对应 add_MakeGreet,“-=”对应 remove_MakeGreet。而这两个方法的访问限制取决于声明事件时的访问限制符。

namespace Delegate { 
	class Heater 
	{
 			private int temperature; // 水温 
 			// 烧水 
	 		public void BoilWater() {
		 		 for (int i = 0; i <= 100; i++) 
		 			 {  
		 			 		temperature = i;
							if (temperature > 95) 
							{
							 MakeAlert(temperature);
							  ShowMsg(temperature);
						    }
					 }
			}
			// 发出语音警报 
			private void MakeAlert(int param) {
				 Console.WriteLine("Alarm:嘀嘀嘀,水已经 {0} 度了:" , param); 
			 }
			// 显示水温 
			private void ShowMsg(int param) {
				 Console.WriteLine("Display:水快开了,当前温度:{0}度。" , param); 
			 }
 	 }
	class Program { 
		static void Main() 
		{ 
			Heater ht = new Heater(); 
			ht.BoilWater();
		} 
	}
 }

在这里插入图片描述

namespace Delegate {
	 // 热水器
	 public class Heater { 
		  private int temperature; 
		  public delegate void BoilHandler(int param); //声明委托
		  public event BoilHandler BoilEvent;//声明事件
	     // 烧水
		 public void BoilWater() {
		 	 for (int i = 0; i <= 100; i++) {
		 	    temperature = i;
				if (temperature > 95) { 
					if (BoilEvent != null) {         //如果有对象注册 
					    BoilEvent(temperature); 	 //调用所有注册对象的方法 
					 } 
				 } 
			} 
	  }
  }
  // 警报器 
  public class Alarm { 
	  public void MakeAlert(int param) {
	  	 Console.WriteLine("Alarm:嘀嘀嘀,水已经 {0} 度了:",param); 
	   } 
   }
// 显示器
 public class Display {
	  public static void ShowMsg(int param) {
		   //静态方法
		    Console.WriteLine("Display:水快烧开了,当前温度:{0}度。", param); 
	    }
  }
class Program { 
	static void Main() {
		 Heater heater = new Heater(); 
		 Alarm alarm = new Alarm();
		 heater.BoilEvent += alarm.MakeAlert; //注册方法 
		 heater.BoilEvent += (new Alarm()).MakeAlert; //给匿名对象注册方法 
		 heater.BoilEvent += Display.ShowMsg; //注册静态方法
		 heater.BoilWater(); 
		 //烧水,会自动调用注册过对象的方 法 
	 }
 }

在这里插入图片描述

using System; 
using System.Collections.Generic;
 using System.Text;
namespace Delegate { 
	// 热水器 
	public class Heater {
		 private int temperature; 
	 	 public string type = "RealFire 001"; // 添加型号作为演示 
		 public string area = "China Xian"; // 添加产地作为演示
	    //声明委托 
		 public delegate void BoiledEventHandler(Object sender,BoiledEventArgs e); 
		 public event BoiledEventHandler Boiled; //声明事件
		 // 定义BoiledEventArgs类,传递给Observer所感兴趣的信息    //事件参数
		 public class BoiledEventArgs : EventArgs {
			  public readonly int temperature;
			  public BoiledEventArgs(int temperature) {
			     this.temperature = temperature;
			  }
		 } 
	// 可以供继承自 Heater 的类重写,以便继承类拒绝其他对象对它的监视
	protected virtual void OnBoiled(BoiledEventArgs e) { 
		if (Boiled != null) { // 如果有对象注册 
		  Boiled(this, e); // 调用所有注册对象的方法
		} 
	}
	// 烧水。
	 public void BoilWater() { 
		 for (int i = 0; i <= 100; i++) { 
				   temperature = i;
				  if (temperature > 95) { 
					  //建立BoiledEventArgs 对象。
					   BoiledEventArgs e = new BoiledEventArgs(temperature); 
					   OnBoiled(e); // 调用 OnBolied方法 
				  } 
		} 
	} 
}
//---------
// 警报器 
public class Alarm { 
	public void MakeAlert(Object sender,Heater.BoiledEventArgs e) 
	{ 
		Heater heater = (Heater)sender; //这里是不是很熟悉呢?
		//访问 sender 中的公共字段 
		Console.WriteLine("Alarm:{0} - {1}: ", heater.area,heater.type); 
		Console.WriteLine("Alarm: 嘀嘀嘀,水已经 {0} 度了:",e.temperature); 
		Console.WriteLine();
	}
 }
// 显示器 
public class Display {
	public static void ShowMsg(Object sender,Heater.BoiledEventArgs e) {
	  //静态方法 
	  Heater heater = (Heater)sender; 
	  Console.WriteLine("Display:{0} - {1}: ",heater.area, heater.type);
	  Console.WriteLine("Display:水快烧开了,当前温度:{0}度。", e.temperature); 
	  Console.WriteLine();
	} 
}
	class Program { 
		static void Main() { 
				Heater heater = new Heater();
				Alarm alarm = new Alarm();
				heater.Boiled += alarm.MakeAlert; //注册方法 
				heater.Boiled += (new Alarm()).MakeAlert;//给匿名对象注册方法 
				heater.Boiled += new Heater.BoiledEventHandler(alarm.MakeAlert); //也可以这么注册
				heater.Boiled += Display.ShowMsg; //注册静态方法 
				heater.BoilWater(); //烧水,会自动调用注册过对象的方 法 
		 } 
	 } 
 }
输出为:
  Alarm:China Xian - RealFire 001: 
  Alarm: 嘀嘀嘀,水已经 96 度了:
  Alarm:China Xian - RealFire 001:
  Alarm: 嘀嘀嘀,水已经 96 度了: 
  Alarm:China Xian - RealFire 001: 
  Alarm: 嘀嘀嘀,水已经 96 度了: 
  Display:China Xian - RealFire 001: 
  Display:水快烧开了,当前温度:96度。
   // 省略 ...

相关文章:

  • 猿创征文|时间序列分析算法之平稳时间序列预测算法和自回归模型(AR)详解+Python代码实现
  • 设置Redis最大占用内存
  • 调用MapReuce对文件中各单词出现次数统计
  • Redis三大缓存问题(穿透、击穿、雪崩)
  • C/C++基础
  • Java刷题面试系列习题(五)
  • AprilTags c++识别
  • LeetCode精选200道--二叉树篇(二)
  • 【学习教程】MCM箱模型建模方法及大气O3来源解析实践技术应用
  • IDEA配置
  • 这可能是目前最全的Redis高可用技术解决方案
  • FLASH:一种高效的Transformer设计
  • SpringBoot 和 Vue前后端分离在线工具项目实战,源码+超详细讲解
  • 【前端技术】Vue3 技术学习笔记
  • JS中4种常见的内存泄漏
  • 【React系列】如何构建React应用程序
  • ERLANG 网工修炼笔记 ---- UDP
  • maven工程打包jar以及java jar命令的classpath使用
  • oschina
  • Vue全家桶实现一个Web App
  • windows下如何用phpstorm同步测试服务器
  • 聊聊springcloud的EurekaClientAutoConfiguration
  • 前端每日实战:70# 视频演示如何用纯 CSS 创作一只徘徊的果冻怪兽
  • 学习Vue.js的五个小例子
  • 在weex里面使用chart图表
  • 正则与JS中的正则
  • 最近的计划
  • d²y/dx²; 偏导数问题 请问f1 f2是什么意思
  • 说说我为什么看好Spring Cloud Alibaba
  • ​如何防止网络攻击?
  • ​云纳万物 · 数皆有言|2021 七牛云战略发布会启幕,邀您赴约
  • !!【OpenCV学习】计算两幅图像的重叠区域
  • #我与Java虚拟机的故事#连载13:有这本书就够了
  • #预处理和函数的对比以及条件编译
  • (3)nginx 配置(nginx.conf)
  • (4)通过调用hadoop的java api实现本地文件上传到hadoop文件系统上
  • (day 2)JavaScript学习笔记(基础之变量、常量和注释)
  • (Java)【深基9.例1】选举学生会
  • (论文阅读40-45)图像描述1
  • (未解决)jmeter报错之“请在微信客户端打开链接”
  • .CSS-hover 的解释
  • .form文件_一篇文章学会文件上传
  • .gitignore文件_Git:.gitignore
  • .NET Standard / dotnet-core / net472 —— .NET 究竟应该如何大小写?
  • .NET 使用 XPath 来读写 XML 文件
  • .NET(C#、VB)APP开发——Smobiler平台控件介绍:Bluetooth组件
  • .Net8 Blazor 尝鲜
  • .NET微信公众号开发-2.0创建自定义菜单
  • :O)修改linux硬件时间
  • [<MySQL优化总结>]
  • [2013AAA]On a fractional nonlinear hyperbolic equation arising from relative theory
  • [20150707]外部表与rowid.txt
  • [2023年]-hadoop面试真题(一)
  • [23] GaussianAvatars: Photorealistic Head Avatars with Rigged 3D Gaussians
  • [2544]最短路 (两种算法)(HDU)