maui
maui copied to clipboard
Weak subscription to CanExecuteChange events
Description of Change
This pull request introduces a mechanism for managing and cleaning up command event subscriptions when the lifetime of the ICommand instance exceeds that of the XAML element it's assigned to.
Example from the Unit Tests
var command = new CommandWithoutWeakEventHandler();
// The Button subscribes to "command.CanExecuteChanged", creating a strong reference
// from the command to the Button. If the command outlives the Button,
// this prevents the Button from being garbage collected, resulting in a memory leak.
// Our built-in Command implementation avoids this by using WeakEventManager,
// but custom ICommand implementations that do not use weak event patterns are prone to leaks.
{
var button = new Button();
button.Command = command;
}
This largerly comes up with scenarios using static viewmodels or if people are using reference bindings. The key changes include the addition of a WeakCommandSubscription class, updates to the ICommandElement interface, and new unit tests to verify the functionality.
Memory Management Enhancements:
-
Added
WeakCommandSubscriptionclass: Introduced a new class to manage weak references to command subscriptions, ensuring that event handlers do not prevent garbage collection of associated objects. This includes theCommandCanExecuteSubscriptioninner class for handlingCanExecuteChangedevents. (src/Controls/src/Core/WeakCommandSubscription.cs) -
Updated
ICommandElementinterface: Added aCleanupTrackerproperty to manage the lifecycle of command subscriptions. (src/Controls/src/Core/ICommandElement.cs) -
Integrated
WeakCommandSubscriptionin controls: Implemented theCleanupTrackerproperty in multiple controls (Button,ImageButton,MenuItem,RefreshView,SearchBar) to utilize the new subscription management system. [1] [2] [3] [4] [5]
Command Handling Improvements:
- Modified
CommandElementstatic class: UpdatedOnCommandChangingandOnCommandChangedmethods to use theCleanupTrackerfor disposing of old subscriptions and creating new ones. (src/Controls/src/Core/CommandElement.cs)
Unit Testing Enhancements:
-
New tests for garbage collection: Added tests to ensure that controls with commands can be garbage collected properly and that weak event handlers are not collected prematurely. (
src/Controls/tests/Core.UnitTests/ButtonUnitTest.cs) -
Improved garbage collection helper: Enhanced the
TestHelpers.Collectmethod to force multiple garbage collection cycles and ensure deterministic behavior during testing. (src/Controls/tests/Core.UnitTests/TestHelpers.cs)
These changes collectively improve the robustness of command handling in the library while addressing potential memory management issues.
Concerns
- Calling Managed code from the finalizer typically isn't recommended. The only purpose of the finalizer is to cleanup
CommandCanExecuteSubscription. The commandelement itself will cleanup without the finalizer. One approach we could take here would be to track these and then just clean them up every so often.
Other Notes
- WPF has a much larger solution to this https://github.com/dotnet/wpf/blob/main/src/Microsoft.DotNet.Wpf/src/PresentationCore/System/Windows/Input/Command/CanExecuteChangedEventManager.cs#L321
Issues Fixed
Fixes #16124
/rebase
I've been testing this PR and it solved a lot of leaks.
It's a hit!
Thank you guys