WPF: a deep understanding of the Weak Event model, wpfweak
In a previous article (XAML: The best practices for event processing in custom controls), we mentioned in. if the event is not unregistered in. NET, memory leakage will occur. This is mainly because when the event source generates a strong reference to the event listener, the event listener cannot be reclaimed.
In this article, we will first describe the memory leakage problem, and then we will focus on it. NET Weak Event model and its application. The Weak Event model is used to solve Memory leakage caused by regular events. Finally, we will implement the Weak Event model by ourselves.
1. Memory leakage 1. Cause
We usually add event listening for the event as follows: <source>. <event> + = <listener-delegate>. In this way, the event source will generate a strong reference (for example) to the event listener ). Even if the event listener is no longer in use, it cannot be recycled, resulting in Memory leakage.
Public abstract class handler: DispatcherObject {protected static WeakEventManager GetCurrentManager (Type managerType); protected static void SetCurrentManager (Type managerType, WeakEventManager manager); protected void handler (object sender, EventArgs args ); protected void listener (object source, Delegate handler); protected void ProtectedAddListener (object source, IWeakEventListener listener); protected void listener (object source, Delegate handler); protected void listener (object source, IWeakEventListener listener); protected abstract void StartListening (object source); protected abstract void StopListening (object source );}
In addition to WeakEventManager, The IWeakEventListener interface is also used. The class that needs to handle the event needs to implement this interface. It contains a method:
public interface IWeakEventListener { bool ReceiveWeakEvent(Type managerType, object sender, EventArgs e); }
The ReceiveWeakEvent method can obtain the EventManager type, event source, and event parameters. It returns the bool type to indicate whether the passed events are processed.
2. How does WPF solve the problem?
In WPF, corresponding WeakEventManager is used to process the PropertyChanged events of the INotifyPropertyChanged interface and the collechanged changed events of the INotifyCollectionChanged interface. As follows:
# Region event source public delegate void ValueChangedHanlder (object sender, ValueChangedEventArgs e); public class ValueChangedEventArgs: EventArgs {public object NewValue {get; set ;}} public class ValueObject {public event ValueChangedHanlder ValueChanged; public void ChangeValue (object newValue) {// ValueChanged ?. Invoke (this, new ValueChangedEventArgs {NewValue = newValue}) ;}# endregion event Source
Add: to implement the Weak Event model for the Event source, the Event source does not need to be modified.
1. Use WeakEventManager <TEventSource, TEventArgs>
The two generic types of WeakEventManager <TEventSource, TEventArgs> are the event source and event parameters, which have two methods: AddHanlder and RemoveHanlder. We can use this method as follows:
Private static void Main (string [] args) {var vo = new ValueObject (); WeakEventManager <ValueObject, ValueChangedEventArgs>. addHandler (vo, "ValueChanged", OnValueChanged); // triggers the event vo. changeValue ("This is new value");} private static void OnValueChanged (object sender, ValueChangedEventArgs e) {Console. the value of WriteLine ($ "[Handler in Main] has been changed. New Value: {e. newValue }");}
The running result of the above Code is as follows:
[Handler in Main] value changed. new value: This is new value
In the AddHanlder method, we need to manually specify the name of the event to be monitored. Therefore, we can see that reflection is used in the AddHanlder method, which consumes a little performance. The custom WeakEventManager class that will be mentioned later does not have this problem. However, it requires more code.
2. Create a custom WeakEventManager class
Create a class named ValueChangedEventManager to inherit from WeakEventManager and override its abstract method:
Public class ValueChangedEventManager: WeakEventManager {protected override void StartListening (object source) {var vo = source as ValueObject; vo. valueChanged + = Vo_ValueChanged;} protected override void StopListening (object source) {var vo = source as ValueObject; vo. valueChanged-= Vo_ValueChanged;} private void Vo_ValueChanged (object sender, ValueChangedEventArgs e) {// transmits the event base to the event listener. deliverEvent (sender, e );}}
In the above code, we can see that the custom WeakEventManager class is used as the event listener, so the event source no longer references the event listener, but the current WeakEventManager.
Then, add the following code to it to facilitate event listening:
/// <Summary> /// return the current instance // </summary> public static ValueChangedEventManager CurrentManager {get {var mgr = GetCurrentManager (typeof (ValueChangedEventManager) as ValueChangedEventManager; if (mgr = null) {mgr = new ValueChangedEventManager (); SetCurrentManager (typeof (ValueChangedEventManager), mgr);} return mgr ;}} /// <summary> /// add event listening /// </summary> /// <param name = "source"> </param> /// <param name = "eventListener"> </param> public static void AddListener (object source, IWeakEventListener eventListener) {CurrentManager. protectedAddListener (source, eventListener );} /// <summary> /// remove event listening /// </summary> /// <param name = "source"> </param> /// <param name = "eventListener"> </param> public static void RemoveListener (object source, IWeakEventListener eventListener) {CurrentManager. protectedRemoveListener (source, eventListener );}
Note: here we define a static read-only attribute to return the current WeakEventManager singleton and use it to call the corresponding methods of its base class.
Next, create a ValueChangedListener class and implement the IWeakEventListener interface. This class is responsible for processing events passed by WeakEventManager:
Public class ValueChangedListener: IWeakEventListener {public void HandleValueChangedEvent (object sender, ValueChangedEventArgs e) {Console. writeLine ($ "[ValueChangedListener] value changed. New Value: {e. newValue} ") ;}/// <summary> /// events received from WeakEventManager, defined by IWeakEventListener // </summary> // <param name = "managerType"> </param> // <param name = "sender"> </param> /// <param name = "e"> </param> /// <returns> </returns> public bool ReceiveWeakEvent (Type managerType, object sender, EventArgs e) {// determines the type. if it is of the corresponding type, the event will be processed if (managerType = typeof (ValueChangedEventManager) {HandleValueChangedEvent (sender, (ValueChangedEventArgs) e); return true;} else {return false ;}}}
In the ReceiveWeakEvent method, the HandleValueChangedEvent method is called to process the events passed to the Listener. Usage:
Var vo = new ValueObject (); var eventListener = new ValueChangedListener (); ValueChangedEventManager. addListener (vo, eventListener); // triggers the event vo. changeValue ("This is new value ");
When the last code is executed, the following results are output:
[ValueChangedListener] value changed. new value: This is new value
3. Use existing WeakEventManager
WPF contains some ready-made WeakEventManager classes, which are derived from WeakEventManager, as shown in the preceding figure. If you are using these EventManager events, you can directly use the corresponding WeakEventManager.
For example, there is a Person class, we need to pay attention to its attribute value changes, then we can implement INotifyPropertyChanged for it, as shown below:
public class Person : INotifyPropertyChanged { private string _name; public event PropertyChangedEventHandler PropertyChanged; public string Name { get { return _name; } set { _name = value; RaisePropertyChanged(nameof(Name)); } } private void RaisePropertyChanged(string propertyName) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } }
Note: The scenario currently discussed is applicable not only to WPF, but also to any other platform, as long as you have the same requirement: monitor attribute value changes.
Then, create a class PropertyChangedEventListener to respond to the PropertyChanged event. Like the ValueChangedListener class above, this class also implements the IWeakEventListener interface. The Code is as follows:
/// <Summary> // listen for and process the PropertyChanged event // </summary> public class PropertyChangedEventListener: IWeakEventListener {public bool ReceiveWeakEvent (Type managerType, object sender, EventArgs e) {if (managerType = typeof (PropertyChangedEventManager) {// process the event, such as updating the Console of the bound value in the UI. writeLine ($ "[PropertyChangedEventListener] This attribute value has been changed: {(e as PropertyChangedEventArgs ). propertyName} "); return true ;}{ return false ;}}}
In the ReceiveWeakEvent method, we can add how to handle a property change. In fact, we have simply simulated the idea of updating the UI through data binding in WPF, but the real situation will be more complicated than this. Let's see how to use it:
Var person = new Person (); var property = new PropertyChangedEventListener (); PropertyChangedEventManager. addListener (person, property, nameof (person. name); // triggers the PropertyChanged event person by modifying the property value. name = "Jim ";
Output result:
[PropertyChangedEventListener] the attribute value has changed: Name
Summary
This article discusses the Weak Event model in WPF, which is used to solve the problem of Memory leakage in general events. The implementation principle is to use WeakEventManager as the "intermediary" to remove strong references between event sources and event listeners. When an event in the event source is triggered, weakEventManager transmits the event source and event parameters to the listener. After receiving the event, the event listener processes the event based on the passed parameters. In addition, we also discuss the use of the Weak Event model scenario and three methods to implement the Weak Event model.
If you encounter similar scenarios or problems during development, you can try to use Weak Event to solve them.
References:
Weak Event Patterns
WeakEventManager Class
Preventing Event-based Memory Leaks-WeakEventManager
Source code download