笔记【委托•预备篇】委托的一般使用介绍,泛型委托Action和Func以及委托的缺点(附:模板方法和回调方法的初次介绍
【持续更新ing】
上一期【自定义委托:Delegate】
小joe的视频链接:【委托•语法篇】委托类型的声明和实例以及Action委托和Func委托(附:委托的重要性,函数指针,多播委托,委托的缺点)
个人笔记链接:笔记【委托•语法篇】委托类型的声明和实例以及Action委托和Func委托(附:委托的重要性,函数指针,多播委托,委托的缺点)
委托
可以存储一个、或者多个方法的引用,
可以封装一个或者多个方法
如果没有使用委托,需要在Update写三个函数,进行三次方法的【直接调用】。
而写成图上那种的话,可以通过委托类型的变量 封装这三个方法。只需要在Update里,调用委托类型的变量,就能实现对这三个方法的【间接调用】了。
所以问题:【使用委托[间接调用]方法 比[直接调用]好在哪里呢?】
它的好处在于,我们的委托 封装了一个方法。
它可以封装任意一个、与之 类型兼容的方法。
我们知道 委托它是一种类,是一种 引用类型,
我们把委托 当做方法的参数,传递到这个方法里面去,
使用了这个传进来的委托。(就是 把委托当参数 →传到方法里。) 【间接的去调用】了我们委托所封装的某个方法。酱紫就形成了一种动态调用方法的代码。
这种把委托当做参数,传递到方法的用法,也是小joe视频后半段的重点项目的内容。
Action委托
是系统内置的委托类型,还是一个 泛型委托类型,它可以指向最多16个参数个数的方法。
不了解泛型也不要着急,泛型也就是泛化的数据类型。
比如说 我们的字典Dictionary就是泛型,还有我们的List集合,它都是泛型。
这些数据类型,都是它具体的,特化之后的数据类型。
T是泛化的数据类型,之后会单独介绍。
Action action;
针对于 ↑ 上面这个Action委托,它的【返回值】和【参数列表】都为空,
委托类型的变量名,我们取为 小写的 action;
这一行相当于两行的内容。
当前脚本中,我们通过Action委托类型,创建了一个委托类型的变量。
这里需要强调一点的是:
我们的Action委托,它只能指向,注意是 只能
只能够指向 或者 必须指向的是 一个返回值为空的 目标方法。
Action委托,参数列表可以为空也可以不为空。可以指向 有参数列表的方法。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;
public class DelegateAction : MonoBehaviour
{
private SpriteRenderer sp;
Action action;
Action<string, float> action01; //我们要指向 【参数列表】 为string和float类型的目标方法。
private void OnEnable()
{
action = new Action(Teleport);
action += new Action(ChangeColor);
action += new Action(Log);
//action01 = new Action(SayHello); //这是错误的
action01 = new Action<string, float>(SayHello);
}
void Start()
{
sp = GetComponent<SpriteRenderer>();
}
// Update is called once per frame
void Update()
{
if (Input.GetKeyDown(KeyCode.Space))
{
action01("Ivring",1);
}
}
//随机更换位置
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);
}
//_____________________________________________
public void SayHello(string playerName, float num)
{
Debug.Log(string.Format("{0} has {1} Championships Now", playerName, num));
}
public double Add(double a, double b)
{
return a + b;
}
public double Multiply(double a, double b)
{
return a * b;
}
}
Action action;
Action<string, float> action01; //我们要指向 【参数列表】 为string和float类型的目标方法。 <> 尖括号是 泛型。
action = new Action(Teleport);
action += new Action(ChangeColor);
action += new Action(Log);
action01 = new Action<string, float>(SayHello);
Func委托的 【返回值类型】 是放在所有参数的最后一位。
小joe说,这一点和 由params关键字 来修饰的参数,必须放在形式参数的最后一位的道理有些类型。 (params 其实在string.Format方法中 体现的最明显) (这个parames 章章不知道诶)
![](https://img2022.cnblogs.com/blog/2178516/202208/2178516-20220801145900898-1266300078.png) Action委托和Func委托 省去【自定义委托类型声明】的过程,转而使用C#类库,.NET为我们提供的Action委托Func委托,足够我们日常使用。
多播委托 很容易出现人为失误,而导致委托所封装的 所有方法重置(Reset Invocation List) 就是那个 把应该是+=的,写成=或者==, 提高了我们项目的维护成本的同时,安全性也极大的降低了。
(这个缺点也是 为什么我们之后要使用 事件 来作为委托类型字段的包装器)(原因之一 因为事件只允许+=或者-=符号,而不是赋值符号)
并且,过度使用委托,还会导致 内存泄露:Memory Leak
如果委托中 嵌套别的委托,那么debug难度也会上升。可读性几乎没有。
下下期会介绍 委托中嵌套另一个委托的 传送门:
现在来介绍一下
【委托】在平时的使用情况
委托是一种类(class),引用类型:Reference Type的数据类型。
可以存储一个、或者多个方法的引用。
类、引用类型可以干嘛呢?
我们的类,可以声明变量,创建实例,
比如在unity中,创建了 Player类型,
我们可以通过Player类型,声明Player类型的变量,创建实例
public class BeforeDemo : MonoBehaviour
{
//这里就是 创建实例 实例化
//创建三个 Player类型的实例
Player playerA = new Player() { playerName = "Alice", coins = 100, att = 10 };
Player playerB = new Player() { playerName = "Betty", coins = 200, att = 20 };
Player playerC = new Player() { playerName = "Candy", coins = 300, att = 30 };
private void Start()
{
DisplayInfo(playerC);
DisplayInfo(playerB);
DisplayInfo(playerA);
}
//参数列表为 【引用类型 Player类型】
public void DisplayInfo(Player player)
{
Debug.Log(string.Format($"My Name is{player.playerName},I have{player.coins}coins and my " +
$" Attack Damage is { player.att } "));
}
}
public class Player
{
public string playerName;
public int coins;
public int att;
}
还可以干嘛呢?
可以将Player类型 作为参数传递到我们的方法中,
(类可以作为方法的参数类型)
就像屏幕当中,
这个方法的基本功能就是:通过Player类型 传过来的参数,
显示出某个玩家的基本信息,并且打印出来。
委托类型也是引用类型,也是类,所以它也能够 声明变量、创建实例,
但最重要的是,我们的委托类型,引用类型,
它也可以作为参数 传递到我们的 方法的参数中,
所以我们会把委托 当做方法的参数,传递到方法里面去。
我们写了一个方法,这个方法 有一个委托类型的参数,
委托会封装 指向一个方法,只要这个方法 符合我们的「类型兼容」的特点。我们任意一个方法,都可以被委托所封装。
我们可以在主的方法体里面,使用这个传进来的委托。
【间接的去调用】那些委托所封装的方法。
这样呢,就可以形成一种 「动态调用」方法的代码结构。
小Joe制作的了一张图,方便大家去理解。
因为无论是【模板方法】还是【回调方法】,在使用的时候,都是将委托类型,当做参数,传递到方法的参数列表。不同的地方呢,只是概念上的区分。
最重要的是,模板方法 一般是方法中 有一处是不确定的, 其余呢,都是确定好了的,
这个不确定的部分,就靠我们委托传进来的委托类型的参数,所包含的这个方法来“填补”。
这就导致了模板方法中的参数,它一定是一个 有返回值类型的参数(的方法) 是Func委托类型。
而我们的回调方法呢,它是动态的去选择,后续将被调用的方法,
以回调方法的形式使用委托的时候呢,
我们需要把委托类型的参数,传进到主方法里面去,
这就导致我们的参数,要使用的是Action委托类型
(当然这两个方法 使用哪种委托也不是绝对的,只是一般情况下的用法)
小Joe写了一个方法,传进来的 委托类型参数,
“借用”指定的 外部方法,来产生一个结果。
那就相当于写了一个方法,这个方法中,有一个填空题,
这个填空题的空白处,就用传进来的 委托类型的参数来填补。
也就是通过 传进来的委托类型的参数,间接的调用了指定的外部方法。
这个方法,一般是具有返回值的。
让我们拿到这个返回值,我们就可以继续执行,所写方法的后面的逻辑了。
这就是为什么ta叫模板方法了,这个方法中有一处是不确定的,
其余部分都是确定好了的。 这个不确定的部分,就靠我们传出来的委托类型的参数来填补。
比如说这个项目当中,就有三个方法,符合我们委托类型 类型兼容的特点。
我们可以传递这三个方法的任意一个,作为参数传递到方法中。
根据不同的游戏需求,传递不同的委托类型 封装的方法,从而形成了那种 动态调用方法的代码结构。
下期项目中有关「模板方法」的使用,
回调方法
小Joe路上接到了很多小卡片,健身卡、买房中介、洗浴按摩店的,小Joe如果后来有其中的需要,就拿出对应卡片,给提供需求的对方打电话,如果一直没有需求,就不需要打电话。 这就相当于,「调用某个功能」来帮助小Joe解决一些问题,如果一直不办卡,一直不买房,就永远不需要去打这些电话。 这时候 小Joe就和它们之间形成了一种 回调关系。
回调方法,它会给我们1个机会,
我们的方法通过逻辑动态的去选择,后续将被调用的方法。
这就好比很多人都给你小卡片,我们可以打电话也可以不打电话,权利权在我们这里,是我们通过函数「自己的逻辑」来判断的。
当以 回调方法的形式,使用委托的时候,我们需要把委托类型的参数,传递到方法中。
被传进方法里的 委托类型的参数,它的内部就会封装一个被回调的方法,那也就是我们的回调方法。我们的方法会根据自己的逻辑来决定,是不是要调用这个回调方法。 一般回调方法都会放到方法代码的末尾处,而我们的回调方法,一般都是没有返回值的。
按照惯例,事先去预览一下我们之后 回调方法 这个例子。
如果某个玩家 获取到了【击杀数】或者【阵亡】或者【夺旗数】任意一个数据的 最高值的时候,我们就会去显示它获取这个最高数据的 当前时间
即 if (GameManager.instance.playerStatus[i].playerName == topname) 通过了,
for (int i = 0; i < GameManager.instance.playerStatus.Length; i++)
{
if (GameManager.instance.playerStatus[i].playerName == topname) //只有获取一定称号,才会显示时间
{
GameManager.instance.playerStatus[i].titile += title;
//↓ 这个代码不是正确的,还需要再修改 GameManager.instance.playerStatus[i].dateTime=func(GameManager.instance.playerStatus)
}
}
对于很多玩家来说,他们可能没有机会能够获取到 最高数值。那么就不会调用,显示时间的这个方法了。 我们的Log方法显示通用时间, 这个Log方法对于很多人来说,它可以调用,也可以不调用,用得着就用。
public static string Log(PlayerStatus player)
{
DateTime currenTime = DateTime.UtcNow;
Debug.Log(string.Format($"{player.playerName}has Reached{player.title}on{player.dateTime}"))
return currenTime.ToString();
}
如果我们打这把游戏,击杀数是全队的最高,那我们就记录一下我们这个时刻,那我们就调用Log方法。对于其他人来说,如果它这局打的不好,就不记录它获取称号的时间。 这个方法也是通过委托类型的变量 传入方法中来使用的。 方法会根据自己的逻辑来判断,是不是要调用这些方法。
下期视频将会是一个完整的项目演示
核心:介绍委托作为参数的实际用途,也就是我们的模板方法和回调方法的实际使用,结合冒泡算法,升序、降序排列,泛型、Lambda表达式、UI界面等综合知识。
视频传送门:【冒泡算法】升序降序排列玩家各项数据以及DOTween和LeanTween的使用介绍与缺点(附:泛型,模板方法,SWAP问题,十大常用Array方法,富文本等)
笔记传送门: 笔记 【冒泡算法】升序降序排列玩家各项数据以及DOTween和LeanTween的使用介绍与缺点(附:泛型,模板方法,SWAP问题,十大常用Array方法,富文本等)