Memory Leak in ToolStripTextBoxControl

NOTE: This article was originally published as a post in the .NET and Memory blog (by Andreas Suurkuusk, co-founder of SciTech Software AB).

Occasionally we get e-mails about potential memory leaks that a user of .NET Memory Profiler have identified using the profiler, but where they have not been able find a solution to the problem.

What we normally do then is to request a profiler file that contains data from a session that exhibits the memory problem. Using the session file we can hopefully pinpoint what the problem is, and suggest a solution for it.

Sometimes the memory problem is related to a bug in the .NET Framework. In this post I’m going to describe one such problem and present a workaround for it.

In this case I received a session file that contained a large amount of disposed instances, which clearly indicates that there is a memory leak in the application. Without knowing too much about the application, I investigated one of the instances that were disposed. From the Types/Resources view I selected a Type that had disposed instances, and then double-clicked one of the disposed instances. Below is a root path (the only identified) for a disposed Microsoft.Reporting.WinForms.ReportViewer instance.

The root paths show that the instance are kept alive by an EventHandler (UserPreferencesChangedEventHandler), which immediately should draw your attention. A disposed instance should normally not be in use anymore, and should therefore not need to be notified about external events.

Note that this only applies to external events. For instance, if you have a form that is referenced by a child control via an event handler (e.g. TextBox.TextChanged), this is probably not causing a memory leak.

So the UserPreferencesChangedEventHandler is probably involved in this memory leak. By double-clicking the EventHandler instance (#150,722), I can see how it was created.

The EventHandler was created in the ToolStripTextBoxControl.HookStaticEvents method. Investigating ToolStripTextBoxControl using Reflector reveals the following:

  • ToolStripTextBoxControl is an internal class that is used to put a TextBox in a ToolStrip
  • HookStaticEvents is called from OnVisibleChanged (as can be seen in the allocation stack above).
  • If the ToolStripTextBoxControl is Visible, a new event handler will be added to SystemEvents.UserPreferencesChanged
  • If the ToolStripTextBoxControl is not Visible (or if it is being Disposed), and if an event handler has been added, the event handler will be removed from SystemEvents.UserPreferencesChanged.

The thing to be noted in the above list is that a new event handler is ALWAYS added when the Control becomes Visible, the HookStaticEvents method does not check whether the event handler has already been added (which it does before removing it). As I mentioned in another article, when a Control is part of a container, the OnVisibleChanged event will only be invoked when the Control becomes visible, not when it is hidden. So when the ToolStripTextBoxControl becomes hidden it will not be notified about this, but when it becomes visible again another event handler will be added. The Dispose method of ToolStripTextBoxControl will remove the event handler, but only one event handler will be removed. If the ToolStripTextBoxControl has become Visible more than once (e.g. if it’s part of a TabPage), this will not be enough, since more than one event handler will exist.

It could also be noted that removing the event handler in Dispose should not have been necessary at all, if the VisibleChanged event had been raised correctly.

In this case the ToolStripTextBoxControl was part of ToolStrip that was used by the ReportViewer, which in turn was part of a TabPage. Since the ToolStripTextBoxControl is a child of a TabPage, it is very probable that its visibility may change several times, thus creating a memory leak.

In order to fix this memory leak I wrote a small utility class (ToolStripTextBoxDisposeHelper) that tracks the number of times the event handler has been added in HookStaticEvents (by listening to the VisibleChanged event of the ToolStripTextBoxControl ). When the ToolStripTextBoxControl is disposed the UserPreferencesChangedEventHandler will be removed as many times as is necessary. Unfortunately this makes use of reflection, so it will not work in application that is not running under full trust.

At the end of the post you will find the code for the ToolStripTextBoxDisposeHelper class.

Using the helper class is pretty simple. After initializing the ToolStrip (e.g. after InitializeComponent has been called) a new instance of ToolStripTextBoxDisposeHelper should be created for each ToolStripTextBox item. Or the helper method CreateToolStripDisposeHelpers can be used. It will iterate over all items in the ToolStrip and create a ToolStripTextBoxDisposeHelper for each ToolStripTextBox found.

If you have experienced this memory leak I hope that this workaround is useful to you.

Finally, I just want to mention another memory leak that is related to the VisibleChanged event and ToolStrips. This memory leak occurs due to another peculiarity regarding the VisibleChanged event; when a removed control is disposed the VisibleChanged event will be raised (and the Visible property will return true). This can cause several UserPreferencesChanged event handlers to be added, which will never be removed (since the control is already disposed).

For more information about this problem, see the forum topic at:

http://forum.memprofiler.com/viewtopic.php?t=1042

At the end of the topic a short description of the problem is provided.


/// <summary>
/// Utility class for properly disposing ToolStripTextBoxes.
/// </summary>
sealed class ToolStripTextBoxDisposeHelper
{
  TextBox m_textBox;
  int m_visibleCount;          

  /// <summary>
  /// Helper method for creating a ToolStripTextBoxDisposeHelper
  /// for erach ToolStripTextBox in a ToolStrip.
  /// </summary>
  /// <param name="toolStrip">A ToolStrip containing ToolStripTextBoxes
  /// that should be tracked</param>
  public static void CreateToolStripDisposeHelpers( ToolStrip toolStrip )
  {
    foreach( ToolStripItem item in toolStrip.Items )
    {
      ToolStripTextBox toolStripTextBox = item as ToolStripTextBox;
      if( toolStripTextBox != null )
      {
        new ToolStripTextBoxDisposeHelper( toolStripTextBox );
      }
    }
  }          

  /// <summary>
  /// Initializes a new ToolStripTextBoxDisposeHelper.
  /// </summary>
  /// <remarks>
  /// Creating a new ToolStripTextBoxDisposeHelper will track
  /// visibility of the provided ToolStripTextBox and make sure
  /// that it is correctly disposed. There is no
  /// need to store a reference to the created instance. The
  /// helper will be kept alive by event handlers in the
  /// ToolStripTextBox.
  /// </remarks>
  /// <param name="textBox">The ToolStripTextBox to track.</param>
  public ToolStripTextBoxDisposeHelper( ToolStripTextBox textBox )
  {
    m_textBox = textBox.TextBox;
    m_textBox.VisibleChanged += new EventHandler( textBox_VisibleChanged );
    m_textBox.Disposed += new EventHandler( textBox_Disposed );
  }          

  void textBox_Disposed( object sender, EventArgs e )
  {
    // Remove all but one event handlers, since
    // the last one will be removed in
    // ToolStripTextBox.ToolStripTextBoxControl.Dispose.
    try
    {
      UserPreferenceChangedEventHandler eventHandler =
        (UserPreferenceChangedEventHandler)Delegate.CreateDelegate(
          typeof( UserPreferenceChangedEventHandler ),
          m_textBox, "OnUserPreferenceChanged" );
      for( int i = 0; i < m_visibleCount - 1; i++ )
      {
        SystemEvents.UserPreferenceChanged -= eventHandler;
      }
    }
    catch( MissingMethodException )
    {
      // The UserPreferencesChanged implementation in the framework has
      // changed. Hopefully this memory leak has been fixed and we can
      // ignore that this hack didn't succeed.
    }
    catch( MemberAccessException )
    {
      // We don't have permissions to fix this problem using reflection.
      // Handle this somehow (currently we just rethrow the exception)
      throw;
    }
  }          

  /// <summary>
  /// Count the number of times the TextBox has become visible
  /// (according to VisibleChanged)
  /// </summary>
  /// <param name="sender"></param>
  /// <param name="e"></param>
  void textBox_VisibleChanged( object sender, EventArgs e )
  {
    if( m_textBox.Visible )
      m_visibleCount++;
    else
      m_visibleCount--;
  }
}

Download .NET Memory Profiler to see how it can help you find memory leaks and optimize memory usage in your application.

Download Free Trial

© Copyright 2001-2023. SciTech Software AB
All rights reserved.

CONTACT INFO

SciTech Software AB
Ynglingavägen 1-3
SE-177 57 Järfälla
Sweden

E-mail: mail@scitech.se
Telephone: +46-706868081