新闻详情
Unity里的delegate, event, Action, Func, UnityEvent, UnityAction和Event
Unity里的delegate, event, Action, Func, UnityEvent, UnityAction和Event
C# / Unity 中 delegate、event、Action、Func、UnityEvent 的简单理解背景一直以来我对各种“事件”的应用都有点迷迷糊糊。平时写代码的时候大多就是-?.Invoke()这样直接使用。但是很多时候看源码看到delegate、event、Action、Func、UnityEvent这些东西混在一起就会有点懵。所以这次专门花了一些时间翻文章、看代码把自己的理解记录下来。1. delegate1.1 delegate 是什么delegate可以理解为一种类型安全的函数指针。它的作用是用来存储一个或多个方法并在需要的时候执行这些方法。声明delegate的时候本质上是在声明一个“委托类型”。之后就可以用这个委托类型去声明变量并让这个变量指向符合签名的方法。1.2 delegate 的基本写法publicdelegatevoidMyDelegate(intnumber);这行代码的意思是声明一个名为MyDelegate的委托类型。这个委托类型要求绑定的方法必须满足无返回值void有一个int类型参数所以后面只要方法符合这个签名就可以赋值给MyDelegate类型的变量。1.3 delegate 使用案例usingUnityEngine;publicclassTest:MonoBehaviour{publicdelegatevoidMyDelegate(intnumber);privatevoidStart(){MyDelegatedel1TestFunction1;del1(55);}privatevoidTestFunction1(intnum){Debug.Log(Test1: num);}}这里的执行流程是MyDelegatedel1TestFunction1;表示把TestFunction1这个方法赋值给del1。然后del1(55);就相当于调用TestFunction1(55);所以最终会输出Test1:552. event2.1 event 是什么event是一个关键字。从表现上看它可以理解为一种受限制的 delegate。它通常和委托类型一起使用。例如publicdelegatevoidMyDelegate(intnumber);publiceventMyDelegatedel2;这里的del2是一个事件。2.2 event 和普通 delegate 的区别在当前类内部event修饰的委托变量基本可以像普通委托一样使用del2SomeFunction;del2SomeFunction;del2-SomeFunction;del2?.Invoke(10);但是在类的外部event会限制访问权限。外部类只能-不能直接也不能直接调用del2.Invoke();2.3 为什么要用 event假如不用event而是直接暴露一个 public delegatepublicMyDelegatedel;那么外部类可以直接这样写obj.delnull;这样会把之前所有注册的方法都清空风险很大。而使用event后publiceventMyDelegatedel;外部类就只能添加和移除监听obj.delSomeFunction;obj.del-SomeFunction;不能直接覆盖整个事件。所以event的主要作用是保护委托变量防止外部随意赋值、清空、调用。2.4 event 的底层理解可以简单理解为event会把委托变量的直接赋值权限限制起来。外部访问时只暴露类似下面这样的操作addremove也就是外部的-本质上会走事件的添加和移除逻辑。而赋值和Invoke调用只能在声明事件的类内部使用。3. Action 和 Func3.1 Action 和 Func 是什么Action和Func都是系统提前封装好的委托类型。使用它们的好处是不需要自己手动声明 delegate可以直接拿来用。3.2 ActionAction表示没有返回值的委托。例如系统内部大概是这样声明的namespaceSystem{publicdelegatevoidAction();}也就是说ActionmyAction;本质上就相当于声明了一个无参数、无返回值的委托变量。如果需要参数也可以写Actionintaction1;Actionint,stringaction2;3.3 FuncFunc表示有返回值的委托。例如namespaceSystem{publicdelegateTResultFuncinT,outTResult(Targ);}常见写法Funcint,intmyFunc;它表示传入一个int返回一个int最后一个泛型参数永远表示返回值类型。例如Funcint,string,boolfunc;表示第一个参数是int第二个参数是string返回值是bool3.4 Action、Func 和 event 一起使用Action和Func本身也是委托类型。所以它们也可以配合event使用。例如publicActionMyAction;publicFuncint,intMyFunc;publiceventActionMyActionEvent;其中publicActionMyAction;外部可以直接赋值、清空、调用。而publiceventActionMyActionEvent;外部只能和-不能直接赋值或调用。3.5 代码案例usingSystem;usingUnityEngine;publicclassTestDelegate:MonoBehaviour{publicActionMyAction;publicFuncint,intMyFunc;publiceventActionMyActionEvent;voidStart(){MyActionTest3;MyActionTest4;MyAction();MyActionnull;Debug.Log(**************);MyFuncTest5;intresultMyFunc(77);Debug.Log(Result: result);MyFuncnull;Debug.Log(**************);MyActionEventTest3;MyActionEvent();MyActionEventnull;}publicvoidTest3(){Debug.Log(Test3);}publicvoidTest4(){Debug.Log(Test4);}publicintTest5(intnum){Debug.Log(Test5: num);returnnum;}}在当前类TestDelegate内部MyActionEventTest3;MyActionEvent();MyActionEventnull;这些都是合法的。但是如果在外部类中这样写publicclassTest:MonoBehaviour{publicTestDelegatet;privatevoidStart(){t.MyActionnull;t.MyFuncnull;t.MyActionEventnull;// 非法外部不能直接赋值 eventt.MyActionEvent?.invoke();//非法外部不可以调用event标记的事件}}这里t.MyActionnull;t.MyFuncnull;是合法的。但是t.MyActionEventnull;t.MyActionEvent?.invoke();会报错。因为MyActionEvent被event关键字保护了外部不能直接赋值。外部只能这样t.MyActionEventSomeFunction;t.MyActionEvent-SomeFunction;4. UnityAction 和 UnityEvent4.1 UnityAction理解了Action之后UnityAction就很好理解了。UnityAction是 Unity 内部预先声明好的委托类型。它和Action很像。Unity 内部大概是这样声明的namespaceUnityEngine.Events{publicdelegatevoidUnityAction();}所以UnityActionaction;本质上也是一个无参数、无返回值的委托。4.2 UnityEventUnityEvent是 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(){EventeEvent.current;if(e.typeEventType.MouseDown){Debug.Log(鼠标按下);}}5.3 Event 常见用途Event多用于OnGUI编辑器扩展自定义 EditorWindow自定义 Inspector旧版 IMGUI 输入处理它和下面这些概念不是同一类东西delegateeventAction Func UnityEvent所以可以简单记成event是 C# 事件关键字。Event是 Unity 的 GUI 事件数据类。6. 简单总结6.1 delegatedelegate是委托类型。它可以存储符合指定签名的方法并在需要的时候调用。6.2 eventevent是对委托变量的一层限制。它可以防止外部类直接赋值、清空和调用事件。外部只能-6.3 ActionAction是系统封装好的无返回值委托。例如Action ActionintActionint,string6.4 FuncFunc是系统封装好的有返回值委托。最后一个泛型参数表示返回值类型。例如Funcint,intFuncint,string,bool6.5 UnityActionUnityAction是 Unity 自己封装的委托类型和Action很像。6.6 UnityEventUnityEvent是 Unity 封装的事件系统。它可以通过代码注册监听也可以在 Inspector 面板中配置监听。常用方法是AddListener RemoveListener Invoke6.7 EventEvent是 Unity 的 GUI 事件数据类。它主要用于OnGUI Event.current EditorWindow 自定义 Inspector它和 C# 的event关键字不是一个东西。7. 最后总结我现在对这些概念的简单理解是delegate声明一种“方法变量类型” Action / Func / UnityAction系统或 Unity 提前声明好的委托类型 event限制外部访问权限的委托变量 UnityEventUnity 封装的、支持 Inspector 配置的事件系统 EventUnity OnGUI / 编辑器系统中的事件数据类