You are correct, using WhenAny / ObservableForProperty incorrectly can cause your application to leak memory if you are not careful. Consider the following code:
public ItemInAListBoxViewModel(MainWindowViewModel mainWindow)
{
this.window = mainWindow;
// Reset the "selected" when the user minimizes
this.WhenAnyValue(x => x.window.IsMinimized)
.Where(x => x == true)
.Subscribe(x => this.IsSelected = false);
}
Because we've WhenAny'd through an object whose lifetime is longer than ours (i.e. the ListBox item vs the Window), we're holding ListBox items forever until the Window goes away (which may be never in your app).
You will avoid the vast majority of these cases if you only WhenAny on your own object (i.e. always this.WhenAny
, never someObject.WhenAny
).
A special note about Dependency Properties
No matter what, you have to Dispose of any WhenAny that goes through a DependencyProperty, or else you leak. Because Windows.
Crap, what do I do now?
A new feature was added to ReactiveUI to handle the scenario where you do want to do this though, called "Activation". You can find more info at:
We can now define a Scope of "things that should only be active when On Screen", that will go away immediately after a View and its ViewModel are removed from the screen (i.e. removed from the Visual Tree in WPF).
public ItemInAListBoxViewModel(MainWindowViewModel mainWindow)
{
this.window = mainWindow;
Activator = new ViewModelActivator();
// This gets called every time the View for this VM gets put on screen
this.WhenActivated(d => {
// The 'd' is for "Dispose this when you're Deactivated"
d(this.WhenAnyValue(x => x.window.IsMinimized)
.Where(x => x == true)
.Subscribe(x => this.IsSelected = false));
});
}
For this to work, here's what needs to be true:
- Your VieWModel needs to implement
ISupportsActivation
(super easy) - The View associated with your ViewModel needs to call
WhenActivated
too.
This sounds super hard!
It looks that way, but it's totally not. Just remember two things:
- Don't WhenAny through objects that stick around forever, unless you also stick around forever
- If you do have to, use WhenActivated.