Memory Management Best Practices in WPF

  • 3.9K Views
  • Last Post 30 November 2016
dnoll posted this 30 November 2016

When a Detail window (or any XAML control) is closed, or otherwise goes out of scope, it must be cleaned up before the memory taken up by it can be released. In most cases this happens automatically, but it is possible to get yourself into a situation where stale references to your object prevent it from being cleaned up. In this situation the object remains in memory for much longer than intended and, if the problem is severe enough, has the potential to make the program unstable/unusable over time. 

Memory Leaks like the one described above should be a constant concern for developers. Systems with poor memory managment will appear to degrade with use, which leaves a bad taste in the mouths of Users who often rely on these systems to do their jobs. Because the goal should be to have a system that is predictable and stable, Memory Leaks should be identified and fixed as soon in the development process as possible.

Below are several best practices for managing memory when coding with C#, including some WPF and XAML-specific tips.

Order By: Standard | Newest | Votes
dnoll posted this 30 November 2016

Implement the Dispose Pattern

Objects that have unmanaged resources - or managed resources that need to be cleaned up - often implement the IDisposable interface. When a developer creates a Disposable object in a using block, that object's Dispose method is called when the code within the using block is finished executing. For objects that need to be cleaned up, implementing IDisposble is the recommended course of action.

However, it is sometimes necessary to implement your own Finalizer/Destructor in a managed object. If your object contains a custom Finalizer, and implements IDisposable, you must implement the "Dispose Pattern", which extends the functionality of the default IDisposable functionality. Without using this pattern, it is likely your system will leak memory over time as objects are added to the Finalizer queue, but never finalized.

Below is the Dispose Pattern recommended by Microsoft (the class name in this case is "CustomObject"):

protected virtual void Dispose(bool disposing) {
    // Release unmanaged memory
    if (disposing) { 
        // Release other objects
    }
}

public void Dispose() {
    Dispose(true);
    GC.SuppressFinalize(this);
}

~ CustomObject() {
    Dispose(false);
}

There are a couple important things to notice here.

First, in addition to the Dispose method required by the IDisposable interface, a second Dispose method is added that accepts a boolean for "disposing". This allows us to have two entry points for our clean-up code (Dispose(), or ~CustomObject()) that both lead to the same place. It is important to remember that:

  • When calling the second Dispose method from the original method, you must pass in "True" as the boolean parameter, to signal that this operation was triggered while the object was being disposed.
  • When calling the second Dispose method from the Finalizer, you must pass "False" as the boolean parameter, to signal that this operation was triggered by the destructor and not a call to Dispose().

It's up to Developers how they want to handle each case, but being able to identify the caller is vital to making the right choices about unmanaged resources when cleaning up an object.

Second, note that Dipose(bool disposing) is a protected method that is only ever called from the Finalizer and Dispose(). Dispose(bool disposing) should be considered an implementation detail, and all objects referencing this CustomObject from the outside should call Dispose() when looking to release the object.

Finally, the public Dispose() method must call GC.SuppressFinalize(this) after returning from its call to Dispose(bool disposing). From a memory management perspective, this is the most important item. If an object implements both IDisposable and a custom Finalizer, the object has the potential to remain on the Finalizer queue for the rest of the programs runtime, essentially leaking memory everytime a new object of that type is created. Calling SuppressFinalize notifies the Finalizer queue that this object no longer needs to be cleaned up, and will allow it to drop off of the queue. 

During development and troubleshooting, you can add breakpoints to these methods to diagnose issues with memory management. If you notice that your breakpoints are not being hit when you are expecting your object to be cleaned up, you may have a memory leak somewhere in your program that is preventing the Garbage Collector from working with your object.

dnoll posted this 30 November 2016

Tips for Writing Dispose Methods

In addition to the Dispose Pattern which should be implemented by anyone with a custom Finalizer, there are other best practices you can observe when writing a Dispose method for a XAML control.

When Disposing an object, you want to ensure that you:

  • Dispose all Children and Child Collections when disposing an Object (if they are disposable).
  • Call BindingOperations.ClearAllBindings against the the object being disposed, as well as all child objects that this object was responsible for binding.
  • Unsubscribe to Events.

Unsubscribing to events is particularly important, as these event subscriptions can keep your object "in-action" for longer than intended, and cause a tree of orphaned objects that all root back to your stale event reference.

dnoll posted this 30 November 2016

Tips for Memory Management in XAML

As a XAML best practice, we recommend that you do not use DynamicResource bindings in inline resource dictionaries when binding to a Brush. The following code snippet illustrates an example of this problem:

<StackPanel Grid.Row="0" Orientation="Horizontal" Margin="5,0,0,0">
    <StackPanel.Resources>
        <Style TargetType="{x:Type Label}">
            <Setter Property="Foreground" Value="{DynamicResource Order_Status_Warning}" />
            <Setter Property="FontSize" Value="22" />
            <Setter Property="FontWeight" Value="Bold" />
        </Style>
    </StackPanel.Resources>
    (...)
</StackPanel>

In the example above, the Style is created as a resource on the StackPanel object. Within the Style is a Setter for the Foreground property that has a DynamicResource reference to a Brush defined in a Resource Dictionary elsewhere. Because DynamicResources are resolved lazily, you can potentially end up with a reference to a brush that will never be needed. In this case the object may not be cleaned up, leading to a Memory Leak. 

If possible, prefer StaticResource bindings over DynamicResource bindings, or move your style definition outside of a control's inline resource dictionary.

Close