事件的本质是【委托类型字段(Field-Like)】包装器,用来保护委托字段不被滥用,LAMBDA表达式的基础也是委托,LAMBDA表达式同时也是LINQ1的基础,只有理解了委托的一般使用情况,才能熟练地把一个LAMBDA表达式当做一个实际参数传递到方法中,
C#语言共有五种主要数据类型,而这五种类型又分为引用类型和值类型,引用类型有类、接口、委托,值类型包含结构体类型(struct、Vector2)和枚举类型(enum),即委托是一种类。
·
委托能够存储一个或多个方法的引用。简单来说就是当要用到一个或多个方法时,不需要直接调用,而是赋值给委托类型的变量,这个委托类型的变量将会存储、封装这些方法,然后由委托类型的变量通过Invoke或其它更加简便的调用形式来间接调用这些方法。
举个例子,当一个游戏结束后会提升主角的经验值以及奖励物品,还会出现游戏结束的UI和动画,而这些功能分别写在不同的方法之中,然后在GameManager方法之中来调用。
若使用委托,我们可以将要调用的方法赋值给委托类型的变量,然后再通过委托类型的变量调用这些方法。
using UnityEngine; public class GameManager : MonoBehaviour { public delegate void MyDelegate(); MyDelegate myDelegate; private void Enable() { myDelegate += FindObjectOfType<MainPlayer>().GetBonus(); myDelegate += FindObjectOfType<MainPlayer>().GetExp(); myDelegate += FindObjectOfType<UIManager>().ShowWinCanvas(); myDelegate += FindObjectOfType<AnimationManager>().AnimationPlay(); } public void OnGameOver() { //使用委托调用 myDelegate(); /*直接调用*/ /*FindObjectOfType<MainPlayer>().GetBonus(); FindObjectOfType<MainPlayer>().GetExp(); FindObjectOfType<UIManager>().ShowWinCanvas(); FindObjectOfType<AnimationManager>().AnimationPlay();*/ } }
using UnityEngine; public class MainPlayer : MonoBehaviour { //奖励经验值和物品 public int exp, bunus; public int expValue, bonusValue; public void GetExp() { exp += expValue; } public void GetBonus() { bonusValue += bonusValue; } }
using UnityEngine; public class UIManager : MonoBehaviour { // 游戏胜利的画面 public GameObject winPanel; public void ShowWinCanvas() { winPanel.SetActive(true); } }
using UnityEngine; using UnityEngine.UI; public class AnimationManager : MonoBehaviour { //游戏胜利的动画 public Animator winAnim; public void AnimationPlay() { winAnim.Play(); } }
一个事件,看上去大多像一个委托类型的字段,但它只是一个委托类型字段的包装器、限制器,用来限制外界对这个委托类型字段的访问。
而委托类型的字段在通过事件包装、限制之后,外界只能访问它的+=、-=操作,只能添加、移除事件处理器
类可以声明变量,可以创建实例(实例即对象,是类经过实例化之后得到的内存中的实体),即委托也可以,但委托的声明和一般的类不同,更像一般的方法的声明,与C语言函数指针相似,如下:
//声明委托类型 public delegate void MyDelegate();//这个委托类型可以指向任何一个返回值为空、参数列表为空的方法
C语言函数指针
//C语言声明一个函数指针 tepdef int(*Calculator)(int _x,int _y); /*C#委托的声明*/ public delegate void MyDelegate01(int _a,int _b); private delegate int MyDelegate02(int _x,int _y);//这个委托指向一个返回值为int类型,参数列表为两个int类型的方法 private delegate double MyDelegate03(double _a); int Add(int _a,int _b){ return _a + _b; } int Multiply(int _a,int _b){ return _a *_b; } int mian(){ int x=9; int y=11; /*直接调用*/ printf("Add Result : %d\n",Add(x,y)); printf("Multyply Result : %d\n",Multiply(x,y)); /*通过指针调用*/ Caculator pointer01 = &Add; Caculator pointer02 = &Multiply; printf("Add Result (pointer01): %d\n",pointer01(x,y)); printf("Multyply Result (pointer02): %d\n",pointer02(x,y)); return 0; }
一个委托内部封装了多个方法,例如本文刚开始的例子,便是多播委托
using UnityEngine; public class GameManager : MonoBehaviour { public delegate void MyDelegate(); MyDelegate myDelegate; private void Enable() { myDelegate += FindObjectOfType<MainPlayer>().GetBonus(); myDelegate += FindObjectOfType<MainPlayer>().GetExp(); myDelegate += FindObjectOfType<UIManager>().ShowWinCanvas(); myDelegate += FindObjectOfType<AnimationManager>().AnimationPlay(); } public void OnGameOver() { myDelegate(); } }
但是每个方法的返回值类型都要与委托类型的返回值类型兼容,比如我们声明了一个void的委托类型public delegate void MyDelegate(),要调用的方法如下:
private string Log() { return DateTime.UtcNow.ToString(); }
由于Log()方法的返回值类型是string,与void类型不兼容,所以不能调用Log()方法
容易导致内存泄漏
当委托调用的是实例方法(静态方法)时,这个被调用的方法必须存在于内存之中,即便没有其他引用变量引用该方法,这个方法的内存也不能被释放,一旦释放,委托便不能再间接调用对象的方法了,可能导致内存泄漏,随着泄漏的内存越来越多,程序性能会下降,直至程序崩溃,这些缺点(将+=写成=)是事件、观察者模式出现的原因
C#类库中有两个定义好的委托【Action委托】和【Func委托】,可以用来代替delegate关键字自定义委托的类型。
//delegate关键字定义委托 public delegate void MyDelegate01(); public delegate string MyDelegate02(); public delegate double MyDelegate03(double _numA,double _numB); MyDelegate01 myDelegate01; MyDelegate02 myDelegate02; MyDelegate03 myDelegate03; private void Start() { myDelegate01+=new MyDelegate01(Teleport); myDelegate02+=new MyDelegate02(Log); myDelegate03+=new MyDelegate(Add); } private void Update() { myDelegate01.Invoke(); print(myDelegate02()); print(myDelegate03(2.2, 7.89f)); } private void Teleport() { Vector3 currentPos = transform.position; currentPos.x = UnityEngine.Random.Range(-5f, 5f); transform.position = currentPos; } private string Log() { return System.DateTime.UtcNow.ToString(); } private double Add(Double _num1,double _num2) { return _num1+_num2; }
//不使用自定义委托 Action action01; Func<string> func01; Func<double,double,double> func02; private void OnEnable() { action01=new Action(Teleport); func01=new Func<string>(Log); func02=new Func<double,double,double>(Add); } private void Start() { action01(); func01(); func01(2.2, 7.89f); } private void Teleport() { Vector3 currentPos = transform.position; currentPos.x = UnityEngine.Random.Range(-5f, 5f); transform.position = currentPos; } private string Log() { return System.DateTime.UtcNow.ToString(); } private double Add(Double _num1,double _num2) { return _num1+_num2; }
LINQ:.Net的“Language Integrated Quary”,较常用的有:Where/Select,都是以LamBDA形式显示在代码中 ↩︎