1.向SendMessage和BroadcastMessage说再见
首先我们知道小标题所说的两个方法的作用是通过参数名来进行方法的调用,但是这两个函数过于依赖反射机制来查找消息对应的被调用的函数,频繁的使用反射机制会影响性能能,更加严重的问题是,使用这种机制后代码的维护成本太高,加入在一个开发团队中如果某一个方法被重新命名甚至被删除,会导致很严重的隐患,并且在编译器严重,他是没有错误的,因此也不会通过一些错误的提示来告诉开发者某些方法不存在,另外在一个团队的开发中,方法的命名如果一样呢,这也是个很大的隐患,而且所述的这几种隐患在平常都是不会发生的,只有当执行特定的功能的时候,发现找不到或者识别不了是那个函数的时候才会发现,但是这个时候已经太迟了。
2.认识回调函数—委托
先上代码:
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Blog_test : MonoBehaviour
{
// Start is called before the first frame update
public delegate void MyDelegate(int number);
MyDelegate myDelegate;
void Start()
{
myDelegate += printNumber;
myDelegate += doubleNumber;
myDelegate(3);
}
private void doubleNumber(int number)
{
Debug.Log("doubleNumber number" + 2 * number);
}
private void printNumber(int number)
{
Debug.Log("printNumber number:" + number);
}
// Update is called once per frame
void Update()
{
}
}
如果和使用委托?
1.声明委托类型,其实例和引用的方法
2.注册委托的方法
3.方法内的逻辑
注意,上述代码的委托类型是空,当然也可以是任意类型,委托要确定一个回调方法签名,参数以及返回类型等,在上述例子中回调方法的参数类型是int,返回类型是void
将此脚本挂载到u3d上的物体上,运行游戏会直接输出
通过这样的机制,在Start函数中绑定几个回调函数,在某个触发事件函数中,通过调用myDelegare(参数)的方式通知已经注册委托的回调函数,令其执行相应的程序,比如说,在某个游戏场景中英雄损失血量,这个时候,如果要多个功能一起实现,如,UI血量损失的变化、英雄被击打的动画播放如果采用委托机制,这些功能可以互不干扰的执行,体验会更好一点。
当然涉及到委托的还有它的另一个特点-委托参数的逆变性,和委托返回类型的协变性
逆变性:方法获取的参数类型可以是委托参数的类型的基类
协变性:方法的返回类型可以是从委托的返回类型派生的一个派生类,例如在我们的项目中存在基础单位类(BaseUnitCalss)、士兵类(SoldierClass)以及英雄类(HeroClass),其中基础类作为积累派生出士兵类和英雄类,那么可以这样定义一个委托
public delegate object TellMeYouName(SolderClass solder);
TellMeYouName TellMeYou;
void Start()
{
TellMeYou += TellMeYourNameMethod;
}
private string TellMeYourNameMethod(BaseUnitClass baser)
{
throw new NotImplementedException();
}
由于TellMeYourNameMethod的返回值类型是String,是派生于TellMeYourName委托的返回值Object的,因此这种返回类型的协变性也是可以的,参数也是一样,一个是基类,一个是派生类,这样的协变性也是可以的
注意:
协变性和逆变性都是只支持引用类型的,所以如果值类型或者void是不可以这样做的,例如将
string TellMeYourNameMethod(BaseUnitClass baser)
{
throw new NotImplementedException();
}
修改成
int TellMeYourNameMethod(BaseUnitClass baser)
{
throw new NotImplementedException();
}
虽然两者都派生出obect但是int是值类型所以编译器报错,至于什么类型是值类型,什么类型是引用类型可以百度
3.委托是如何实现的
internal delegate void MyDelegate(int number);
MyDelegate myDelegate = new MyDelegate(myMethon1);
myDelegate = myMethon2;
myDelegate(2);
从表面上看,委托似乎很简单,使用new构造MyDelegate委托实例,通过构造函数创建的实例此时引用的方法是myMethod1,然后通过方法组替换为myMethod2,最后调用回调函数,看上去真的很简单 。
实际上编译器为MyDelegate类定义了四个方法,一个构造器、Invoke、BeginInvoke、EndInvoke。而MyDelegate类本省又派生字基础类库种定义的System.MulticastDelegate类,所以可以推断出所有的委托类型都派生自System.MulticastDelegate,但是这个类又派生于类外一个类——System.Delegate,所以我们在使用委托的时候可能会用到Delegate种的方法,例如Combine、Remove(现在被+=、-=重载)。
上文提到所有的委托都派生于MulticastDelege,所以自然会继承其属性、字段、方法,对于委托而言,有三个字段是很重要i的
字段 | 作用 |
---|---|
_targe | 保存回调方法要操作的对象 |
_methodPtr | 内部的整数值,用来标识回调方法 |
– | – |
_invocationList | 如果回调函数不值一个(构造委托链),他会引用一个数组,将回调函数的引用加入数组,当委托调用回调函数的时候,依次调用数组种的回调函数 |
需要注意的一点,所有委托都有一个获取两个参数的构造方法,这两个方法分别是对象的引用和IntPro类型的用来引用回调函数的句柄
public MyDelegate(object @object,IntPtr method)
所以在注册回调函数的时候,c#编译器会分析代码,来确定引用的是那个放发和那个对象,然后将对象的引用传递给object参数,而方法的引用传递给method参数,当这两个参数传递给构造函数的时候,会被_target和_methodPtr这两个私有字段保存,用来唯一标识要回调的函数。
4.委托是如何调用多个回调函数的
上文中有一个字段_invocationList,起作用是如果注册了多个回调函数,每个回调函数都会有自己的_targe、_methodPtr,
myDelegate += printNumber;
myDelegate += doubleNumber;
myDelegate(2);
第一行代码种,此时myDelegate为NULL,执行完第一行代码,会创建一个委托实例,调用Delegate.Combine返回myDelegate的值,因此第一行执行结束后,myDelegate引用printNumber;
执行第二行的时候,继续创建一个不同于printNumber委托实例,调用Delegate.Combine,此时_targe、_methodPtr依旧是专属标识和引用,但是_invocationList被初始化为一个委托实力数组的引用。
执行myDelegate(2)的时候,由于会饮食的执行Invoke方法,当Invoke发现_invocationList不再是null而是引用了一个委托实例的数组,他会执行一个循环,按照顺序调用每个委托实例.
参考教材unity3d脚本编程