笔记【委托•语法篇】委托类型的声明和实例以及Action委托和Func委托

B站视频学习笔记 UP:BeaverJoe
【委托•语法篇】委托类型的声明和实例以及Action委托和Func委托
【笔记最后没有做好,有时间再去完善】

03:15 现实世界:什么是委托
08:43 程序世界:什么是委托
13:04 委托为何如此重要
14:45 自定义委托的声明
19:07 C语言函数指针
31:48 创建委托类型的实例与目标方法
38:09 多播委托
39:48 委托的缺点
47:48 Action委托和Func委托
49:32 下期预告
50:30 特别鸣谢

小JOE在外面准备回来,在他们宿舍群发了一条消息 ,说“今天饭很好吃,现在准备回宿舍了”
A舍友看到 我要回来 的消息之后,委托小Joe给他带一份回锅肉盖浇饭。
B舍友看到之后,委托小Joe给他带一份炸鸡。

委托小Joe,去完成 他们各自应该去完成的事情

这个
事件 就是委托字段的包装器,
(事件 看起来像委托类型字段,字段即变量,事件的本质还是event,不是委托字段,小Joe说之后详谈)


切记:一个事件,不管看上去多像一个委托类型的字段,他也不是委托类型的字段,它只是一个委托类型字段的包装器、限制器。限制外界对这个委托字段的访问。
委托类型的字段,通过事件——包装、限制之后,外界只能访问它的 +=、-=操作,只能添加、移除事件处理器。

委托什么,需要的时候就用这个委托,执行相应的事情,委托之外的,概不负责。

我们替舍友呢,去取快递 去带饭 还有呢
就是通过 委托类型的变量 去调用委托类型变量 所存储的各种方法,
(这些方法不需要直接调用)

或者呢 我们可以叫
通过委托类型的变量 去调用那些 封装在委托内部的方法。

C#语言有五大数据类型
而数据类型可以分为:值类型和引用类型

引用类型(Reference Type)包含了:类,接口,委托
值类型(Value Type):结构体,枚举

结构体类型:比如 Struct,Vector2
枚举类型:enum

委托 是一种 类,引用类型的数据结构。

如何[证明]委托是一种类呢,我们可以直接在编译器中 通过typeof操作符来查看一下, 自定义声明的委托[MyDelegate],和C#提供给我们的[Action委托]和[Func委托]

  public delegate void MyDelegate();
        Action action;
        Func<string> func;

        private void Start()
        {
            Console.WriteLine("MyDelegate Is Class Or Not:" + typeof(MyDelegate).IsClass);
            Console.WriteLine("Action Is Class Or Not:" + typeof(action).IsClass);
            Console.WriteLine("Func Is Class Or Not:" + typeof(func).IsClass);

        }

委托的全称是委托类型,他是一种类型。

微软中的 委托的定义: 可以指向 一个或者多个方法,
这里获取的第二个信息:委托类型 创建的实例,可以存储 一个或者多个方法的引用。
直接通过委托的实例 可以【间接调用】 (Invoke或者是Call)调用这些方法。

当然,并不是什么样的方法都可以赋值到委托类型的变量中,
让委托类型的变量来调用。
其中隐含着[类型兼容]的特点。
(这个大概知道了,所以笔记不写了。) (很尴尬的事情是,又忘记了。。。)

事件的基本 是委托,而Lambda表达式的基础 也是委托,而lambda表达式又是LINQ的基础,
(LINQ: .Net的“Language Integrated Query” 较常用的有:Where/Select
都是以Lambda形式 显示在代码中)。

所以 学好委托,才能理解 事件的本质是 【委托类型字段】的包装器
(只是像字段一样的声明格式,但是不是一个字段,核心是前面的关键字event, )

自定义委托的声明

类可以声明变量
public string PlayerName;

类可以创建实例

比如说 我们声明了一个Player类型的变量:player
之后,通过new关键字 创建Player的一个实例。

当我们拿[引用类型]声明变量后,我们默认它就是一个Null值,
或者 我们可以给它一个Null值,说明这个变量并没有引用任何实例。

或者创建一个委托类型的实例,让这个变量 引用这个实例。

委托是一种类,是引用类型的数据类型。
那么委托和类一样 也可以声明变量、创建委托类型的实例,
实例,也叫做对象,是类经过实例化之后 得到的内存中的实体。

委托虽然是一种类、引用类型,但是它的声明方式和 一般的类 不同。
(主要原因是为了照顾可读性 和C、C++传统)。 反而和一般的方法的声明格式 非常相近。

类的声明的三个要素,
class关键字 类名 {...}花括号内的类体

public delegate void MyDelegate();
delegate关键字,void是MyDelegate中 【目标方法】的返回值类型,
目标方法 是委托可以指向的 这个方法。
或者说 我们委托可以存储的这个方法的引用,存储的这个方法 叫做 目标方法。
叫做目标方法 是因为在编译器中可以观察到target

这个目标方法究竟是什么呢,(委托变量)可以指向什么样的方法呢?
我们之后可以自定义任何一个【返回值为空】【参数列表为空】的方法。

比如:
private void MyMethod()
{
}

C语言函数指针

为什么委托 它是引用类型,它是一种类,但是它的声明格式,不像类反而像方法呢。

  这个问题的答案,简单来说就是

  委托的声明 保持了【函数指针】声明类似的格式

  它具有目标方法的【返回值类型】和【参数列表类型】
  委托的声明格式 它是仿造C/C++ [函数指针]的声明格式,

函数指针的声明格式是什么样的

int Add(int _a,int _b)
{return _a+_b;
}

通过[函数的名字]来调用这两个函数,
 我们称之为 直接调用

 什么是间接调用呢?
我们会把通过【C++函数指针】来调用这个函数呢,称之为 间接调用
(C#中 就是委托类型变量调用的形式,叫做间接调用)。


#include <iostream>

//(* Calculator) ()        //Calculator 就是这个函数指针类型的名字,可以自定义
// 这种类型的【函数指针】能够指向什么样的函数呢
//我们希望 它所指向的函数 有[两个整数类型的参数] (Add(),Multiply() 两个函数有两个参数)
//(*Calculator)(int _x,int _y)

//并且 能够返回一个 int 整数类型的值  (因为 Add和Multiply 的返回值就是int)
//int (*Calculator)(int _x, int _y);
//这样呢 对[函数指针]的定义\声明,就算基本完成了。

//并且可以用 [typedef关键字] 定义成一种 数据类型, 
//这样我们就拥有了一种 函数指针 数据类型
typedef int (*Calculator)(int _x, int _y);   //这就是函数指针,C语言的声明格式 (我还以为是C++)



int Add(int _a, int _b)
{
    return _a + _b;
}

int Multiply(int _a,int _b)
{
    return _a * _b;
}

int main()
{
    int x = 9;
    int  y = 11;
    printf("Hello World\n");


    //声明函数指针的变量

    //函数指针类型 Calculator  变量名 pointer01
    Calculator  pointer01 = &Add;
    Calculator pointer02 = &Multiply;
    
    //我们现在不再[直接调用]add或者Multiply这两个函数,
    printf("间接调用的 Add Result(pointer01):%d\n", pointer01(x, y));  //间接调用
    printf("间接调用的  Multiply Result (pointer02):%d\n", pointer02(x, y));

    printf("直接调用的 Add Result:%d\n", Add(x, y)); //直接调用
    printf("直接调用的 Multiply Result(pointer02):%d\n", pointer02(x, y));

    std::cout << "Hello World!\n";
    return 0;
}

// C语言:声明一个函数指针;
typedef int (*Calculator)(int _x, int _y);

//C#:委托的声明
public delegate void MyDelagate01(int _a, int _b);
private delegate int MyDelegate02(int _x, int _y);
private delegate double MyDelegate03(double _a);

C#通过委托 这一数据类型,保留了与 函数指针 相对应的这部分内容
(java则毫无保留,通过接口来代替的函数指针)

delegate关键字 对应了 C语言中的*号、指针定义符。

创建委托类型的实例与目标方法

private void Log()
{
Debug.Log("Current Time is:" + System.DateTime.UtcNow);
}

System.DateTime.UtcNow 世界时间、协调世界时间(英国时区)
数据库相关的话,使用这个更好,

System.DetaTime.Now 计算机当地时间

using System;
Action,Func委托在这个名称空间下方。

    private SpriteRenderer sp;

    void Start()
    {
        sp = GetComponent<SpriteRenderer>();   //获取组件
    }

OnEnable方法 会在Awake方法之后,Start方法之前 进行调用,

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;

public delegate void MyDelegate(); //声明委托    void 返回值类型,()参数模式

public class DelegateEx01 : MonoBehaviour
{
    private SpriteRenderer sp;

    MyDelegate myDelegate;  //委托类型:MyDelegate  变量名:myDelegate

    private void OnEnable()
    {
        myDelegate = new MyDelegate(Teleport); // 把方法的引用:Teleport 【赋值】myDelegate这个委托类型的变量中
        myDelegate = Teleport;
    }

    void Start()
    {
        sp = GetComponent<SpriteRenderer>();
    }

    // Update is called once per frame
    void Update()
    {
        if (Input.GetKeyDown(KeyCode.Space))
        {
            //Teleport();  //直接调用
            //ChangeColor();
            //Log();

            //myDelegate.Invoke();
            myDelegate();
        }
    }


    //随机更换位置
    private void Teleport()
    {
        Vector2 currentPos = transform.position;
        currentPos.x = UnityEngine.Random.Range(-5f,5f);
        transform.position = currentPos;
    }

    //随机更换颜色
    private void ChangeColor()
    {
        sp.color = new Color(UnityEngine.Random.value, UnityEngine.Random.value, UnityEngine.Random.value);
    }

    private void Log()
    {
        Debug.Log("Current Time is:" + System.DateTime.UtcNow);
    }

}

↑ 这里通过委托 调用了Teleport方法

我们还需不需要另外创建一个委托 存储剩下的方法?
  当然Duck不比了。
我们既然声明了委托类型, 这个委托类型可以指向任何方法,
【只要这个方法的 [参数类型]和[返回值类型],
与 我们声明的这个[委托类型] 保持绝对的类型兼容】

    private void OnEnable()
    {
        myDelegate = new MyDelegate(Teleport); // 把方法的引用:Teleport 【赋值】myDelegate这个委托类型的变量中
        myDelegate = Teleport;   
    }

`
private void OnEnable()
{
myDelegate = new MyDelegate(Teleport); // 把方法的引用:Teleport 【赋值】myDelegate这个委托类型的变量中
//myDelegate = Teleport; //这样写也可以哦,熟悉了委托之后。
//myDelegate = new MyDelegate(ChangeColor); //单个等号是赋值,这样会覆盖上次的数值即覆盖掉Teleport

    //多播委托
    myDelegate += new MyDelegate(ChangeColor);
    myDelegate += new MyDelegate(Log);

}

`
现在,委托类型变量就能【间接的调用】封装在委托内部的两个方法了。

其实在将来的很多时候,我们并不会这么去使用委托,原因在于,委托会引用一个方法,
而这个方法呢是实例方法的话(非静态方法),
实例方法 也就是说 它是隶属于一个对象,
实例的方法、不属于static方法。
(静态方法 static
静态方法是不属于特定对象的方法;
 静态方法可以访问静态成员;
静态方法不可以直接访问实例成员,可以在实例函数调用的情况下,实例成员做为参数传给静态方法;
 静态方法也不能直接调用实例方法,可以间接调用,首先要创建一个类的实例,然后通过这一特定对象来调用静态方法。)

如果我们拿一个委托 引用这个方法的话,
那么【这个对象】它就必须存在内存当中,
 即便 没有 其他引用变量,去引用这个对象,
这个对象的内存呢,也不能够得以释放。
  因为一旦释放,委托就不再能够 间接调用对象的方法了。
所以说 委托可能导致「内存泄露」 (章章其实并没有十分明白,对象,引用,内存 之间有什么关系嘛? 简单搜了一下,发现自己对 C#内存相关的一些东西并不是很熟悉,等待其他博文中的补充更新 2022年7月20日)




![](https://img2022.cnblogs.com/blog/2178516/202207/2178516-20220704111415485-358374730.png)

本期内容 小joe没有提到 委托的使用, 都是在介绍委托的知识点和语法的声明,和对知识点的扎深概念。

委托的一般使用情况,小jio放到了下期视频中
笔记链接
  委托当然有用咯,它是一个「引用类型」,
我们把委托当成一个方法的参数,传递到别的方法中,
间接的去调用委托所封装的方法,从而形成一种动态调用方法的代码结果。
像↓ 这样婶的 myDelegate 是一个委托变量,

    private void OnEnable()
    {
      //  myDelegate = new MyDelegate(Teleport); // 把方法的引用:Teleport 【赋值】myDelegate这个委托类型的变量中
        myDelegate = Teleport;    //和上面注释掉的一样
    }


即使我们可以通过【多播委托】来调用很多其他方法。但是这也是非常不安全的,在书本中会把这样的失误叫做“Reset Invocation List” 表示重置了委托封装的引用列表。 如上图,原本的委托类型应该调用三个方法的,但是最后由于一个失误,写了单个等号, 只调用了最后赋值的Log方法。

委托的这个缺点会在下下期-Event篇章当中得以解决。笔记链接:

小joe后面讲了 类型不兼容的例子和类型相符的几个例子。

Action action01; //Action委托 无返回值,可以有参数列表
Func func01; //Func委托 有 返回值,也可以有 参数列表。

Action action01;
Func<string> func01;
Func<double,double,double>  func01;

private void OnEnable()
{
  action01=new Action()
func01=new Func<string>(Log);
func02=new  Func<double,double,dobule>(Add);
}

private void Start()
{
  action01();
  func01();
func02(2.2f,3.3f,7f);
}

下篇
视频 【委托•预备篇】委托的一般使用介绍,泛型委托Action和Func以及委托的缺点(附:模板方法和回调方法的初次介绍)
笔记【委托•预备篇】

posted @ 2022-07-04 11:24  专心Coding的程侠  阅读(225)  评论(0编辑  收藏  举报