Click here to Skip to main content
15,867,141 members
Articles / Desktop Programming / WPF
Article

Challenge: How many memory leaks can you find?

1 Feb 2015CPOL6 min read 33K   27  
MVP Rainer Stropek sets .NET developers a fun challenge. Here is a sample application with 3 memory leaks. Can you find them?

This article is in the Product Showcase section for our sponsors at CodeProject. These articles are intended to provide you with information on products and services that we consider useful and of value to developers.

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:

Image 1

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:

XAML
<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.

C#
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);

			// Setup DI container for executing assembly
			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);
			// Export print menu via MEF so that all views can subscribe to click event.
			((App)Application.Current).Container.ComposeExportedValue<MenuItem>("PrintMenuItem", this.PrintMenuItem);
		}

		private void OnCreateNewTab(object sender, RoutedEventArgs e)
		{
			// Note that we use MEF to create instance here.
			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)
			{
				// Remove selected tab
				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

  1. The class implements IDisposable correctly, because it contains a reference to DbContext and that class implements IDisposable.
  2. 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 class so that it can be created by MEF
	[Export("CustomerView", typeof(UserControl))]
	[PartCreationPolicy(CreationPolicy.NonShared)]
	public partial class CustomerControl : UserControl, IPartImportsSatisfiedNotification, 
		IDisposable // Note that class has to implement IDisposable as it contains a member 
					// (repository) that implements IDisposable, too.
	{
		private CustomerRepository repository = new CustomerRepository();

		[Import("PrintMenuItem")]
		private MenuItem PrintMenuItem;

		public CustomerControl()
		{
			InitializeComponent();

			// For simplicity we do not implement full MVVM here. Note that you should
			// use MVVM in practice when working with XAML.
			this.DataContext = this;
		}

		public IEnumerable<Customer> Customers
		{
			get
			{
				// In practice we would find more complex data access logic. In this simple
				// sample we just select all existing customers.
				return this.repository.Customers.ToArray();
			}
		}

		public void OnImportsSatisfied()
		{
			// Connect to click event in main menu to "print" this item.
			this.PrintMenuItem.Click += (s, ea) => Debug.WriteLine("Printing {0} ...", this);
		}

		// Implementation of IDisposable
		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)
{
    //Populate the database with some test data
    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.

Image 2

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:

Image 3

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:

Image 4

This shows us the objects that keep a reference to CustomerControl. Things become clear: it looks like the MEF CompositionContainer is the culprit.

Image 5

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
	{
		[...]
		// Dictionary to remember exports that led to view objects
		private Dictionary<UserControl, Lazy<UserControl>> exports = new Dictionary<UserControl, Lazy<UserControl>>();

		private void OnCreateNewTab(object sender, RoutedEventArgs e)
		{
			// Get the export that can be used to generate a new instance
			var viewExport = currentApp.Container.GetExport<UserControl>("CustomerView");
			// Store the export and the generated instance
			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;

				// Remove selected tab
				this.Content.Items.Remove(selectedTabItem);
				// Release the export; releases the created instance, too
				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.

Image 6

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()
		{
			// Connect to click event in main menu to "print" this item.
			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:

Image 7

Image 8

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:

Image 9

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.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Austria Austria
Rainer is a Windows Azure MVP and the co-founder and CEO of Software Architects where he and his team are developing the award-winning SaaS solution Time Cockpit. In his work, Rainer focuses on .NET development and software architecture and he has written a number of books and articles on C#, database development, Windows Azure, WPF, etc. He also speaks regularly at conferences and conduct trainings across Europe and the US and runs the Time Cockpit blog.

Comments and Discussions