1

I am working with Unity3D's GUI system and have created a wrapper around the GUI methods (like GUI.Label(), in this case). However, my question is actually a general C# question.

Here is my GuiLabel class which wraps the functionality of Unity's GUI.Label method and also subscribes to an event of the singleton 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 is a long-living singleton object which is created at the first use of LanguageManager.Instance. GuiElementBase doesn't do much, it contains a Rect m_rect which represents the position of the label. It is NOT derived from MonoBehavior, it's just an ordinary class.

Here are my questions:

1) When to unsubscribe from OnChangeLanguage?

I don't know how I should handle unsubscribing from the LanguageManager's OnChangeLanguage event. I suppose, doing it in the destructor is OK if the GuiLabel gets destroyed BEFORE the LanguageManager?

However, what if the GuiLabel and the LanguageManager are destroyed at the same time which could happen if a GuiLabel is still being displayed/used and the user choses to quit the app. When should I unsubscribe from the event in this case?

Does it even make sense to access the LanguageManager.Instance in GuiLabel's destructor? Or could this do something terrible?

2) How to unsubscribe on the main thread?

The second question is more Unity-specific. The above code produces the following error message when the application is quit:

CompareBaseObjectsInternal can only be called from the main thread. Constructors and field initializers will be executed from the loading thread when loading a scene. Don't use this function in the constructor or field initializers, instead move initialization code to the Awake or Start function.

I understand it that Unity wants me do something on the main thread instead of another tread. The problematic line is null != LanguageManager.Instance in the destructor. Do I have to compare this instance on the main thread? How would I do that?

Soner Gönül
  • 97,193
  • 102
  • 206
  • 364
j00hi
  • 5,420
  • 3
  • 45
  • 82
  • 2
    If your GuiLabel class is subscribed to a static event, that is a strong reference, so it will never get garbage collected. Since it will never get garbage collected, the destructor will never get called, and your even will never be unsubscribed. You have a bit of a chicken and egg problem there. – vcsjones Dec 10 '14 at 15:51
  • Are you suggesting to let GuiLabel implement IDisposable and calling it's Dispose() method (which would unsubscribe from the event) when the GuiLabel is no longer needed? – j00hi Dec 10 '14 at 15:58
  • That would be one way to do it. I'm not familiar enough with Unity to know when or how is the best way to do that. – vcsjones Dec 10 '14 at 17:49
  • @j00hi: given that you've already implemented the finalizer, it doesn't seem a stretch to think that the object itself ought to implement `IDisposable`. It solves two problems at once: it gives callers a way to signal the object is no longer needed, and that it can unsubscribe itself from the event; and it gives you the opportunity to call `GC.SuppressFinalize()`, which is moot at the moment (since the object is never GC'ed) but will be pertinent once collection of the object is possible. – Peter Duniho Dec 10 '14 at 21:15

1 Answers1

0

My implementation of the IDisposable idea from @j00hi in the comments to the question after reading When/how is IDisposable.Dispose called?. Further validation from 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 ]
}

A monobehaviour should call Dispose in either OnDestroy or OnDisable:

private void OnDestroy()
{
    ((IDisposable)guiLabelInstance).Dispose(); // removes event handlers
}
ShawnFeatherly
  • 2,470
  • 27
  • 20