Search code examples
c#idisposablereactiveui

ReactiveUI OneWayBind leaks handles


I have a simple ViewModel:

public class MeetingPageViewModel : ReactiveObject, IRoutableViewModel
{
    public MeetingPageViewModel(IScreen hs, IMeetingRef mRef)
    {
        HostScreen = hs;
        _backing = "hi there";
    }

    public IScreen HostScreen { get; private set; }

    public string MeetingTitle
    {
        get { return _backing; }
        set { this.RaiseAndSetIfChanged(ref _backing, value); }
    }
    string _backing;

    public string UrlPathSegment
    {
        get { return "/meeting"; }
    }
}

And I bind to the MeetingTitle a TextBlock:

public sealed partial class MeetingPage : Page, IViewFor<MeetingPageViewModel>
{
    public MeetingPage()
    {
        this.InitializeComponent();

        // Bind everything together we need.
        this.OneWayBind(ViewModel, x => x.MeetingTitle, y => y.MeetingTitle.Text);
    }

    /// <summary>
    /// Stash the view model
    /// </summary>
    public MeetingPageViewModel ViewModel
    {
        get { return (MeetingPageViewModel)GetValue(ViewModelProperty); }
        set { SetValue(ViewModelProperty, value); }
    }
    public static readonly DependencyProperty ViewModelProperty =
        DependencyProperty.Register("ViewModel", typeof(MeetingPageViewModel), typeof(MeetingPage), new PropertyMetadata(null));

    object IViewFor.ViewModel
    {
        get { return ViewModel; }
        set { ViewModel = (MeetingPageViewModel)value; }
    }
}

After navigating back to the previous screen MeetingPageViewModel isn't garbage collected. I'm using 6.4.1 of RxUI and VS2013 (and the memory analysis analyze tool). If I dispose of the return value from OneWayBind then everything is cleaned up properly - but of course, I no longer have the bindings.


Solution

  • It turns out the problem is the DependencyProperty ViewModel. Its lifetime is forever (note the "static" in its declaration). As a result, the binding that is attached to it is never garbage collected, since it never goes away, and that binding then holds a reference to both the view and viewmodel, and so they never go away.

    The only way to break this is explicitly clean up the bindings. RxUI provides the WhenActivated method to help with this. Surround the bindings in a lambda, and use the provided function to track the IDisposals. When the view goes away this will then be cleaned up.

    this.WhenActivated(disposeOfMe => {
        disposeOfMe (this.OneWayBind(ViewModel, x => x.MeetingTitle, y => y.MeetingTitle.Text));
    });