委托

委托介绍

  • 委托是函数的容器,可以理解为表示函数的变量类型,可以用来储存,传递函数。
  • 委托的本质是一个类,用来定义函数的类型,不同的函数必须对应各自格式(参数和返回值)一致的委托。
  • 委托(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 () {
        //一般情况
        monkeyDel = () => Debug.Log("NORMAL");
        monkeyDel += () => { Debug.Log("NORMALFUNC"); };
        monkeyDel();//调用委托链
        
        //使用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;   //有参有返回值 前面是参数 后面是返回值