I have a CF application that over time leaks UserControls. It took some time, but I narrowed it down, and even replicated the behavior in the full framework (3.5). Since the behavior exists in both, I don't want to call it a bug, but I sure don't understand why it's happening and hope someone can shed some light on it.
So I create a simple WinForms app with a Form and a Button. Clicking on the Button alternates between creating a new UserControl and Disposing that control. Very simple.
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
UserControl1 m_ctl;
private void button1_Click(object sender, EventArgs e)
{
if (m_ctl == null)
{
m_ctl = new UserControl1();
m_ctl.Visible = true;
this.Controls.Add(m_ctl);
}
else
{
this.Controls.Remove(m_ctl);
m_ctl.Dispose();
m_ctl = null;
GC.Collect();
}
}
}
And here's the UserControl. It simply tracks the number of live (i.e. not finalized) instances. It has nothing on it but a single label so I can visually confirm it's on the Form.
public partial class UserControl1 : UserControl
{
private static int m_instanceCount = 0;
public UserControl1()
{
var c = Interlocked.Increment(ref m_instanceCount);
Debug.WriteLine("Instances: " + c.ToString());
InitializeComponent();
}
~UserControl1()
{
var c = Interlocked.Decrement(ref m_instanceCount);
Debug.WriteLine("Instances: " + c.ToString());
}
}
The strange thing here is that the instance count grows indefinitely. Eventually, on the device, I run out of memory. I suspect I would on the PC as well, I'm just not inclined to click the button for the next year.
Now if I alter the UserControl's default, designer-generated Dispose method like this, simply adding the ReRegisterForFinalize call:
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
if (disposing)
{
GC.ReRegisterForFinalize(this);
}
}
Then it behaves exactly as expected, Finalizing instances during collection (when manual or automatic).
So why is this happening? Evidently the base is calling SuppressFinalize, but exactly why would this be happening, and why in the name of Odin is it the default behavior?