委托
C语言中的函数指针
#include <stdio.h>
int Add(int a, int b)
{
return a + b;
}
int Multiply(int a, int b)
{
return a * b;
}
int main()
{
int a = 1;
int b = 2;
printf("Add: %d\n", Add(a, b));
printf("Multiply: %d\n", Multiply(a, b));
return 0;
}
复制代码
正常调用:
Add: 3
Multiply: 2
复制代码
定义一个 两个 int 参数,返回值为 int 的函数指针。存两个函数的地址,然后通过地址调用函数。
#include <stdio.h>
typedef int (*Fun_ptr)(int,int);
int Add(int a, int b)
{
return a + b;
}
int Multiply(int a, int b)
{
return a * b;
}
int main(void) {
Fun_ptr fun1 = &Add;
Fun_ptr fun2 = &Multiply;
int a = 1;
int b = 2;
printf("fun1 Add: %d\n", fun1(a, b));
printf("fun2 Multiply: %d\n", fun2(a, b));
return 0;
}
复制代码
fun1 Add: 3
fun2 Multiply: 2
复制代码
C语言中的回调函数
在main中用xAddRandomNum时,xAddRandomNum需要传入一个“不要参赛,且返回值为int”的函数指针。
xAddRandomNum需要调用另一个函数完成自己的任务,被调用的那个函数就是回调函数。
#include <stdlib.h>
#include <stdio.h>
int getNextRandomValue(void)
{
return rand() % 100;
}
int xAddRandomNum(int x, int (*getRandomFun)(void))
{
return x + getRandomFun();
}
int main(void) {
printf("xAddRandomNum: %d\n", xAddRandomNum(1, getNextRandomValue));
return 0;
}
复制代码
C#中的委托
class HelloWorld
{
// 定义一个委托方法DeleFun() 相当于C里面定义方法指针
public delegate void DeleFun();
// 声明一个 如上定义 没有参赛没有返回值的 委托变量
static DeleFun deleFun;
static void Main(string[] args)
{
deleFun = new DeleFun(Fun1);
//deleFun = Fun1; 简写,一样的效果
deleFun.Invoke();
Console.ReadKey();
}
private static void Fun1()
{ Console.Write("Invoke Fun1");}
}
复制代码
Invoke Fun1
复制代码
可以一个委托变量绑定多个方法:
class HelloWorld
{
// 定义一个委托方法DeleFun() 相当于C里面定义方法指针
public delegate void DeleFun();
// 声明一个 如上定义 没有参赛没有返回值的 委托变量
static DeleFun deleFun;
static void Main(string[] args)
{
deleFun += Fun1;
deleFun += Fun2;
deleFun.Invoke();
Console.ReadKey();
}
private static void Fun1()
{ Console.WriteLine("Invoke Fun1");}
private static void Fun2()
{ Console.WriteLine("Invoke Fun2");}
}
复制代码
Invoke Fun1
Invoke Fun2
复制代码
绑定多个方法,叫多播,绑定单个方法叫单播,但是单播实际上也是通过多播实现的,只不过把个数限制为1个。
static void Main(string[] args)
{
deleFun += Fun1;
// 去掉+
deleFun = Fun2;
deleFun.Invoke();
Console.ReadKey();
}
private static void Fun1()
{ Console.WriteLine("Invoke Fun1");}
private static void Fun2()
{ Console.WriteLine("Invoke Fun2");}
}
复制代码
最后一个不用“+=”,而是“=”。
Invoke Fun2
复制代码
换一下,先“=”,再“+=”:
static void Main(string[] args)
{
deleFun = Fun1;
deleFun += Fun2;
deleFun.Invoke();
Console.ReadKey();
}
private static void Fun1()
{ Console.WriteLine("Invoke Fun1");}
private static void Fun2()
{ Console.WriteLine("Invoke Fun2");}
}
复制代码
正常多播:
Invoke Fun1
Invoke Fun2
复制代码
Action
Action就是C#官方给你提供一个拿来就直接用的委托。
class HelloWorld
{
// 定义一个变量
static Action<int> OneKeyThreeFuns;
static void Main(string[] args)
{
// 直接绑方法
OneKeyThreeFuns += Fun1;
OneKeyThreeFuns += Fun2;
OneKeyThreeFuns += Fun3;
OneKeyThreeFuns.Invoke(2);
Console.ReadKey();
}
static void Fun1(int a)
{
Console.WriteLine($"num:{a}, Fun1");
}
static void Fun2(int a)
{
Console.WriteLine($"num:{a}, Fun2");
}
static void Fun3(int a)
{
Console.WriteLine($"num:{a}, Fun3");
}
}
复制代码
Action<int, bool, string ….> 绑最多十六个参数,无返回值的方法。
Func
前面是参数,最后面是返回类型,也是最多16个参数。
using System;
namespace HelloWorldApplication
{
class HelloWorld
{
static void Main()
{
Func<int, string> method = Fun1;
Console.WriteLine(method(3));
Console.ReadLine();
}
static string Fun1(int x)
{
return (x * x).ToString();
}
}
}
复制代码
lambda表达式形式
Func<int, int, string> method = (x, y) =>
{
int val = x + y;
return val.ToString();
};
复制代码
匿名委托形式
Func<int, int, string> method = delegate (int x, int y)
{
int val = x + y;
return val.ToString();
};
复制代码
使用方法其一
假如有这样一个玩家数据的类,存储了击杀数、死亡数、分数等数据。
public class PlayerStats
{
public string playerName;
// 击杀数、死亡数、得分数
public int killNum;
public int deathNum;
public int scoreNum;
}
复制代码
playerStatsList在「GameManager」中,是存储了所有玩家的列表。
public class GameManager : Singleton<GameManager>
{
public PlayerStats[] playerStatsList;
}
复制代码
如果想要获得,击杀数最高的玩家的名字,可以如下写一个遍历所有玩家,然后记录最大值对应的名字。
public class GameManager : Singleton<GameManager>
{
public PlayerStats[] playerStatsList;
void Awake()
{
print(GetTopKillPlayerName());
}
private string GetTopKillPlayerName()
{
string playerName = "";
int topKillNum = 0;
foreach (PlayerStats playerStats in playerStatsList)
{
int tempNum = playerStats.killNum;
if (tempNum > topNum)
{
topNum = tempNum;
playerName = playerStats.playerName;
}
}
return playerName;
}
}
复制代码
如果还想获得死亡数最高的玩家的名称,再写一个方法,只不过比较的不是击杀数而是死亡数。
public class GameManager : Singleton<GameManager>
{
public PlayerStats[] playerStatsList;
void Awake()
{
print(GetTopKillPlayerName());
print(GetTopDeathPlayerName());
}
private string GetTopKillPlayerName()
{
string playerName = "";
int topKillNum = 0;
foreach (PlayerStats playerStats in playerStatsList)
{
int tempNum = playerStats.killNum;
if (tempNum > topNum)
{
topNum = tempNum;
playerName = playerStats.playerName;
}
}
return playerName;
}
private string GetTopDeathPlayerName()
{
string playerName = "";
int topDeathNum = 0;
foreach (PlayerStats playerStats in playerStatsList)
{
int tempNum = playerStats.deathNum;
if (tempNum > topNum)
{
topNum = tempNum;
playerName = playerStats.playerName;
}
}
return playerName;
}
}
复制代码
问题出现了,很明显,大部分代码都是相同的,但是为了不同的部分,重新写了一个方法,如果玩家有成百上千个属性,总不能写几百个方法吧?
private int GetKillNum(PlayerStats playerStats)
{
return playerStats.killNum;
}
private int GetDeathNum(PlayerStats playerStats)
{
return playerStats.deathNum;
}
private int GetScoreNum(PlayerStats playerStats)
{
return playerStats.scoreNum;
}
复制代码
所以可以写三个,参数为PlayerStats,返回值为int的方法。然后将委托作为GetTopItemPlayerName的参数。
string topKillPlayerName = GetTopItemPlayerName(GetKillNum);
string topDeathPlayerName = GetTopItemPlayerName(GetDeathNum);
string topScorePlayerName = GetTopItemPlayerName(GetScoreNum);
复制代码
那么tempNum获得的,就是对应的数据。
private string GetTopItemPlayerName(Func<PlayerStats, int> func)
{
string playerName = "";
int topNum = 0;
foreach (PlayerStats playerStats in playerStatsList)
{
int tempNum = func(playerStats);
if (tempNum > topNum)
{
topNum = tempNum;
playerName = playerStats.playerName;
}
}
return playerName;
}
复制代码
再简洁一点,用Lambda表达式,可以进一步省略声明方法的过程。
string topKillPlayerName = GetTopItemPlayerName(playerStats => playerStats.killNum);
string topDeathPlayerName = GetTopItemPlayerName(playerStats => playerStats.deathNum);
string topScorePlayerName = GetTopItemPlayerName(playerStats => playerStats.scoreNum);
复制代码
将方法中不确定的部分,利用委托作为参数,生成模板方法。这是委托的使用方法其一。
使用方法其二
假设现在有「GameManager」和「UIManager」:
public class GameManager : MonoBehaviour
{
private void Update()
{
if (Input.GetKeyDown(KeyCode.A))
GameOver();
}
void GameOver()
{
Debug.Log("GameOver");
// 让UI Manager显示 “GameOver”字样
}
}
复制代码
public class UIManager : MonoBehaviour
{
public TextMeshProUGUI gameOverText;
void Awake()
{
gameOverText.gameObject.SetActive(false);
}
private void ShowGameOverText()
{
gameOverText.gameObject.SetActive(true);
}
}
复制代码
我想在「GameManager」执行GameOver方法时,调用UI管理器的“private”方法,显示GameOver字样。
在「GameManager」中声明一个 “static” 委托「OnGameOver」,那么委托绑定的方法,会在GameOver()中被调用。
using System;
public class GameManager : MonoBehaviour
{
public static Action OnGameOver;
private void Update()
{
if (Input.GetKeyDown(KeyCode.A))
GameOver();
}
void GameOver()
{
// 委托不为空的时候,调用委托绑定的所有方法
OnGameOver?.Invoke();
}
}
复制代码
在UI Manager脚本中,脚本“自愿的”将自己的private方法,绑定在别人的委托上,这样其它脚本就相当于通过委托,获得了这个private方法的地址,自然就可以调用了。
public class UIManager : MonoBehaviour
{
public TextMeshProUGUI gameOverText;
private void OnEnable()
{
GameManager.OnGameOver += ShowGameOverText;
}
private void OnDisable()
{
GameManager.OnGameOver -= ShowGameOverText;
}
void Awake()
{
gameOverText.gameObject.SetActive(false);
}
private void ShowGameOverText()
{
gameOverText.gameObject.SetActive(true);
}
}
复制代码
游戏开始:
按A后
再拓展一下,一个委托可以绑定,各个不同的类的各个不同的方法。比如音效管理器里,停止音乐的方法,玩家脚本里,玩家死亡后的各种方法。通过多播委托实现。
但是使用多播委托,会有各种各样的问题,比如在多人协作里,负责某一脚本的人,将“+=”写成了“=”,前面绑定的其它方法全都被这个方法顶掉了,造成各种问题。
所以event,事件,就应运而生,相当于将多播委托进行了规范,封装成了事件,详细介绍见下篇。