Skip to content

Weak Event

jbe2277 edited this page Dec 30, 2020 · 6 revisions

Memory Leaks in .NET

  • .NET comes with a Garbage Collector (GC) which automatically release memory.
  • The GC releases the memory for objects that are no longer being used by the application.
  • Determining if an object is no longer being used is when the application does not have any reference to the object anymore.
  • A .NET memory leak occurs when unneeded objects are still referenced by other objects. The Garbage Collector will not free the memory of those objects as it cannot know that they are not needed anymore.
  • A common cause for .NET memory leaks is by using Events (or Delegates). By subscribing to an event, the event holds a reference to the subscriber. When the publisher lives longer (e.g. singleton) as the subscribers then it keeps all subscribers referenced and thus in memory.

How to avoid memory leaks caused by Events?

  • Unsubscribe unneeded subscribers from events.
  • Sometimes the application code does not know when to unsubscribe. This case is most seen in code that use an UI Framework (e.g. WPF).
    • Use a weak event

WAF Weak Event

.NET comes with a weak event implementation for WPF. Unfortunately, this one cannot be used by other application models (e.g. Xamarin Forms). Because of this limitation the System.Waf.Core library provides another weak event implementation with following characteristics:

  • Independent of a specific application model. Can be used in any .NET application (e.g. Xamarin Forms)
    • Works with AOT compiler as well (e.g. Xamarin.iOS)
  • Simple API for most used event types
  • Fast because it does not use reflection or open delegates.
  • Use the listener-side weak event approach. This variant is less intrusive as the alternative source-side weak event approach.
    • Publisher (source) does not need any adaption to support this weak event implementation.
  • A subscription creates a small proxy object IWeakEventProxy which is kept by the publisher. It removes itself from the publisher, when:
    • The subscriber is collected by the garbage collector and the publisher raises the event one more time.
    • Remove method on the proxy is called.
  • Limitations
    • A small proxy object could stay in memory when the publisher does not raise the event after GC collects the subscriber.
    • Only these event types are supported:
      • EventHandler (Instance and Static)
      • EventHandler<TEventArgs> (Instance and Static)
      • INotifyPropertyChanged.PropertyChanged
      • INotifyCollectionChanged.CollectionChanged
      • ICommand.CanExecuteChanged
      • INotifyDataErrorInfo.ErrorsChanged
    • At the moment other event types are not supported. However, the implementation for another event type is not that difficult - see WeakEvent.cs

Samples

INotifyPropertyChanged.PropertyChanged

public class Publisher : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler? PropertyChanged;
}

public class Subscriber
{
    public void Init(Publisher publisher)
    {
        // Instead of publisher.PropertyChanged += Handler; use the following statement:
        WeakEvent.PropertyChanged.Add(publisher, Handler)        
    }

    public void Handler(object? sender, PropertyChangedEventArgs e) { }
}

EventHandler

public class Publisher
{
    public event EventHandler? MyEvent;
}

public class Subscriber
{
    public void Init(Publisher publisher)
    {
        // Instead of publisher.MyEvent += Handler; use the following statement:
        WeakEvent.EventHandler.Add(publisher, Handler, (s, h) => s.MyEvent += h, (s, h) => s.MyEvent -= h);
    }

    public void Handler(object? sender, EventArgs e) { }
}

IWeakEventProxy

public class Publisher : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler? PropertyChanged;
}

public class Subscriber
{
    private IWeakEventProxy? weakEventProxy;
    
    public void AddHandler(Publisher publisher)
    {
        weakEventProxy = WeakEvent.PropertyChanged.Add(publisher, Handler)        
    }

    public void RemoveHandler()
    {
        weakEventProxy?.Remove();
        weakEventProxy = null;
    }

    public void Handler(object? sender, PropertyChangedEventArgs e) { }
}
Clone this wiki locally