CARVIEW |
Navigation Menu
-
Notifications
You must be signed in to change notification settings - Fork 5.1k
Description
Background and motivation
It's quite common in MVVM scenarios, that you will bind your models and viewmodels to UI controls. These UI controls will often listen for PropertyChanged and CollectionChanged events to update the UI dynamically. However since the UI views are quite often transient but the model data is long lived, you often see very expensive UI controls getting stuck in memory due to the event handler not getting unsubscribed. This problem is of course not limited to UI components only, but where you'll often see large costs associated with not unsubscribing from the events.
A typical pattern for this is to use a helper class to subscribe to weak event handlers, and over and over again we see different implementations of this in various libraries. So much so recently .NET MAUI decided to graduate their toolkit helper to the main MAUI library. This got me thinking that since this is such a common scenario, that .NET should provide this at a lower level for all libraries to use.
Earlier I had suggested that this should be a language feature, but it was concluded this should be an API feature: dotnet/roslyn#101
Example of various implementations:
- .NET MAUI
- Xamarin.Forms
- Windows Community Toolkit
- Xamarin.Forms Community Toolkit
- Telerik UI for UWP
- ArcGIS Runtime Toolkit
- A search for
WeakEventListener
on Github reveals C# is quite common to have this:
- Same when searching for
WeakEvent
API Proposal
I think there are quite a few different approaches that can be taken, as shown with all the implementations above. I'd refer to the .NET MAUI example to start with:
public void AddEventHandler(Delegate? handler, [CallerMemberName] string eventName = null)
{
ArgumentNullException.ThrowIfNull(eventName);
ArgumentNullException.ThrowIfNull(handler)
var methodInfo = handler.GetMethodInfo() ?? throw new NullReferenceException("Could not locate MethodInfo");
AddEventHandler(eventName, handler.Target, methodInfo, eventHandlers);
}
public void RemoveEventHandler(Delegate? handler, [CallerMemberName] string eventName = "")
{
ArgumentNullException.ThrowIfNull(eventName);
ArgumentNullException.ThrowIfNull(handler)
var methodInfo = handler.GetMethodInfo() ?? throw new NullReferenceException("Could not locate MethodInfo");
RemoveEventHandler(eventName, handler.Target, methodInfo, eventHandlers);
}
API Usage
readonly WeakEventManager weakEventManager = new WeakEventManager();
event PropertyChangedEventHandler? INotifyPropertyChanged.PropertyChanged
{
add => weakEventManager.AddEventHandler(value);
remove => weakEventManager.RemoveEventHandler(value);
}
void OnPropertyChanged([CallerMemberName] in string propertyName = "") => weakEventManager.HandleEvent(this, new PropertyChangedEventArgs(propertyName), nameof(INotifyPropertyChanged.PropertyChanged));
Alternative Designs
Windows Community Toolkit:
WeakEventListener.cs
var weakEvent = new WeakEventListener<INotifyCollectionChanged, object, NotifyCollectionChangedEventArgs>(valNotifyCollection)
{
OnEventAction = (instance, source, args) => obj.SetActive(IsNullOrEmpty(instance)),
OnDetachAction = (weakEventListener) => valNotifyCollection.CollectionChanged -= weakEventListener.OnEvent
};
Risks
No response