Unity里的delegate, event, Action, Func, UnityEvent, UnityAction和Event
C# / Unity 中 delegate、event、Action、Func、UnityEvent 的简单理解
背景
一直以来,我对各种“事件”的应用都有点迷迷糊糊。
平时写代码的时候,大多就是:
+=-=?.Invoke()这样直接使用。
但是很多时候看源码,看到delegate、event、Action、Func、UnityEvent这些东西混在一起,就会有点懵。
所以这次专门花了一些时间翻文章、看代码,把自己的理解记录下来。
1. delegate
1.1 delegate 是什么?
delegate可以理解为一种类型安全的函数指针。
它的作用是:
用来存储一个或多个方法,并在需要的时候执行这些方法。
声明delegate的时候,本质上是在声明一个“委托类型”。
之后就可以用这个委托类型去声明变量,并让这个变量指向符合签名的方法。
1.2 delegate 的基本写法
publicdelegatevoidMyDelegate(intnumber);这行代码的意思是:
声明一个名为MyDelegate的委托类型。
这个委托类型要求绑定的方法必须满足:
- 无返回值:
void - 有一个
int类型参数
所以,后面只要方法符合这个签名,就可以赋值给MyDelegate类型的变量。
1.3 delegate 使用案例
usingUnityEngine;publicclassTest:MonoBehaviour{publicdelegatevoidMyDelegate(intnumber);privatevoidStart(){MyDelegatedel1=TestFunction1;del1(55);}privatevoidTestFunction1(intnum){Debug.Log("Test1: "+num);}}这里的执行流程是:
MyDelegatedel1=TestFunction1;表示把TestFunction1这个方法赋值给del1。
然后:
del1(55);就相当于调用:
TestFunction1(55);所以最终会输出:
Test1:552. event
2.1 event 是什么?
event是一个关键字。
从表现上看,它可以理解为一种受限制的 delegate。
它通常和委托类型一起使用。
例如:
publicdelegatevoidMyDelegate(intnumber);publiceventMyDelegatedel2;这里的del2是一个事件。
2.2 event 和普通 delegate 的区别
在当前类内部,event修饰的委托变量,基本可以像普通委托一样使用:
del2=SomeFunction;del2+=SomeFunction;del2-=SomeFunction;del2?.Invoke(10);但是在类的外部,event会限制访问权限。
外部类只能:
+=-=不能直接:
=也不能直接调用:
del2.Invoke();2.3 为什么要用 event?
假如不用event,而是直接暴露一个 public delegate:
publicMyDelegatedel;那么外部类可以直接这样写:
obj.del=null;这样会把之前所有注册的方法都清空,风险很大。
而使用event后:
publiceventMyDelegatedel;外部类就只能添加和移除监听:
obj.del+=SomeFunction;obj.del-=SomeFunction;不能直接覆盖整个事件。
所以event的主要作用是:
保护委托变量,防止外部随意赋值、清空、调用。
2.4 event 的底层理解
可以简单理解为:
event会把委托变量的直接赋值权限限制起来。
外部访问时,只暴露类似下面这样的操作:
addremove也就是外部的:
+=-=本质上会走事件的添加和移除逻辑。
而=赋值和Invoke调用,只能在声明事件的类内部使用。
3. Action 和 Func
3.1 Action 和 Func 是什么?
Action和Func都是系统提前封装好的委托类型。
使用它们的好处是:
不需要自己手动声明 delegate,可以直接拿来用。
3.2 Action
Action表示没有返回值的委托。
例如系统内部大概是这样声明的:
namespaceSystem{publicdelegatevoidAction();}也就是说:
ActionmyAction;本质上就相当于声明了一个无参数、无返回值的委托变量。
如果需要参数,也可以写:
Action<int>action1;Action<int,string>action2;3.3 Func
Func表示有返回值的委托。
例如:
namespaceSystem{publicdelegateTResultFunc<inT,outTResult>(Targ);}常见写法:
Func<int,int>myFunc;它表示:
- 传入一个
int - 返回一个
int
最后一个泛型参数永远表示返回值类型。
例如:
Func<int,string,bool>func;表示:
- 第一个参数是
int - 第二个参数是
string - 返回值是
bool
3.4 Action、Func 和 event 一起使用
Action和Func本身也是委托类型。
所以它们也可以配合event使用。
例如:
publicActionMyAction;publicFunc<int,int>MyFunc;publiceventActionMyActionEvent;其中:
publicActionMyAction;外部可以直接赋值、清空、调用。
而:
publiceventActionMyActionEvent;外部只能+=和-=,不能直接赋值或调用。
3.5 代码案例
usingSystem;usingUnityEngine;publicclassTestDelegate:MonoBehaviour{publicActionMyAction;publicFunc<int,int>MyFunc;publiceventActionMyActionEvent;voidStart(){MyAction+=Test3;MyAction+=Test4;MyAction();MyAction=null;Debug.Log("**************");MyFunc=Test5;intresult=MyFunc(77);Debug.Log("Result: "+result);MyFunc=null;Debug.Log("**************");MyActionEvent=Test3;MyActionEvent();MyActionEvent=null;}publicvoidTest3(){Debug.Log("Test3");}publicvoidTest4(){Debug.Log("Test4");}publicintTest5(intnum){Debug.Log("Test5: "+num);returnnum;}}在当前类TestDelegate内部:
MyActionEvent=Test3;MyActionEvent();MyActionEvent=null;这些都是合法的。
但是如果在外部类中这样写:
publicclassTest:MonoBehaviour{publicTestDelegatet;privatevoidStart(){t.MyAction=null;t.MyFunc=null;t.MyActionEvent=null;// 非法,外部不能直接赋值 eventt.MyActionEvent?.invoke();//非法,外部不可以调用event标记的事件}}这里:
t.MyAction=null;t.MyFunc=null;是合法的。
但是:
t.MyActionEvent=null;t.MyActionEvent?.invoke();会报错。
因为MyActionEvent被event关键字保护了,外部不能直接赋值。
外部只能这样:
t.MyActionEvent+=SomeFunction;t.MyActionEvent-=SomeFunction;4. UnityAction 和 UnityEvent
4.1 UnityAction
理解了Action之后,UnityAction就很好理解了。
UnityAction是 Unity 内部预先声明好的委托类型。
它和Action很像。
Unity 内部大概是这样声明的:
namespaceUnityEngine.Events{publicdelegatevoidUnityAction();}所以:
UnityActionaction;本质上也是一个无参数、无返回值的委托。
4.2 UnityEvent
UnityEvent是 Unity 封装的一套事件系统。
它和普通委托的使用方式不太一样。
普通委托通常是:
+=-=?.Invoke()而UnityEvent通常是:
AddListener RemoveListener Invoke4.3 UnityEvent 的使用案例
usingUnityEngine;usingUnityEngine.Events;publicclassTest:MonoBehaviour{publicUnityEventMyEvent;voidStart(){MyEvent.AddListener(TestFunction);}privatevoidTestFunction(){Debug.Log("TestFunction");}}调用事件时:
MyEvent.Invoke();移除监听时:
MyEvent.RemoveListener(TestFunction);4.4 UnityEvent 的特点
UnityEvent的一个重要特点是:
可以在 Unity Inspector 面板中显示,并且可以手动拖拽对象和方法进行注册。
这点是普通Action、delegate、event不具备的。
所以在 Unity 中,UnityEvent常用于:
- 按钮点击事件
- Inspector 面板配置事件
- 不想写死代码,希望策划或美术可以在面板中配置回调
- 简单的组件通信
5. Event 类
5.1 Event 和 event 不是一个东西
一开始我也容易把Event和event搞混。
后来查了一下才知道:
Event是 Unity 里的一个类,主要用于OnGUI系统。
它和 C# 的事件委托不是一回事。
5.2 Event.current
在 Unity 的旧版 IMGUI 系统中,每一帧 Unity 会多次推送输入、绘制等事件。
每一次都会生成一个Event对象。
可以通过:
Event.current获取当前这一次 GUI 事件的数据。
例如:
voidOnGUI(){Evente=Event.current;if(e.type==EventType.MouseDown){Debug.Log("鼠标按下");}}5.3 Event 常见用途
Event多用于:
OnGUI- 编辑器扩展
- 自定义 EditorWindow
- 自定义 Inspector
- 旧版 IMGUI 输入处理
它和下面这些概念不是同一类东西:
delegateeventAction Func UnityEvent所以可以简单记成:
event是 C# 事件关键字。Event是 Unity 的 GUI 事件数据类。
6. 简单总结
6.1 delegate
delegate是委托类型。
它可以存储符合指定签名的方法,并在需要的时候调用。
6.2 event
event是对委托变量的一层限制。
它可以防止外部类直接赋值、清空和调用事件。
外部只能:
+=-=6.3 Action
Action是系统封装好的无返回值委托。
例如:
Action Action<int>Action<int,string>6.4 Func
Func是系统封装好的有返回值委托。
最后一个泛型参数表示返回值类型。
例如:
Func<int,int>Func<int,string,bool>6.5 UnityAction
UnityAction是 Unity 自己封装的委托类型,和Action很像。
6.6 UnityEvent
UnityEvent是 Unity 封装的事件系统。
它可以通过代码注册监听,也可以在 Inspector 面板中配置监听。
常用方法是:
AddListener RemoveListener Invoke6.7 Event
Event是 Unity 的 GUI 事件数据类。
它主要用于:
OnGUI Event.current EditorWindow 自定义 Inspector它和 C# 的event关键字不是一个东西。
7. 最后总结
我现在对这些概念的简单理解是:
delegate:声明一种“方法变量类型” Action / Func / UnityAction:系统或 Unity 提前声明好的委托类型 event:限制外部访问权限的委托变量 UnityEvent:Unity 封装的、支持 Inspector 配置的事件系统 Event:Unity OnGUI / 编辑器系统中的事件数据类