.NET / WPF Memory Leaks
This time I will try to cover few interesting issues that we can meet when we are creating WPF application.
Most of .NET developers are aware of the fact, that .NET is not perfect and could also cause memory leaks. I will show you few of them from the perspective of WPF application and how to prevent those.
1. Commands
When/WHy leaking
We use e.g. ICommand instance in ViewModel, to create binding in Views quite often. It’s almost default snippet for delegating View action to ViewModel in MVVM pattern. We create then “on demand” properties for them with getters, like below I used RelayCommand implementation of interface ICommand to define a command.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
public RelayCommand MyGreatCommand { get { return this.myGreatCommand ?? ( this.myGreatCommand = new RelayCommand( () => { // some code here }); ); } } |
It is very good solution and good way to create command only when the View part that is using it exists and binds to it.
What happens, when we remove this visual from UI (which is using such command)? Nothing special, visual element is removed, because binding contains weak reference to command. But the command still leaves in ViewModel. If we try to remove ViewModel from parent navigator, clean it up somehow, all it’s logic is removed, but those commands are not. Why? Because we didn’t inform GC to make it available for garbage collection, we need to set them to null – you probably knew that, but forgot about it 🙂
Another annoying side effect of this is that this command can keep other objects references in memory preventing them from being garbage collected. If you e.g. use some method this.MyExampleMethod() inside the body of command handler you may cause in a situation when you removed ViewModel reference from memory, but it is still kept by this command and this can keep another references and so on. That is why it’s so important. Delegates memory leaks are really small – sever dozen of bytes, but it is still a memory leak.
How to prevent
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
using GalaSoft.MvvmLight; using GalaSoft.MvvmLight.Command; internal class TestViewModel : ViewModel { private RelayCommand myGreatCommand; // ... some stuff here public override void Cleanup() { base.Cleanup(); this.myGreatCommand = null; GC.Collect(); } } |
I’ve used here MVVM Light framework, but it is just an example.
2. Event handlers
When/Why leaking
I think that you surely know about this, but just to cover all situations, I’ve added also this case. When we use event handlers we typically use delegates and set of handlers for it. Example?
1 2 3 |
this.scrollBar.ValueChanged += this.OnScrollValueChanged; |
As you can see, we add new handler that is a method in current class. And it is simple, when we won’t detach this method from delegate we will get memory leak. The reference to this event will be there, even . This is especially dangerous when we are recreating this event in a view model or control which is a part of collection. The items in collection are added and removed and we are not controlling when we should detach handlers. On the other hand, in such situations memory leaks are easy to observe e.g. in windows task manager.
How to prevent
It’s easy, always remember to detach handler from delegate.
1 2 3 |
this.scrollBar.ValueChanged -= this.OnScrollValueChanged; |
info_outlineThere is one very important thing to mention when we are talking about handlers. You can observe sometimes that you have different behaviors and errors regarding memory between Debug mode and Release mode. Good example is below:
1 2 3 4 5 |
this.ExampleEvent += (s, e) => { // some code of handler here }; |
In such cases normally in Release mode we shouldn’t observe memory leaks, but in Debug we can. That’s because of code optimization. In Release mode (if opttimization is enabled) lambdas are wrapped in special way so that they are created as a part of parent class. If parent class is deallocated, same happens with lambda handlers. But in Debug that depends on optimization settings.
3. Non notifiable source of binding
When/Why leaking
Some times we may forget to inherit our Binding source class from a class implementing INotifyPropertyChanged or to implement it directly in our class. Let’s take a look on the code below:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
public class ListItem { public string Name {get; set;} public bool IsSelected {get; set;} } // then example usage in a ViewModel public class MyViewModel: ViewModelBase { private ListItem myItem; public ListItem MyItem { get { return this.myItem; } private set { this.myItem = value; this.RaisePropertyChanged(() => this.MyItem); } } } |
If we use this item in e.g. ItemsControl template of item or anywhere in View e.g. like this:
1 2 3 4 5 6 |
<CheckBox DataContext="{Binding MyItem}" Content="{Binding Name}" IsChecked="{Binding IsSelected}" /> |
We will get the CheckBox updated when we will update MyItem property. But what about bindings to IsSelected and Name properties of this object. First of all, Binsing mechanism need a source object to Bind from, so if it is not a DependencyProperty or object implementing INotifyPropertyChanged interface, WPF must create a special object for it of type System.ComponentModel.PropertyDescriptor and use its ValueChanged event as a source of notifications, which must be created in runtime. The problem is that there is no event/trigger that will notify GC that this object should be removed from memory. Even when we remove control in UI, ViewModel in backend, the reference still exists. That causes memory leaks.
How to prevent
If we don’t use a valid implementation of INotifyPropertyChanged in our source object class (here ListItem ), we can always clear this property by setting explicit null to property MyItem in notifiable ViewModel . That will cause our object to be garbage collected. Simple, but we sometimes forgot about those nuances.
The only exception of this problem is when we are using Mode=OneTime binding. Because in such cases the source object values are taken only once and there is no need to create any property descriptors in runtime for that.
4. Removing x:Name’d controls from C# code
When/Why leaking
That is a quite specific situation when we are removing some UI controls, that has been declared in XAML. Generally we can do it from own custom control implementation class or from code-behind in any UserControl.
WPF creates a reference to all named controls that can be then used from control code-behind. But we need to be aware that if we create a template or User control where we have visuals with strong names like below
1 2 3 4 5 |
<Grid x:Name="rootGrid"> <ContentControl x:Name="MyNameOfScreenHolder" Content="{Binding Screen}" /> </Grid> |
We cannot just remove them in code-behind if we need it , like here:
1 2 3 4 5 6 |
private void RemoveControl() { this.rootGrid.Children.Clear(); } |
How to prevent
We need to unregister them also, cause they are created globally, and there is no other way to remove them from memory. To do it we need to call method FrameworkElement.UnregisterName("nameOfControl") , to again notify GC about it. Something like this code below:
1 2 3 4 5 6 7 |
private void RemoveControl() { this.rootGrid.Children.Clear(); this.UnregisterName("MyNameOfScreenHolder"); } |
5. Non notifiable collections binding
When/Why leaking
A similar situation like in point 3. If we try to use a collection in our ViewModel, that is bound to View control property, and the collection is not implementing interface INotifyCollectionChanged, WPF mechanism will create a strong reference to this collection to make some kind of helper descriptor for it. The problem is that this reference will remain in memory for whole application lifetime.
How to prevent
I would suggest to use ObservableCollection<T> only for all observable collections used for binding items with View. As an alternative you can inherit own collection implementations by this class or implement required interface: INotifyCollectionChanged.
One thought on “.NET / WPF Memory Leaks”
Thankyou for helping out, great info .