文章目录
- 委托(Delegate)
- 委托的特性
- 声明委托
- 实例化委托
- 委托的多播(Multicasting of a Delegate)
- 委托的用途
- 匿名方法
- 委托实际应用场景
- 事件(Event)
- 声明事件
- 事件实例1
- 事件实例2
- 事件实例3
- 委托和事件的区别
- 总结
委托(Delegate)
委托是对具有特定参数列表和返回类型的方法的引用。在实例化委托时,你可以将委托实例与方法相关联,然后通过委托实力调用方法,简单的说就是做一个工具,然后把方法传给工具,用工具来调用。就像Java中的事件和JS中的callback方法。
C# 中的委托(Delegate)类似于 C 或 C++ 中函数的指针。委托(Delegate) 是存有对某个方法的引用的一种引用类型变量。引用可在运行时被改变。
委托通常应用于事件和回调函数。所有的委托(Delegate)都派生自 System.Delegate 类。
委托的特性
- 委托类似于指针,但委托面向对象,指针会记住函数,委托则会同时封装对象的实例和方法。
- 委托允许将方法作为参数传递。
- 委托可以做多播,将多个方法链接在一起。还可以进行加减,调用时分别调用多个方法。
- 协变特性,委托允许使用指定返回类的子类作为返回类型。
- 逆变特性,委托允许使用指定参数类型的子类作为委托参数。
- 可以使用Lambda表达式( => )定义委托,类似于ES6新特性的方法定义方式。
声明委托
委托声明决定了可由该委托引用的方法。委托可指向一个与其具有相同标签的方法。
例如,假设有一个委托:public delegate int MyDelegate (string s);
上面的委托可被用于引用任何一个带有一个单一的 string 参数的方法,并返回一个 int 类型变量。
声明委托的语法如下:
delegate <return type> <delegate-name> <parameter list>
实例化委托
一旦声明了委托类型,委托对象必须使用 new 关键字来创建,且与一个特定的方法有关。当创建委托时,传递到 new 语句的参数就像方法调用一样书写,但是不带有参数。例如:
public delegate void printString(string s); ... printString ps1 = new printString(WriteToScreen); printString ps2 = new printString(WriteToFile);
下面的实例演示了委托的声明、实例化和使用,该委托可用于引用带有一个整型参数的方法,并返回一个整型值。
using System; delegate int NumberChanger(int n); namespace DelegateAppl { class TestDelegate { static int num = 10; public static int AddNum(int p) { num += p; return num; } public static int MultNum(int q) { num *= q; return num; } public static int getNum() { return num; } static void Main(string[] args) { // 创建委托实例 NumberChanger nc1 = new NumberChanger(AddNum); NumberChanger nc2 = new NumberChanger(MultNum); // 使用委托对象调用方法 nc1(25); Console.WriteLine("Value of Num: {0}", getNum()); nc2(5); Console.WriteLine("Value of Num: {0}", getNum()); Console.ReadKey(); } } }
执行结果:
Value of Num: 35 Value of Num: 175
委托的多播(Multicasting of a Delegate)
委托对象可使用 “+” 运算符进行合并。一个合并委托调用它所合并的两个委托。只有相同类型的委托可被合并。"-" 运算符可用于从合并的委托中移除组件委托。
使用委托的这个有用的特点,您可以创建一个委托被调用时要调用的方法的调用列表。这被称为委托的 多播(multicasting),也叫组播。下面的程序演示了委托的多播:
using System; delegate int NumberChanger(int n); namespace DelegateAppl { class TestDelegate { static int num = 10; public static int AddNum(int p) { num += p; return num; } public static int MultNum(int q) { num *= q; return num; } public static int getNum() { return num; } static void Main(string[] args) { // 创建委托实例 NumberChanger nc; NumberChanger nc1 = new NumberChanger(AddNum); NumberChanger nc2 = new NumberChanger(MultNum); nc = nc1; nc += nc2; // 调用多播 nc(5); Console.WriteLine("Value of Num: {0}", getNum()); Console.ReadKey(); } } }
执行结果:
Value of Num: 75
委托的用途
下面的实例演示了委托的用法。委托 printString 可用于引用带有一个字符串作为输入的方法,并不返回任何东西。
我们使用这个委托来调用两个方法,第一个把字符串打印到控制台,第二个把字符串打印到文件:
using System; using System.IO; namespace DelegateAppl { class PrintString { static FileStream fs; static StreamWriter sw; // 委托声明 public delegate void printString(string s); // 该方法打印到控制台 public static void WriteToScreen(string str) { Console.WriteLine("The String is: {0}", str); } // 该方法打印到文件 public static void WriteToFile(string s) { fs = new FileStream("c:\\message.txt", FileMode.Append, FileAccess.Write); sw = new StreamWriter(fs); sw.WriteLine(s); sw.Flush(); sw.Close(); fs.Close(); } // 该方法把委托作为参数,并使用它调用方法 public static void sendString(printString ps) { ps("Hello World"); } static void Main(string[] args) { printString ps1 = new printString(WriteToScreen); printString ps2 = new printString(WriteToFile); sendString(ps1); sendString(ps2); Console.ReadKey(); } } }
执行结果:
The String is: Hello World
匿名方法
委托的实例可以用匿名方法代替。
delegate void MyHandler(int xx); // 正常调用方法: MyHandler mh = new MyHandler(xxFunction); // 匿名调用方法: MyHandler mh = delegate(int xx) { // xxxxxxx };
委托实际应用场景
- 回调函数:假如系统中经常需要在执行某项任务后调用一个回调函数,那么就可以考虑使用委托。比如:假如自己实现了一个寻路系统,需要在玩家移动到相应点位后触发任务,就可以使用委托进行方法回调。大部分情况下,回调函数的作用都是进行异步操作,所以当系统中需要进行异步处理的时候,可以考虑用协程结合委托来实现。
- 通用方法,统一调用技能释放。假如一个游戏中有很多角色,每个角色都有自己的一套技能,如果每个英雄都做一套方法来实现技能效果,那代码就太冗余了。这时候可以考虑使用委托结合多态来实现具体调用的选择。
- 操作叠加:因为委托具有+、-的功能,当有一系列操作需要进行时,可以考虑使用委托来进行叠加,根据玩家的操作以及实时数据来决定如何叠加操作(是否扣HP、是否扣MP、是否增加麻痹buff等),这样就能避免为了多种操作或者多种复杂情况去写单独的实现。有点类似于面向切面编程,把操作切片化,然后分层进行处理。
事件(Event)
C#时间与Java有所不同,Java使用接口来实现事件,而C#使用委托来实现。C# 中使用事件机制实现线程间的通信。
事件在类中声明且生成,且通过使用同一个类或其他类中的委托与事件处理程序关联。包含事件的类用于发布事件。这被称为 发布器(publisher) 类。其他接受该事件的类被称为 订阅器(subscriber) 类。事件使用 发布-订阅(publisher-subscriber) 模型。
订阅器:是一个接受事件并提供事件处理程序的对象,在发布器中调用订阅器委托过来的方法。
发布器:是一个包含事件和委托的对象,事件和委托的联系也在这个类中,用于处理事件的触发。
声明事件
在类的内部声明事件,首先必须声明该事件的委托类型。例如:
public delegate void BoilerLogHandler(string status);
然后,声明事件本身,使用 event 关键字:
// 基于上面的委托定义事件 public event BoilerLogHandler BoilerEventLog;
上面的代码定义了一个名为 BoilerLogHandler 的委托和一个名为 BoilerEventLog 的事件,该事件在生成的时候会调用委托。
事件实例1
我想给我的组件添加一些事件监听,可以使用委托+事件来实现,代码如下:
以下是事件类的定义,包含了委托的定义、事件的定义、事件的调用。
using UnityEngine; using UnityEngine.EventSystems; namespace y7play.VR.UGUI.Framework { /// <summary> /// 定义委托 /// </summary> /// <param name="eventData"></param> public delegate void PointerEventHandler(PointerEventData eventData); /// <summary> /// UI事件监听器 /// </summary> public class UIEventListener : MonoBehaviour, IPointerDownHandler, IPointerClickHandler, IPointerUpHandler { /// <summary> /// 声明事件 /// </summary> public event PointerEventHandler PointerClick; public event PointerEventHandler PointerDown; public event PointerEventHandler PointerUp; public void OnPointerClick(PointerEventData eventData) { // 如果PointerClick不为空,就调用PointerClick方法 PointerClick?.Invoke(eventData); } public void OnPointerDown(PointerEventData eventData) { // 如果PointerDown不为空,就调用PointerDown方法 PointerDown?.Invoke(eventData); } public void OnPointerUp(PointerEventData eventData) { // 如果PointerUp不为空,就调用PointerUp方法 PointerUp?.Invoke(eventData); } } }
以下是在实际使用这个组件时注册和使用事件的方法。
using UnityEngine.EventSystems; using y7play.Common; using y7play.VR.UGUI.Framework; namespace y7play.VR.UGUI { /// <summary> /// 游戏主窗口 /// </summary> public class UIMainWindow : UIWindow { private void Start() { // 给开始游戏按钮添加事件 transform.FindChildByName("ButtonGameStart").GetComponent<UIEventListener>().PointerClick += OnPointerClick; } private void OnPointerClick(PointerEventData eventData) { // 调用游戏开始方法 GameController.Instance.GameStart(); } } }
事件实例2
using System; namespace SimpleEvent { 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 MainClass { 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 ); } } }
执行结果:
event not fire event fire event fire
本实例提供一个简单的用于热水锅炉系统故障排除的应用程序。当维修工程师检查锅炉时,锅炉的温度和压力会随着维修工程师的备注自动记录到日志文件中。
事件实例3
using System; using System.IO; namespace BoilerEventAppl { // 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("e:\\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 }
执行结果:
Logging info: Temperature 100 Pressure 12 Message: O. K
委托和事件的区别
从面向对象的角度来讲,委托和事件本来就不是同一种东西,委托是数据类型,而事件则是对某个对象的描述(也可以理解为对委托对象的封装),下面看一下区别:
从代码使用层面来讲,委托和事件最大的区别就是委托可以用“=、+=、-=”赋值,而事件则只能用“+=和-=”赋值,这样就使事件有了不会被轻易干扰的特性,避免自己写的事件被别人的一个“=”覆盖掉导致程序bug。
总结
- 委托的作用:占位,在不知道将来要执行的方法的具体代码时,可以先用一个委托变量来代替方法调用(委托的返回值,参数列表要确定)。在实际调用之前,需要为委托赋值,否则为null。
- 事件的作用:事件的作用与委托变量一样,只是功能上比委托变量有更多的限制。(比如:1.只能通过+=或-=来绑定方法(事件处理程序)2.只能在类内部调用(触发)事件。)
- 自定义控件的时候,通常需要编写一些事件。然而,确定具体执行哪些事件处理程序是编写控件的人无法确定的。这时只能通过事件来占位(调用),具体调用哪个方法由使用控件的人来决定,例如:Click += new 委托(方法名)。
标签:委托,C#,void,int,匿名,事件,new,解析,public From: https://www.cnblogs.com/hxjcore/p/17873242.html