Back to Devexpress

Weak Event

wpf-117204-mvvm-framework-weak-event.md

latest4.2 KB
Original Source

Weak Event

  • Jun 07, 2019
  • 3 minutes to read

Developing an MVVM application may require you to declare events that don’t need to be unregistered and don’t cause memory leaks. Such events may be useful in organizing the interaction between different modules of the application (for example, data services and view models) and objects that exist throughout the entire application lifecycle.

The WeakEvent class allows you to declare weak events in the delegate void(object sender, object e) format. To declare a weak event, use the following syntax.

csharp
WeakEvent<EventHandler, EventArgs> myEvent = new WeakEvent<EventHandler, EventArgs>();
public event EventHandler MyEvent { add { myEvent.Add(value); } remove { myEvent.Remove(value); } }

void RaiseMyEvent(object sender, EventArgs e) {
    myEvent.Raise(sender, e);
}
vb
Private _myEvent As New WeakEvent(Of EventHandler, EventArgs)()
Public Custom Event MyEvent As EventHandler
         AddHandler(ByVal value As EventHandler)
                  _myEvent.Add(value)
         End AddHandler
         RemoveHandler(ByVal value As EventHandler)
                  _myEvent.Remove(value)
         End RemoveHandler
         RaiseEvent()
         End RaiseEvent
End Event

Private Sub RaiseMyEvent(sender As Object, e As EventArgs)
    _myEvent.Raise(sender, e)
End Sub

Use Of Closures in Event Handlers

When you subscribe a weak event, that means that the handler is separated by the instance that owns the delegate and the method itself. The instance is stored as a weak reference. This allows you to ignore unsubscribing and to avoid memory leaks. However, this imposes certain limitations when you use a lambda expression as an event handler. Imagine that you have the following subscription:

csharp
//Incorrect approach
public class ViewModel {
    public ViewModel() {
        var service = ServiceLocator.Default.GetService<IService>();
        object localVariable = new object();
        //This event handler may be never invoked
        service.MyWeakEvent += (s, e) => {
            object closure = localVariable;
        };
    }
}
vb
'Incorrect approach
Public Class ViewModel
    Public Sub New()
        Dim service = ServiceLocator.[Default].GetService(Of IService)()
        Dim localVariable As New Object()
        'This event handler may be never invoked
        AddHandler(service.MyWeakEvent), Sub(s, e)
                                             Dim closure As Object = localVariable
                                         End Sub
    End Sub
End Class

In this case, the lambda expression contains a closure to a local variable that is declared in the ViewModel’s constructor. Since the compiler creates an intermediate object for the defined lambda with closures and this intermediate object is weakly referenced, nothing prevents the Garbage Collector from collecting the intermediate object, so your message handler may never be invoked.

To fix this potential issue with your weak event, declare your local variable as a property/field at the level of the object from which subscription is performed.

csharp
//Correct approach.
public class ViewModel {
    object LocalVariable {get; set;}
    public ViewModel() {
        var service = ServiceLocator.Default.GetService<IService>();
        //This event handler will be invoked correctly
        service.MyWeakEvent += (s, e) => {
            object closure = LocalVariable;
        };
    }
}
vb
'Correct approach.
Public Class ViewModel
    Private MyProperty As Object
    Public Sub New(ByVal service As Service)
        AddHandler(service.MyWeakEvent), Sub(s, e)
                                             Dim closure As Object = MyProperty
                                             Debug.WriteLine("Works")
                                         End Sub
    End Sub
End Class

In this case, the event handler will be available throughout the entire lifecycle of the owner object.