One of the most common causes of memory leaks appearing in an application is the failure to remove event handlers once they are no longer needed. When we attach an event handler to an object's event in the usual way, we are effectively passing that object a reference to the handler and creating a hard reference to it.
When the object is no longer needed and could otherwise be disposed of, the reference in the object that raises the event will prevent that from occurring. This is because the garbage collector cannot collect an object that can be accessed from any part of the application code. In the worst-case scenario, the object being kept alive may contain numerous other objects and, therefore, inadvertently keep them alive as well.
The problem with this is that keeping objects alive after they are no longer needed will unnecessarily increase the memory footprint of the application, in some cases, with dramatic and irreversible consequences, leading to an OutOfMemoryException being thrown. It is, therefore, essential that we detach our event handlers from the events that they are subscribed to in objects that we have no further use for before trying to dispose of them.
There is, however, an alternative method that we can use to avoid this situation. In the .NET Framework, there is a WeakReference class and it can be used to remove the hard references caused by attaching event handlers to events using the traditional method.
The basic idea is that the class that raises the event should maintain a collection of WeakReference instances and add to it each time another class attaches an event handler to the event. Let's now create a new WeakReferenceActionCommand class from our ActionCommand class from earlier to use this WeakReference class:
using System; using System.Collections.Generic; using System.Windows.Input; namespace CompanyName.ApplicationName.ViewModels.Commands { public class WeakReferenceActionCommand : ICommand { private readonly Action<object> action; private readonly Predicate<object> canExecute; private List<WeakReference> eventHandlers = new List<WeakReference>(); public WeakReferenceActionCommand(Action<object> action) :
this(action, null) { } public WeakReferenceActionCommand(Action<object> action, Predicate<object> canExecute) { if (action == null) throw new ArgumentNullException("The action
input parameter of the WeakReferenceActionCommand constructor
cannot be null.");
this.action = action; this.canExecute = canExecute; } public event EventHandler CanExecuteChanged { add { eventHandlers.Add(new WeakReference(value)); CommandManager.RequerySuggested += value; } remove { if (eventHandlers == null) return; for (int i = eventHandlers.Count - 1; i >= 0; i--) { WeakReference weakReference = eventHandlers[i]; EventHandler handler = weakReference.Target as EventHandler; if (handler == null || handler == value) { eventHandlers.RemoveAt(i); } } CommandManager.RequerySuggested -= value; } } public void RaiseCanExecuteChanged() { eventHandlers.ForEach( r => (r.Target as EventHandler)?.Invoke(this, new EventArgs())); } public bool CanExecute(object parameter) { return canExecute == null ? true : canExecute(parameter); } public void Execute(object parameter) { action(parameter); } } }
We start by adding a declaration of our new collection containing objects of the WeakReference type to the pre-existing fields. The two constructors remain unchanged, but when attaching handlers in the CanExecuteChanged event, we now wrap the event handling delegate in a WeakReference object and add it to the collection. We still need to pass the references to the handlers that get attached through to the RequerySuggested event of the CommandManager class as before.
When an event handler is removed, we first double-check that our WeakReference collection is not null and simply return control to the caller if it is. If not, we use a for loop to iterate through the collection in reverse so that we can remove items without affecting the loop index.
We attempt to access the actual event handler from the Target property of each WeakReference object in turn, converting it to the EventHandler base type using the as keyword. We then remove the WeakReference instance if its event handler reference is either null or it matches the handler being removed.
Note that a null reference in the Target property would be the result of an event handler from a class that has been disposed of by the garbage collector. As before, we then also detach the event handler from the CommandManager.RequerySuggested event.
Finally, we need to update our RaiseCanExecuteChanged method to use our new collection of WeakReference objects. In it, we again iterate through each instance in the collection using our ForEach Extension Method and after checking whether its Target property is null by using the null conditional operator, call it using the delegate's Invoke method.
So, the idea here is that we no longer directly hold on to any references to the attached event handlers and are, therefore, free to dispose of those classes at any point without fear of them being kept alive unnecessarily.