Memory management and databindings
I was reading this article on MSDN Field Views on Windows Forms 1.0 and 1.1 Data Binding and bumped into a section about databinding and memory leaks. As you may know, I've struggled with this in my first .Net project. Steve White, the author of this article, explains what happens to datasource that are bound to controls and why they are not garbage collected. I have not really looked at how it works, and how to further implement the suggestions made here, but it gives you another example of how memory leaks in .Net can occur. Here is the text from that document:
Always Call DataBindings.Clear when Disposing
This is a habit that's worth getting into in order to avoid a problem with versions 1.0 and 1.1 of the .NET Framework. When disposing a data-bound Form, recurse into its Controls and clear their DataBindings collections. If you know which properties of which controls are bound, then there is a more selective method that I'll go into shortly.
The problem I mentioned has to do with bound objects not being garbage-collected. Technically speaking, this is not a memory leak because references on the objects are held by an internal .NET Framework class. However, given that the application code releases all of its references, for all intents and purposes, the objects are leaked for the lifetime of the application domain. Specifically, if an application constructs, shows and closes a Form on which a control is bound to an object, then the object will not be garbage collected unless bindings are cleared. If the object is a substantial one and has references to the form itself through events, then the Form will not be collected either. What happens is that, during binding, the bound object's property is examined through reflection and a reference to the object is placed in a ValueChanged event, which is indirectly cached in a static HashTable. The HashTable has to be explicitly cleared at some point, and a good time to do this is when the bound control's owning Form is disposed. Consider this binding statement:
C# code
label1.DataBindings.Add("Text", myObj, "Property");
Visual Basic .NET code
label1.DataBindings.Add("Text", myObj, "Property")
For single controls where the control's bound property is known, clearing the binding is as simple as using code similar to either:
C# code
label1.DataBindings.Clear();
Visual Basic .NET code
label1.DataBindings.Clear()
Or:
C# code
TypeDescriptor.Refresh(label1.DataBindings["Text"].DataSource);
Visual Basic .NET code
TypeDescriptor.Refresh(label1.DataBindings("Text").DataSource)
For a hierarchy of controls, use the same principle while recursing into the control tree. You should be aware of a further wrinkle when your navigation path refers to a property of a property. So, if you are binding like so:
C# code
label1.DataBindings.Add("Text", myObj, "Nested.Property");
Visual Basic .NET code
label1.DataBindings.Add("Text", myObj, "Nested.Property")
Then your disposing logic needs to be slightly more involved:
C# code
object ds = label1.DataBindings["Text"].DataSource;
label1.DataBindings.Clear();
TypeDescriptor.Refresh(ds);
Visual Basic .NET code
Dim ds As Object = label1.DataBindings("Text").DataSource
label1.DataBindings.Clear()
TypeDescriptor.Refresh(ds)
Again, recurse if you want to handle a control tree. To finish this section, I'll give the recommended method for performing a general clearing of bindings courtesy of Mike Taulty, a colleague in the DPE team whose customer first encountered this issue. This code covers all cases known, so although probably not the most performant solution, it is the safest and most general.
C# code
private void ClearBindings(Control c)
{
Binding[] bindings = new Binding[c.DataBindings.Count];
c.DataBindings.CopyTo(bindings, 0);
c.DataBindings.Clear();
foreach (Binding binding in bindings)
{
TypeDescriptor.Refresh(binding.DataSource);
}
foreach (Control cc in c.Controls)
{
ClearBindings(cc);
}
}
Visual Basic .NET code
Private Sub ClearBindings(ByVal c As Control)
Dim bindings(c.DataBindings.Count) As Binding
c.DataBindings.CopyTo(bindings, 0)
c.DataBindings.Clear()
For Each bind As Binding In bindings
System.ComponentModel. _
TypeDescriptor.Refresh(bind.DataSource)
Next
For Each cc As Control In c.Controls
ClearBindings(cc)
Next
End Sub