委托
委托介绍
- 委托是函数的容器,可以理解为表示函数的变量类型,可以用来储存,传递函数。
- 委托的本质是一个类,用来定义函数的类型,不同的函数必须对应各自格式(参数和返回值)一致的委托。
- 委托(Delegate)特别用于实现事件和回调方法。
- 所有的委托(Delegate)都派生自 System.Delegate 类。
- 事件、匿名方法、Lambda表达式都建立在委托的基础上。
- 委托可以将方法当作方法的参数来传递。
委托语法
//访问修饰符 delegate 返回值类型 委托名称(参数列表);
public delegate void DelegateName(int a, int b);
- 委托申明的关键字是delegate
- 声明的位置可以在类外,可以在类内
- 定义在类外,同命名空间内其他的类也可以使用这个委托
- 定义在类内,则是作为类的成员存在,只能在当前类中访问
- 委托的声明要与其绑定的方法的返回值、参列表类型一致(相同签名:相同返回值、参数列表一致的方法)
eg:
使用委托让函数参数列表中能够使用方法
//委托的声明,需要与绑定的方法签名相同
public delegate void CalcDelegate(int numA, int numB);
//使用委托的函数
private void MyCalc(int a, int b, CalcDelegate calcDel)//CalcDelegate calcDel是委托变量
{
calcDel(a, b);
}
//委托绑定的方法
private void JiaFa(int a, int b)
{
Debug.Log(string.Format("{0} + {1} = {2}", a, b, a + b));
}
多播委托
- 以委托为基础,扩展出多播委托,多播委托是一个“委托链”,可以一次传递(绑定)多个方法。
- 委托变量和普通的变量有一点不同,普通变量只能存放一个数据,而委托变量是一个“委托链”,里面可以存放多个数据。
eg:
static private void Baidu(){}
static private void Taobao(){}
static private void YouKu(){}
public delegate void WebInfoDelegate();//委托声明
WebInfoDelegate webInfoDel;//创建委托变量
webInfoDel = Baidu;//委托变量赋值,第一次使用“=”号;
webInfoDel += Taobao;//第二次开始就要使用“+=”进行添加;
webInfoDel += YouKu;
webInfoDel -= Taobao;//可以使用“-=”把委托链中的某个方法移除;
webInfoDel();//调用这个委托变量,执行的时候,是按赋值的先后顺序执行的;
//执行的时候,会把添加到该委托链中的方法全部依次执行。
事件
- 委托变量可以使用的符号有“=”,“+=”,“-=”;这三个符号中“=”是比较危险的。
- (在类的对象外面访问委托,可以直接用+=)
- 因为使用了“=”赋值,之前委托变量中存在的方法,就会被重置。
- 之前类中字段数据直接公开不安全,出现了属性通过set进行安全性校验;
- 由于委托不安全,就出现了事件。
- 在声明的委托变量的类型前面加 event 关键字,这个委托就被事件修饰包装了,这个委托变量就称为事件,事件是委托的安全包裹。
- 事件不能在类的外部被赋值和调用,这也是他与委托的区别
- 也就是说,在外部访问事件只能使用“+=”,“-=”两个符号(不能用“=”号赋值),更为安全。
- https://www.cnblogs.com/kissazi2/p/3189685.html
- 注意:事件只能作为成员存在于类和接口及其结构体中。
- 之所以要有事件,是为了防止外部随意置空和调用委托,同时相当于对委托进行了一次封装,让其更加安全。
eg:
//委托的声明
public delegate void CalcDelegate(int numA, int numB);
//在委托变量加入event,这个委托变量就是事件
public event void CalcDelegate;
观察者模式
- 观察者模式【Observer】定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。
- 这个主题对象在状态发生改变时,会通知所有观察者对象,使他们能够自动更新自己。
eg:一个频道,有若干个观察者(订阅者),频道更新时,观察者都收到信息
//观察者类,在每个观察者下挂在此脚本
public class NetUser : MonoBehaviour
{
void Start ()
{
YouKuZHJ.Instance.zhjDel += Show;//订阅操作,将该对象加入观察者列表中,之后调用事件时会调用该类的Show函数
}
private void Show(string info)
{
Debug.Log(gameObject.name + "收到新消息:" + info);
}
}
//被观察者类,被称之为主题【subject】,或者被称为发布者
public delegate void ZHJDelegate(string info);
public class YouKuZHJ : MonoBehaviour
{
public static YouKuZHJ Instance;//被观察对象是一个单例模式
private int index = 1;//当前剧集.
public event ZHJDelegate zhjDel;//定义事件.
void Awake()
{
Instance = this;//实现单例
}
void Update()
{
if(Input.GetKeyDown(KeyCode.Space))
{
index++;
zhjDel("更新到第" + index + "集");//调用这个事件
}
}
}
匿名方法
- 在开发过程中,有些方法非常的简单,代码量不大且只会偶尔使用一次。
- 这类方法如果单独的编写定义成一个方法,就比较麻烦且意义不大。
- 因此,匿名方法应运而生。
eg:将上面的例子改写为匿名方法
void Start ()
{
YouKuZHJ.Instance.zhjDel += delegate(string info)
{
Debug.Log(gameObject.name + "收到新消息:" + info);
};
}
Lambda表达式
- Lambda 表达式,是一种代码的语法格式。
- Lambda 表达式是对匿名方法语法格式的进一步简化。
- 普通方法 、匿名方法、 Lambda 表达式分别是方法的三种表达方式,越往后越抽象。
语法:
委托类型 变量 = ([参数列表])=> { 方法体; };
- => 是 Lambda 表达式特有符号,读音是:goes to
- 如果没有参数列表,()也不能省略,如:()=>
- 如果参数列表中只有一个参数,()可以省略
- 如果方法体内只有一句代码,可以省略代码块符号,也就是不用写{}
eg:将上面的例子改写为Lambda表达式,以及省略方法的写法
void Start ()
{
YouKuZHJ.Instance.zhjDel += (string info) =>
{
Debug.Log(gameObject.name + "收到新消息:" + info);
};
//如果参数列表中只有一个参数,()可以省略,类型可以省略
YouKuZHJ.Instance.zhjDel += info =>
{
Debug.Log(gameObject.name + "收到新消息:" + info);
};
//如果方法体内只有一句代码,可以省略代码块符号,也就是不用写{}
YouKuZHJ.Instance.zhjDel += info => Debug.Log(gameObject.name + "收到新消息:" + info);
}
eg:List中的FindAll方法
- FindAll方法的参数是委托类型的public delegate bool Predicate
(T obj); - 可以用下面三种方法(普通方法、匿名方法、Lambda表达式)书写
//Lambda表达式,name定义了要搜索元素应满足的条件
List<string> tempName = names.FindAll(name => name.StartsWith("李"));
List<string> tempName = names.FindAll((string name) => name.StartsWith("李"));//由于只有一个参数,string可舍弃
//匿名方法
List<string> tempName = names.FindAll(delegate(string str) { return str.StartsWith("李"); });
//常规方法
bool find(string str)
{
return str.StartsWith("李");
}
List<string> tempName = names.FindAll(find);
//因为更加简单快捷,我们一般直接回使用Lambda表达式方法。
内置委托类型
注意:需要手动引入using System;命名空间
Action
- Action 是一个无参无返回值的内置委托类型。
- 当我们需要用到无参无返回值的委托的时候,我们可以直接使用该类型声明委托变量。
- 相当于少写一句public delegate void xxxDelegate(); 直接用private Action nameDel;就可以直接声明委托变量。
- Action同时提供private Action< T >,但是该委托定义了 4 种重载形式。
- 我们可以使用 Action< T >定义 1~4 个参数且无返回值类型的委托变量。
eg:
using UnityEngine;
using System;//需要提前引入的命名空间
public delegate void NormalDelegate();//一般情况:我们如果需要使用无参无返回值委托时,需要自己定义
public class ActionDemo : MonoBehaviour
{
private NormalDelegate normalDel;//一般情况
private Action nameDel;//引入命名空间后,可以直接使用Action来代替自己写
private Action<int, int> myCalc;
void Start () {
//一般情况
normalDel = () => Debug.Log("NORMAL");
normalDel += () => { Debug.Log("NORMALFUNC"); };
normalDel();//调用委托链
//使用Action委托
nameDel = () => { Debug.Log("Action"); };
nameDel();
//使用Action<T>委托,可以定义 1~4 个参数且无返回值类型的委托变量
myCalc = (int a, int b) => Debug.Log(string.Format("{0} + {1} = {2}", a, b, a + b));
myCalc += (a, b) => Debug.Log(string.Format("{0} * {1} = {2}", a, b, a * b));
myCalc(2, 5);
}
}
Func
- Func< T >内置委托包含:无参有返回值,有参(1~4 个)有返回值;
- Func< T >和 Action 对比:Action 都是无返回值的,Func 都是有返回值的。
eg:
public Func<int> funcDel;
public Func<int, int, int> funcCalc;//前两个对应参数,最后一个对应返回值
void Start ()
{
funcDel = () => { return 550; };//无参有返回值,返回数字
funcDel = () => 666;//无参有返回值,可以省略return和{}
funcDel = () =>
{
Debug.Log("大家晚上好");
return 1000;
};
int temp = funcDel();//输出
Debug.Log(temp);
funcCalc = (int a, int b) => { return a + b; };//有参有返回值
Debug.Log(funcCalc(10, 5));
}
Func和Action的对比
Action action1; //无参无返回值
Action<int> action2; //有参无返回值
Func<int> funcint; //无参有返回值
Func<int, int> funcintint; //有参有返回值 前面是参数 后面是返回值