笔记【委托•语法篇】委托类型的声明和实例以及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
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以及委托的缺点(附:模板方法和回调方法的初次介绍)
笔记【委托•预备篇】