取消订阅析构函数中的事件 - Unity GUI系统包装器

时间:2014-12-10 15:49:03

标签: c# events unity3d destructor

我正在使用Unity3D的GUI系统,并创建了一个围绕GUI方法的包装器(如本例中的GUI.Label())。但是,我的问题实际上是一个普遍的C#问题。

这是我的GuiLabel类,它包含了Unity GUI.Label方法的功能,并且还订阅了单例LanguageManager.Instance的事件。

public sealed class GuiLabel : GuiElementBase
{
    #region private and protected variables
    protected string m_langMgrKey;
    protected GUIStyle m_style;

    protected string m_text; 
    #endregion

    public GuiLabel(Rect rect, string langMgrKey, GUIStyle style) : base(rect)
    {
        m_langMgrKey = langMgrKey;
        m_style = style;
        m_text = LanguageManager.Instance.GetTextValue(m_langMgrKey);
        LanguageManager.Instance.OnChangeLanguage += HandleOnChangeLanguage;
    }

    void HandleOnChangeLanguage (LanguageManager thisLanguageManager)
    {
        m_text = LanguageManager.Instance.GetTextValue(m_langMgrKey);
    }

    ~GuiLabel()
    {
        if (null != LanguageManager.Instance)
        {
            LanguageManager.Instance.OnChangeLanguage -= HandleOnChangeLanguage;
        }
    }

    public override void Draw ()
    {
        GUI.Label(m_rect, m_text, m_style);
        base.Draw ();
    }
}

LanguageManager是一个长寿单身对象,它是在LanguageManager.Instance首次使用时创建的。 GuiElementBase做得不多,它包含一个代表标签位置的Rect m_rect。它不是从MonoBehavior派生出来的,它只是一个普通的类。

以下是我的问题:

1)何时取消订阅OnChangeLanguage?

我不知道如何处理来自LanguageManager的OnChangeLanguage事件的取消订阅。我想,如果GuiLabel在语言管理器之前被破坏,那么在析构函数中执行它是否正常?

然而,如果GuiLabel和LanguageManager同时被破坏,如果仍然显示/使用GuiLabel并且用户选择退出应用程序,则会发生这种情况。在这种情况下,我什么时候应该取消订阅活动?

在GuiLabel的析构函数中访问LanguageManager.Instance是否有意义?或者这可能做得很糟糕?

2)如何在主线程上取消订阅?

第二个问题更多是针对Unity的。退出应用程序时,上面的代码会生成以下错误消息:

CompareBaseObjectsInternal只能从主线程调用。 加载场景时,将从加载线程执行构造函数和字段初始值设定项。 不要在构造函数或字段初始值设定项中使用此函数,而是将初始化代码移动到Awake或Start函数。

我理解Unity希望我在主线程上做一些事情,而不是另一个步骤。析构函数中有问题的行是null != LanguageManager.Instance。我是否必须在主线程上比较此实例?我该怎么做?

1 个答案:

答案 0 :(得分:0)

我在阅读When/how is IDisposable.Dispose called?后对评论中的@ j00hi实施了IDisposable的想法。来自https://forum.unity3d.com/threads/unsubscribing-from-c-events-when-gameobject-is-destroyed.224069/#post-1493692

的进一步验证
public class GuiLabel : GuiElementBase, IDisposable
{
    #region [ IDisposable Support ]
    private bool disposedValue = false; // To detect redundant calls

    protected virtual void Dispose(bool disposing)
    {
        if (!disposedValue)
        {
            if (disposing)
            {
                // dispose managed state (managed objects).
                LanguageManager.Instance.OnChangeLanguage -= HandleOnChangeLanguage;
            }
            disposedValue = true;
        }
    }

    // This code added to correctly implement the disposable pattern.
    void IDisposable.Dispose()
    {
        // Do not change this code. Put cleanup code in Dispose(bool disposing) above.
        Dispose(true);
    }
    #endregion [ IDisposable Support ]
}

monobehaviour应该在OnDestroy或OnDisable中调用Dispose:

private void OnDestroy()
{
    ((IDisposable)guiLabelInstance).Dispose(); // removes event handlers
}