• .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。*/
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    二、	接口回调
    通常情况下,我们创建一个对象,并马上直接去使用它的方法。然而,在有些情况下,希望能在某个场景出现后或条件满足时才调用此对象的方法。回调就可以解决这个“延迟调用对象方法”的问题。这个被调用方法的对象称为回调对象。
    实现回调的原理简介如下: 
    首先创建一个回调对象,然后再创建一个控制器对象,将回调对象需要被调用的方法告诉控制器对象。控制器对象负责检查某个场景是否出现或某个条件是否满足。当此场景出现或此条件满足时,自动调用回调对象的方法。
    
    以下为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类的代码就再不用被修改,保证了代码对环境的适应性,无疑是一个很好的解决方案。
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73

    通过加入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个参数的重 载”再次让我们联想到了类的构造函数。
    			但事件就可以了。
    			//------------------------------------------------------------------	
    			
            }
    
    
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51

    委托是一个类,它定义了方法的类型,使得可以将方法当作另一个方法的参数 来进行传递,这种将方法动态地赋给参数的做法,可以避免在程序中大量使用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");
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    在这里插入图片描述

    现在已经很明确了: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();
    		} 
    	}
     }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    在这里插入图片描述

    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(); 
    		 //烧水,会自动调用注册过对象的方 法 
    	 }
     }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43

    在这里插入图片描述

    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度。
       // 省略 ...
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81

    事件场景:
    猫叫->老鼠逃跑 & 主人惊醒
    这是一个典型的观察者模式的应用场景,事件的发源在于猫叫这个动作,在猫叫之后,老鼠开始逃跑,而主人则会从睡梦中惊醒。可以发现,主人和老鼠这两个类型的动作相互之间没有联系,但都是由猫叫这一事件触发的。

    设计的大致思路在于,猫类包含并维护一个猫叫的动作,主人和老鼠的对象实例需要订阅猫叫这一事件,保证猫叫这一事件发生时主人和老鼠可以执行相应的动作。

    (1)设计猫类,为其定义一个猫叫的事件CatCryEvent:
      public class Cat
    {
    private string name;
    // 猫叫的事件
    public event EventHandler CatCryEvent;

        public Cat(string name)
        {
            this.name = name;
        }
    
        // 触发猫叫事件
        public void CatCry()
        {
            // 初始化事件参数
            CatCryEventArgs args = new CatCryEventArgs(name);
            Console.WriteLine(args);
            // 开始触发事件
            CatCryEvent(this, args);
        }
    }
    
    public class CatCryEventArgs : EventArgs
    {
        private string catName;
    
        public CatCryEventArgs(string catName)
            : base()
        {
            this.catName = catName;
        }
    
        public override string ToString()
        {
            string message = string.Format("{0}叫了", catName);
            return message;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

    (2)设计老鼠类,在其构造方法中订阅猫叫事件,并提供对应的处理方法
    public class Mouse
    {
    private string name;
    // 在构造方法中订阅事件
    public Mouse(string name, Cat cat)
    {
    this.name = name;
    cat.CatCryEvent += CatCryEventHandler;
    }

        // 猫叫的处理方法
        private void CatCryEventHandler(object sender, CatCryEventArgs e)
        {
            Run();
        }
    
        // 逃跑方法
        private void Run()
        {
            Console.WriteLine("{0}逃走了:我勒个去,赶紧跑啊!", name);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    (3)设计主人类,在其构造犯法中订阅猫叫事件,并提供对应的处理方法

    public class Master
    {
    private string name;

        // 在构造方法中订阅事件
        public Master(string name, Cat cat)
        {
            this.name = name;
            cat.CatCryEvent += CatCryEventHandler;
        }
    
        // 针对猫叫的处理方法
        private void CatCryEventHandler(object sender, CatCryEventArgs e)
        {
            WakeUp();
        }
    
        // 具体的处理方法——惊醒
        private void WakeUp()
        {
            Console.WriteLine("{0}醒了:我勒个去,叫个锤子!", name);
        }
    }
    (4)最后在Main方法中进行场景的模拟:
    class Program
    {
        static void Main(string[] args)
        {
            Cat cat = new Cat("假老练");
            Mouse mouse1 = new Mouse("风车车", cat);
            Mouse mouse2 = new Mouse("米奇妙", cat);
            Master master = new Master("李扯火", cat);
            // 毛开始叫了,老鼠和主人有不同的反应
            cat.CatCry();
    
            Console.ReadKey();
        }
    }
    这里定义了一只猫,两只老鼠与一个主人,当猫的CatCry方法被执行到时,会触发猫叫事件CatCryEvent,此时就会通知所有这一事件的订阅者。本场景的关键之处就在于主人和老鼠的动作应该完全由猫叫来触发。下面是场景模拟代码的运行结果:
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35

    在这里插入图片描述

    总结:
    事件Event本质也是委托,只是特殊的委托
    [Serializable]
    public delegate void EventHandler(object sender, TEventArgs e);
    该委托没有返回值,并且有两个参数:一个事件源和一个事件参数。而当事件的使用者订阅该事件时,其本质就是将事件的处理方法加入到委托链之中。事件是一个特殊的委托实例,提供了两个供订阅事件和取消订阅的方法:add_event和remove_event,其本质都是基于委托链来实现。

    事件比委托应用的场景更广

    一个窗体A中定义一个事件,一般都是public、protect,不能私有的,私有,外部不能访问,不能为其注册方法,就是没有任何意义了。
    另外一个窗体B为其给上面一个窗体A中的事件注册方法【方法可以在上面的窗体A中,也是窗体B中,甚至其它地方的方法】,
    窗体B中,可以触发窗体A中的事件{间接触发,之家触发,上面例子就是间接触发。}。

  • 相关阅读:
    【无标题】
    linux部署服务相关基础操作:磁盘挂载、jdk安装、docker安装、docker-compose环境安装、mysql、redis、jenkins等
    永磁体的温度稳定性:剩磁温度系数、矫顽力温度系数、可逆温度系数
    shell脚本适用场景
    易基因|ONT:三代原核甲基化在痤疮杆菌噬菌体表观遗传印迹中的工程选择性研究
    【wxWidgets实现透明wxPanel_核心实现_原创思想】
    TRUNK理论与配置实验
    MySQL - Explain详解
    显示图像2
    推荐系统-模型:Feed 流推荐
  • 原文地址:https://blog.csdn.net/u013400314/article/details/126599928