为啥选择 UnityEvent 而不是原生 C# 事件?

Posted

技术标签:

【中文标题】为啥选择 UnityEvent 而不是原生 C# 事件?【英文标题】:Why choose UnityEvent over native C# events?为什么选择 UnityEvent 而不是原生 C# 事件? 【发布时间】:2017-06-24 08:43:18 【问题描述】:

我的意思是,UnityEvents 比原生 C# 事件慢,而且它们仍然存储对接收器的强引用。因此,我能找到在原生 C# 事件上使用 UnityEvents 的唯一正当理由是它们与编辑器的集成。 我忽略了什么

【问题讨论】:

实际上 UnityEvent 是一个误导性的名称,因为其中没有真正的事件,只是 Action 的集合。请注意,没有办法像真实事件一样调用 UnityEvent,请使用 Invoke。 @Everts 我不明白“真实”一词如何帮助理解这个问题。另外,我不明白您在说什么:您通过使用 invoke 方法调用 UnityEvent 。据我了解,在底层,原生 c# 事件也是 Actions 的集合,它们只是隐藏该列表的原生 c# 语言结构。 【参考方案1】:

我是否忽略了什么?

不,你没有忽略任何东西。使用UnityEvent 的唯一优势和理由是它允许您在编辑器中使用事件。这适用于拖放人员或制作编辑器插件的人员。

UnityEvent 的另一个优点是它可以防止由于滥用委托或将匿名委托与 Unity 对象一起使用而导致 Unity 对象未被释放的问题。尽管当持有它们的主脚本被销毁时,它们会被释放。这是因为UnityEvent 是用弱引用实现的,因此可以消除/最小化这个问题。这两件事仍然值得在原生 C# 事件上使用 UnityEvent

如果您不制作编辑器插件,您应该始终使用原生事件并委托 UnityEvent,因为它具有快速的性能和较小的内存使用量。有关更多信息,请参阅 this 和 this 帖子。

【讨论】:

谢谢。你会建议使用 Wea​​kEventManager 吗? 不。只需使用正常的 C# 事件。我不会冒险使用任何会阻止 Unity 清理其对象的东西,例如 Texture2D 为什么 WeakEventManager 会阻止 Unity 清理对象?引用很弱。 措辞不当。我不知道它是否会并且没有对此进行测试。在 C# 中使用某些特定功能会阻止 Unity 释放其对象。这包括滥用委托或将匿名委托与 Unity 对象一起使用。我认为您应该先进行测试并确认WeakEventManager 没有任何问题,然后再在完整游戏中使用它。重新阅读我的答案。我为UnityEvent添加了一个积分。 Jackson 的博文(第二个链接)对该主题进行了透彻的分析。【参考方案2】:

UnityEvent主要用于Unity UI系统,因为ugui需要在ui系统中序列化回调配置,比如按钮的OnClick回调。

序列化是unity游戏引擎中最重要的一个特性,c#内置事件系统,你不能做序列化。

所以如果你使用unity UI System,你必须使用UnityEvent,如果你想序列化回调函数配置,你必须使用UnityEvent。

在其他情况下,只需使用 c# 内置事件。

【讨论】:

【参考方案3】:

在尚未讨论的软件设计方面,UnityEvents 优于原生 c#events

c# 中的原生 events are no 'first class objects。

您不能引用事件 您不能为事件编写扩展方法 您不能“从外部”调用事件

最后一个限制实际上是一个设计功能 - 如果您发现自己处于想要这样做的情况,可能您的软件架构搞砸了。 (例如,我正在开发一个具有“EventManager”的代码库,它只是一个仅托管几个事件的类,这些事件是从其他组件触发的——这是一个坏主意——但这远远超出了这篇文章。) 所以没有抱怨,这里的 c# 事件没有错。

但是,我最近多次遇到其他问题。 例如,UI 中的一个常见问题是,您想要一个事件的 OneShot 处理程序 - 即您想要为事件注册一个处理程序,该处理程序会在事件触发一次后自动删除。 例如,假设您在模态对话框中有一个按钮,该按钮应该是

    播放一些动画, 关闭对话框,然后 开始一些其他的序列或游戏逻辑。 也许你想在对话框打开时注册点击处理程序,比如
    Button.onClick.AddListener(OnButtonClicked);

但是,如果用户决定在 1. 动画期间再次按下按钮,您希望阻止单击处理程序再次执行。 如果这是一次性的事情,您只需从处理程序中的 click 事件中删除处理程序即可。

    Button.onClick.RemoveListener(OnButtonClicked);

我们数百个这样的案例在我们的代码库中也有很大不同。 远远不够,我真的很想能够写:

    Button.onClick.OneShot(OnButtonClicked);

为此实现扩展方法 - 以及其他类似的好东西 - 是

可能使用像 UnityEventUnityEvent<T> 这样的对象,但是 不可能使用原生 C# 事件。

我们为所有 Unity 事件添加了几个扩展方法,以便将我们现有的代码库转向更具反应性的风格。 话虽如此,但必须注意 UnityEvent 并不是最佳解决方案,原因有几个,这远远超出了这篇 SO 帖子的主题。 还是让我提一下,我们考虑使用像 UniRx 或 Rx.Unity 这样的库 - 如果反应式编程是您的团队共享的目标,那么做同样的事情可能会很聪明。

【讨论】:

您能否详细说明为什么这对于 C# 事件是不可能的?你不能打电话给Button.onClick -= OnButtonClicked吗? @lufinkey 我说过为此实现扩展方法是不可能的。这是不可能的,因为在 c# 中你不能为事件编写扩展方法。您也不能编写接收事件作为参数的方法 - 事件根本无法通过引用传递。如果对于为什么这是一个问题仍有疑问,请随时与我们联系。

以上是关于为啥选择 UnityEvent 而不是原生 C# 事件?的主要内容,如果未能解决你的问题,请参考以下文章

为啥 DateTime.Now 是属性而不是方法?

为啥微软用c、c++而不是c#来开发windows? [关闭]

为啥c#中的main方法总是放在类中而不是c++中

为啥我的 web api 在 C# 中使用 https 而不是 http

为啥 .NET API 浏览器示例是用 C++ 而不是 C# 编写的? [关闭]

一个整数而不是字符串?为啥? [复制]