前篇链接:Unity之C#学习笔记(14):委托和事件 Delegates and Events
前一篇中我们已经讲了C#中的委托(不清楚的小伙伴可以点击上面的链接),这节来聊聊两种“特化”的委托:Action和Func。
Action,就是只有参数没有返回值的委托。只有参数意味着函数可以有零个、一个或多个参数,没有返回值,即返回类型为void。Action从字面意义上很好理解,“一个活动”,就是做一件事,做完就行了,不用报告结果。
从一个简单的例子看起:Player脚本当按下空格键时要掉血,同时通知UIManager更新UI,显示当前生命值。使用委托,我们会这样实现:
Player:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Player : MonoBehaviour
{
public delegate void OnDamageReceived(int currentHealth);
public static event OnDamageReceived onDamageReceived;
public int health = 100;
void Update()
{
if (Input.GetKeyDown(KeyCode.Space))
ReceiveDamage();
}
void ReceiveDamage()
{
health--;
if (onDamageReceived!= null)
onDamageReceived(health);
}
}
UIManager:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class UIManager : MonoBehaviour
{
public Text TxtPlayerHealth;
void Start()
{
Player.onDamageReceived += UpdatePlayerHealth;
}
private void UpdatePlayerHealth(int health)
{
TxtPlayerHealth.text = "玩家生命值:" + health;
}
}
使用Action,我们可以简化Player中先声明delegate类型,再创建event变量的过程。
using System; // 使用Action要包含System命名空间
// ...
public delegate void OnDamageReceived(int currentHealth);
//public static event OnDamageReceived onDamageReceived;
public delegate void OnComplete(int currentHealth);
//public static event OnComplete onComplete;
public static Action onComplete; // 零个参数的Action,不打开尖括号
// 效果与以上两行相同
public static Action<int> onDamageReceived; // 一个或多个参数的Action,打开尖括号,写参数类型列表
其他使用方式与委托相同。根据微软官方文档,至多提供包含16个参数的Action模版。
Func与Action相反,是有返回值(即不为void)的委托,也可以有参数。目前为止,我们用的委托返回值都是void类型的。委托当然可以有返回值,但是有一个问题:如果有多个函数注册到一个有返回值的委托,该委托最终保留的是最后一个函数的返回值,前面函数的返回会被丢弃。例如下面的例子:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class GameManager : MonoBehaviour
{
public delegate int GetLength(string text);
public event GetLength getLength;
private void Start()
{
getLength += SmartFunc;
getLength += DumbFunc;
Debug.Log(getLength("Hello"));
}
int SmartFunc(string text)
{
Debug.Log("I'm smart!");
return text.Length;
}
int DumbFunc(string text)
{
Debug.Log("I'm dumb...");
return 0;
}
}
可以看到SmartFunc和DumbFunc都被调用了,但最终保留的是DumbFunc的返回值。所以,有返回值的委托在多播中是用的比较少的。
言归正传,继续说Func的使用。Func一定要打开尖括号(因为至少有一个返回值类型),尖括号中先写参数类型,最后一项写返回值类型。例如改写上面的程序,只需要将声明委托类型+声明变量的两行改为声明Func变量的一行:
//public delegate int GetLength(string text);
//public event GetLength getLength;
// 与Action相同,不要忘记using System;
public Func<string, int> getLength;
根据文档,Func也提供0到16个参数+1个返回值的模版。
最后我们来聊聊Lambda表达式。这个写过前端JS的同学们肯定很熟悉了,就是箭头函数。对于一些只在委托中使用一次的函数,实际上赋予名称—按名称绑定的方式显得有些冗余,最好是有一种方法能将函数体直接写在绑定处。C#从2.0开始支持这一特性,引入了匿名函数的概念,其语法为:
delegate void TestDelegate(string s);
TestDelegate testDelA = new TestDelegate(M); // C# 1.0,不支持匿名函数,M为前面定义的函数
TestDelegate testDelB = delegate(string s) { Console.WriteLine(s); }; // C# 2.0,匿名函数写法
C# 3.0引入了Lambda表达式,其表达性更强,更简洁:
TestDelegate testDelC = (x) => { Console.WriteLine(x); }; // C# 3.0 Lambda表达式。TestDelegate函数签名确定了x的类型为string
对于前面获取长度的例子,使用Lambda表达式写为:
getLength = (x) => x.Length;
括号内写参数列表,“=>”右侧写函数体。几种具体情况:
函数体只有一句return xx,称为expression lambda,箭头右侧直接写该返回表达式,不用写return(例如上例):
(input-parameters) => expression
函数体有多个语句时,称为statement lambda,函数体用大括号包围:
(input-parameters) => { <sequence-of-statements> }
函数只有一个参数时,括号可以省略:
getLength = x => x.Length;
函数没有参数时,要写空括号:
() => ...;
当编译器无法推断参数类型时,要显式声明。所有参数要么全部隐式声明,要么全部显式声明:
(int x, string s) => s.Length > x;