【问题标题】:Delegate.Combine: How to Check if multicast (combinable) delegates already has a delegate inside of it?Delegate.Combine:如何检查多播(可组合)委托中是否已经有委托?
【发布时间】:2022-01-17 08:39:11
【问题描述】:

我正在使用 Unity 3D,但是,该信息对于解决此问题应该无关紧要,因为核心问题是 System.Delegate(我想让您知道,因为我将链接到一些 Unity 文档以进行澄清)。

我有一个自定义窗口,它具有自定义更新功能DirectorUpdate。无论用户/窗口在做什么,我都需要这个函数来运行每个编辑器更新。

为了在每次编辑器更新时调用它,我将我的方法与 Delegate EditorApplication.update 结合起来:

protected void OnEnable()
{
    // If I do the below, base EditorApplication.update won't be called anymore.
    // EditorApplication.update = this.DirectorUpdate;
    // So I need to do this:
    EditorApplication.update = (EditorApplication.CallbackFunction)System.Delegate.Combine(new EditorApplication.CallbackFunction(this.DirectorUpdate), EditorApplication.update);

    ... // Other stuff
}

请注意,这是在窗口的 OnEnable 内完成的。

问题在于 OnEnable 在一次运行期间可能会被多次调用(例如,在单个编辑器会话期间关闭窗口然后重新打开窗口时)导致强>

EditorApplication.update = (EditorApplication.CallbackFunction)System.Delegate.Combine(new EditorApplication.CallbackFunction(this.DirectorUpdate), EditorApplication.update);

被多次调用,意味着我的更新方法(this.DirectorUpdate)每次更新最终都会被多次调用,这会导致一些严重的错误。

所以, 问题是我如何检查EditorApplication.update 是否已经有我的方法“内部”。 (在里面,我当然是说它已经是System.Delegate.Combine(d) 给代表了。)

我知道可能还有其他解决方案,例如在窗口关闭时将 EditorApplication.update 恢复到之前的状态,但这并不适用于所有情况(例如,在窗口刷新期间也会调用 OnEnable 和这样)并且该错误将持续存在。 (另外,如果另一个窗口在此窗口打开时与 EditorApplication.update 连接怎么办?)因此,最好的解决方案是在 Delegate.Combine 之前检查 EditorApplication.update 是否已经调用此方法。

【问题讨论】:

    标签: c# unity3d delegates concatenation multicastdelegate


    【解决方案1】:

    我认为你走的是复杂的道路;)

    订阅和取消订阅事件和委托就像使用运算符 +=-= 一样简单

    protected void OnEnable()
    {
        // You can substract your callback even though it wasn't added so far
        // This makes sure it is definitely only added once (for this instance)
        EditorApplication.update -= DirectorUpdate;
        
        // This basically internally does such a Combine for you
        EditorApplication.update += DirectorUpdate;
        
        ... // Other stuff
    }
    
    private void OnDisable()
    {
        // Remove the callback once not needed anymore
        EditorApplication.update -= DirectorUpdate;
    }
    

    这样您还可以打开此窗口的多个实例,它们都将分别接收回调。


    顺便说一句,如果这实际上是关于 EditorWindow 那么 afaik 你不应该使用 OnEnabled 但你宁愿使用 Awake

    在新窗口打开时调用。

    OnDestroy

    【讨论】:

    • 当,我对这个答案充满希望;但是,我只是对其进行了测试,但它似乎不起作用:关闭并重新打开窗口后,错误仍然存​​在。此外,JetBrains 骑手给了我这个警告:“委托减法有不可预测的结果”。
    • 请原谅我的最后一条评论 - 它似乎确实有效! (我犯了一个小错误)但是,似乎 OnDisable() 部分对于它的工作是绝对必要的(我猜是因为否则旧窗口的“实例”不会被删除 om 委托减法)。我将对此进行进一步测试并报告,可能会给这个答案打勾:)
    • @danglingPointer 完全是 ;) 正如所说,一旦不再需要,总是删除回调。如果窗口关闭但您不删除回调,您将收到 NullReferenceExceptions,因为它仍会尝试从不再存在的对象调用实例化方法
    【解决方案2】:

    我不熟悉System.Delegate.Combine(d) 的作用,但您可以考虑代替启用/禁用您的窗口,每次都销毁和实例化它,并将您的代码移动到StartAwake 以便它每个窗口“激活”只调用一次。

    最后但同样重要的是,在 OnDisable 中使用强大的布尔值,以便在组件被禁用时处理组合执行。像这样:

    bool omgIWasDisabled;
    protected void OnEnable()
    {
        if (!omgIWasDisabled) {
            EditorApplication.update = (EditorApplication.CallbackFunction)System.Delegate.Combine(new EditorApplication.CallbackFunction(this.DirectorUpdate), EditorApplication.update);
        }
       
        ... // Other stuff
    }  
    
    void OnDisable() {
        omgIWasDisabled = true;
    }
    

    希望这些都能解决。

    【讨论】:

    • 您可以通过将 bool 'omgIWasAdded' 设为静态并在 OnEnable() 中的主代码之后将其设置为 true(而不必处理 OnDisable())来简化此操作,无论如何我想到了这个解决方案,它很可能就是我得到的。
    猜你喜欢
    • 2011-01-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-06-01
    相关资源
    最近更新 更多