wpf-117204-mvvm-framework-weak-event.md
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.
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);
}
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
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:
//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;
};
}
}
'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.
//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;
};
}
}
'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.