Introduction
The .NET Garbage Collector sometimes gives the impression that .NET developers don’t need to take care of memory usage. In practice, this is not the case. A little carelessness and you have an application with lots of memory leaks.
For this article, I have put together a tiny WPF application with just 32 lines of C# code, in which some memory leaks are hidden. The WPF code is not optimal and lacks proper separation according to MVVM principles. I have deliberately kept it so because it reflects what I regularly see in workshops and training sessions.
Can you find the memory leaks in it?
The sample application
The sample program consists of a window with a main menu and a Tab Control. In the latter, you can open customer lists. You could perhaps later add other lists to it (Products, Orders, etc). The customer list consists of a column with their name and a column with their details.
The following screenshot shows the user interface of the sample program:
The code
GitHub
The code is available for download from this GitHub Repository
Data access
Data access is handled using Entity Framework. Here is the class that represents a customer
namespace WpfApplication19
{
public class Customer
{
[Key]
public string FirstName { get; set; }
public string LastName { get; set; }
}
}
This class is used in the Entity Framework DbContext:
using System;
using System.Data.Entity;
namespace WpfApplication19
{
class CustomerRepository : DbContext
{
public CustomerRepository() : base(
"Server=(localdb)\\v11.0;Database=DemoCrm;Integrated Security=true")
{ }
public DbSet<Customer> Customers { get; set; }
}
}
The main window
The XAML code of the main window contains only the menu and the Tab Control:
<Window x:Class="WpfApplication19.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<DockPanel>
<Menu DockPanel.Dock="Top" Name="MainMenu">
<MenuItem Header="Open new tab" Click="OnCreateNewTab" />
<MenuItem Header="Close tab" Click="OnCloseTab" />
<MenuItem Header="Print" Name="PrintMenuItem" />
</Menu>
<TabControl Name="Content"/>
</DockPanel>
</Window>
The application‘s developer had planned to represent more than just customer lists using Managed Extensibility Framework (MEF), so he is using a CompositionContainer
in the OnStartup
method.
using System.ComponentModel.Composition.Hosting;
using System.Reflection;
using System.Windows;
namespace WpfApplication19
{
public partial class App : Application
{
public CompositionContainer Container { get; private set; }
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
this.Container = new CompositionContainer(
new AssemblyCatalog(Assembly.GetExecutingAssembly()));
}
}
}
MEF is used for the main window‘s code data in order to create the customer lists:
using System.ComponentModel.Composition;
using System.Windows;
using System.Windows.Controls;
namespace WpfApplication19
{
public partial class MainWindow : Window
{
private App currentApp;
public MainWindow()
{
InitializeComponent();
this.currentApp = ((App)Application.Current);
((App)Application.Current).Container.ComposeExportedValue<MenuItem>("PrintMenuItem", this.PrintMenuItem);
}
private void OnCreateNewTab(object sender, RoutedEventArgs e)
{
var view = this.currentApp.Container.GetExportedValue<UserControl>("CustomerView");
this.Content.Items.Add(
new TabItem()
{
Header = "Customers",
Content = view
});
}
private void OnCloseTab(object sender, RoutedEventArgs e)
{
var selectedView = this.Content.SelectedItem as TabItem;
if (selectedView != null)
{
this.Content.Items.Remove(selectedView);
}
}
}
}
The customer list
Now the customer list. Again the XAML code uses a simple structure:
<UserControl x:Class="WpfApplication19.CustomerControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<DockPanel>
<ListBox Name="CustomersList" ItemsSource="{Binding Path=Customers}"
DisplayMemberPath="LastName" DockPanel.Dock="Left" MinWidth="250" />
<Grid Margin="5">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<TextBlock Text="Details:" FontSize="15" />
<TextBlock Text="{Binding ElementName=CustomersList, Path=SelectedItem.FirstName}"
Grid.Row="1" Margin="0,10,0,0" />
<TextBlock Text="{Binding ElementName=CustomersList, Path=SelectedItem.LastName}"
Grid.Row="2" />
</Grid>
</DockPanel>
</UserControl>
The associated code includes two important aspects
- The class implements
IDisposable
correctly, because it contains a reference to DbContext
and that class implements IDisposable
.
- MEF is being used to obtain a reference to the main menu and hook it up to the appropriate event handlers.
Here is the code:
using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.Diagnostics;
using System.Linq;
using System.Windows.Controls;
namespace WpfApplication19
{
[Export("CustomerView", typeof(UserControl))]
[PartCreationPolicy(CreationPolicy.NonShared)]
public partial class CustomerControl : UserControl, IPartImportsSatisfiedNotification,
IDisposable {
private CustomerRepository repository = new CustomerRepository();
[Import("PrintMenuItem")]
private MenuItem PrintMenuItem;
public CustomerControl()
{
InitializeComponent();
this.DataContext = this;
}
public IEnumerable<Customer> Customers
{
get
{
return this.repository.Customers.ToArray();
}
}
public void OnImportsSatisfied()
{
this.PrintMenuItem.Click += (s, ea) => Debug.WriteLine("Printing {0} ...", this);
}
public void Dispose()
{
this.Dispose(true);
}
private void Dispose(bool disposing)
{
if (disposing)
{
this.repository.Dispose();
GC.SuppressFinalize(this);
}
}
}
}
That’s it – have you spotted the memory leaks?
If you want to experiment with the sample application, go ahead. Do not forget to enter some test data sets, and that you may need to update the database connection string to do so. If you continue using localdb, the easiest way to populate some test data would be to add code like the following to the OnStartup
method:
[...]
protected override void OnStartup(StartupEventArgs e)
{
using (var context = new CustomerRepository())
{
context.Customers.Add(new Customer { FirstName = "Ted", LastName = "Jones" });
context.Customers.Add(new Customer { FirstName = "Jeremy", LastName = "Hugo" });
context.Customers.Add(new Customer { FirstName = "Sarah", LastName = "Higgins" });
context.Customers.Add(new Customer { FirstName = "Fiona", LastName = "Wells" });
context.SaveChanges();
}
base.OnStartup(e);
[...]
How to detect memory leaks
The sample application contains three serious programming errors that lead to memory leaks. An experienced WPF developer can probably find them purely by viewing the problematic code, but most business applications contain way too many lines of code to go through them all. Getting support from a tool is usually the right approach.
As a software architect, I would tend to use a tool like ANTS Memory Profiler from Red Gate. If you want to play with the sample application, just download a free trial of the memory profiler.
The answer to the riddle – hunting memory leaks
Now I’ll show you how you can detect the memory leaks and how to correct the errors.
First, start ANTS Memory Profiler and select the application to be analyzed. Take a first Memory Snapshot which we’ll use as a baseline.
Then we open, for example, three records with Open new tab; we then close the three tabs and we take a second snapshot. If the memory is released properly, there should be no objects left in memory.
Now that we have two snapshots, we can easily compare them by clicking on Class list and filtering by Classes with source:
It is immediately apparent that there are three CustomerControl
objects in our second snapshot even though they should have been released. Why is that?
MEF and IDisposable
This is where the Instance Categorizer helps:
This shows us the objects that keep a reference to CustomerControl
. Things become clear: it looks like the MEF CompositionContainer is the culprit.
The reason for this memory leak is that the MEF CompositionContainer
references CustomerControl
objects that implement IDisposable
, and those references will exist until the CompositionContainer
is released (it implements IDisposable
itself). In our case, however, the CompositionContainer
survives until the application terminates, so the CustomerControl
objects are never freed. One memory leak located. But how can we eliminate it?
There are several solutions. One solution is to use the CompositionContainer.ReleaseExport() method. The following modification to MainWindow.xaml.cs solves the problem:
[...]
namespace WpfApplication19
{
public partial class MainWindow : Window
{
[...]
private Dictionary<UserControl, Lazy<UserControl>> exports = new Dictionary<UserControl, Lazy<UserControl>>();
private void OnCreateNewTab(object sender, RoutedEventArgs e)
{
var viewExport = currentApp.Container.GetExport<UserControl>("CustomerView");
this.exports.Add(viewExport.Value, viewExport);
this.Content.Items.Add(
new TabItem()
{
Header = "Customers",
Content = viewExport.Value
});
}
private void OnCloseTab(object sender, RoutedEventArgs e)
{
var selectedTabItem = this.Content.SelectedItem as TabItem;
if (selectedTabItem != null)
{
var selectedView = selectedTabItem.Content as UserControl;
this.Content.Items.Remove(selectedTabItem);
currentApp.Container.ReleaseExport(this.exports[selectedView]);
this.exports.Remove(selectedView);
}
}
}
}
Event Handler
Now that we have resolved the IDisposable
problem, the CustomerControl
object should be released correctly. We repeat the test described above and see that this is still not the case.
ANTS Memory Profiler shows us that MenuItem
holds an indirect reference to our CustomerControl
object. By handling the Click Events for the Menu item, a reference is created from the Menu item to the CustomerControl
object. Unfortunately, we do not unregister the event handlers and that’s where the memory leak comes from.
The solution in this simple example: the event handlers must be unregistered. In practice however, this often presents a challenge, since it is not always clear where this can be done. In this example, we subscribed to the event handler using an anonymous method, making it difficult to unsubscribe. We either need to store the anonymous method as a delegate, or, as we choose to do here, extract the anonymous method to a method called Print
and register that instead, allowing us to unregister it later.
Here is the code for the solution to the second memory leak:
namespace WpfApplication19
{
[...]
public void OnImportsSatisfied()
{
this.PrintMenuItem.Click += this.Print;
}
private void Print(object sender, RoutedEventArgs ea)
{
Debug.WriteLine("Printing {0} ...", this);
}
private void Dispose(bool disposing)
{
if (disposing)
{
this.PrintMenuItem.Click -= this.Print;
this.repository.Dispose();
GC.SuppressFinalize(this);
}
}
}
}
Data Bindings without INotifyPropertyChanged
Now let’s take a look at the Customer
objects. Why do they stay in memory? The CustomerControl
memory leak also keeps the Customer
objects in memory of course, but is that all? Or is there another culprit?
This time, we want to look at an individual instance of a Customer
object using the Instance List:
By using the Retention Graph, you can see exactly what references each object. And indeed, there are more references to the Customer
instance than just from CustomerControl
:
The names of the classes provide information about the underlying problem. In this case, we have created Two Way Bindings in the Customer
class which are not implemented with INotifyPropertyChanged
. Not a good idea in WPF – a reference is held to all of these objects. The Customer list in the property CustomerControl.Customers
is also not right. The Two Way Binding requires the implementation of INotifyCollectionChanged
. Memory leak number three found.
This problem is solved by implementing INotifyPropertyChanged
:
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Runtime.CompilerServices;
namespace WpfApplication19
{
public class Customer : INotifyPropertyChanged
{
private string FirstNameValue;
[Key]
public string FirstName
{
get { return this.FirstNameValue; }
set
{
if (this.FirstNameValue != value)
{
this.FirstNameValue = value;
this.RaisePropertyChanged();
}
}
}
private string LastNameValue;
public string LastName
{
get { return this.LastNameValue; }
set
{
if (this.LastNameValue != value)
{
this.LastNameValue = value;
this.RaisePropertyChanged();
}
}
}
private void RaisePropertyChanged([CallerMemberName]string propertyName = null)
{
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
}
Conclusion
Memory leaks are more frequent in .NET than most beginners believe. A library used incorrectly or an event handler not removed properly quickly leads to a memory leak.
Without a memory profiler like ANTS Memory Profiler it‘s only really possible to solve problems in small applications. If you are working on a larger commercial application, then a professional profiler is likely to be a good investment.