3

Actually I m trying to close my window by firing the event from my ViewModel. Everything works fine and awesome, but I know that I must unsbscribe my event to avoid memory leaks. thus I implement the IDisposable interface and I unsbscribe the event inside the Dispose method.

Below is my code :

public partial class MainWindow : Window, IDisposable
{
    private MainViewModel viewModel;
    public MainWindow()
    {
        InitializeComponent();
        DataContext = viewModel =  new MainViewModel();
        this.viewModel.RequestClose += CloseWindow;
    }

    void CloseWindow(object sender, EventArgs e)
    {
        this.Close();
    }

    public void Dispose()
    {
        ////here we need to unsubscribe the event
        this.viewModel.RequestClose -= this.CloseWindow;
    }
}

What I need to know :

  1. Is that code correct
  2. When the GC will be called and excute the dispose method
  3. Is there a better way to do such a thing
A.Rowan
  • 1,460
  • 2
  • 16
  • 20
MRebai
  • 5,344
  • 3
  • 33
  • 52
  • 4
    in this setup you don't need to unsubscribe the event at all - the event will hold a pointer to the window that's true, but when there is no path to the window there will be no path to the viewmodel as well and so it will be collected IFF the window will – Random Dev Nov 10 '15 at 13:19
  • As a heads up; you might want to read into some proper `IDispose` implementations. (http://stackoverflow.com/questions/18336856/implementing-idisposable-correctly , http://www.codeproject.com/Articles/15360/Implementing-IDisposable-and-the-Dispose-Pattern-P) – Stefan Nov 10 '15 at 13:19
  • @Carsten can you explain more and in which case we must implement the dispose method – MRebai Nov 10 '15 at 13:24
  • 2
    Try use a Attached behavior, actually I'm not a big fan of events, especially in MVVM. – Bruno Joaquim Nov 10 '15 at 13:34

4 Answers4

6

but I know that I must unsbscribe my event to avoid memory leaks

Memory leak occurs, when short-lived object subscribes an event of long-lived objects (or static event), and does not unsubscribe later (e.g., see this answer). I suppose, that this is not your case.

When the GC will be called and excute the dispose method

GC doesn't call IDisposable.Dispose (e.g., see this answer). At all. If you haven't any code, that calls MainWindow.Dispose explicitly, it will be called never.

Is there a better way to do such a thing

I'd avoid IDisposable and events. Attached behavior here is more convenient, IMO (at least, this is reusable):

public static class WindowClosingBehavior
{
        public static bool GetIsClosingInitiated(DependencyObject obj)
        {
            return (bool)obj.GetValue(IsClosingInitiatedProperty);
        }

        public static void SetIsClosingInitiated(DependencyObject obj, bool value)
        {
            obj.SetValue(IsClosingInitiatedProperty, value);
        }

        public static readonly DependencyProperty IsClosingInitiatedProperty = DependencyProperty.RegisterAttached(
            "IsClosingInitiated", 
            typeof(bool), 
            typeof(WindowClosingBehavior),
            new FrameworkPropertyMetadata(false, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, IsClosingInitiatedChanged));

        private static void IsClosingInitiatedChanged(DependencyObject target, DependencyPropertyChangedEventArgs e)
        {
            var window = target as Window;
            if (window != null && (bool)e.NewValue)
            {
                window.Close();
            }
        }
}

Somewhere in window's XAML:

behaviors:WindowClosingBehavior.IsClosingInitiated="{Binding IsClosingInitiated}"

where IsClosingInitiated is a property from view model:

public class SomeViewModel
{
     // ...

     private void Foo()
     {
         // ...
         IsClosingInitiated = true;
     }
}
Community
  • 1
  • 1
Dennis
  • 37,026
  • 10
  • 82
  • 150
  • Actually I don't want to use Attached property – MRebai Nov 10 '15 at 13:39
  • Dennis can you provide me with an example of events using that caused a memory leaks – MRebai Nov 10 '15 at 13:48
  • @MoezRebai: just subscribe any static event (e.g., `CommandManager.RequerySuggested`) from any short-lived object, and you'll get memory leak (without un-subscription). – Dennis Nov 10 '15 at 13:56
4

You only need to unsubscribe events when the source and the handler have different lifetimes, otherwise they both go out of scope at the same time and they are garbage collected together.

So in this case IDisposable is not needed. Anyway if you implement IDisposable you need to explicitly call it, otherwise you don't have control about when it is called.

Ignacio Soler Garcia
  • 21,122
  • 31
  • 128
  • 207
1

Actually when the Window.CloseWindow subscribe to the event, it makes the view model point to the window.

The reverse is also true because there is a ViewModel field in the Window.

Both window and view model reference to each other.

If there is no other reference to them, garbage collection will make the job.

Dispose will be called if some code calls it.

To my knowledge, it won't happen, unless you surround the creation of the windows with a using or explicitly call Dispose

The best way here is to not implement IDisposable / Dispose : keep it simple.

Regards

Emmanuel DURIN
  • 4,803
  • 2
  • 28
  • 53
0

I'd say using an event is a more than acceptable method of achieving this. For a more complete dispose pattern, use the following snippet:

#region IDisposable

//Dispose() calls Dispose(true)
public void Dispose()
{
    Dispose(true);
    GC.SuppressFinalize(this);
}

// NOTE: Delete the finalizer if this class doesn't 
// own unmanaged resources itself.
~ClassName() 
{
    //Finalizer calls Dispose(false)
    Dispose(false);
}

//The bulk of the clean-up code is implemented in Dispose(bool)
protected virtual void Dispose(bool disposing)
{
    if (disposing) 
    {
        //free managed resources (Example below)
        if (managedResource != null)
        {
            managedResource.Dispose();
            managedResource = null;
        }
    }

    //Free native resources if there are any. (Example below)
    if (nativeResource != IntPtr.Zero) 
    {
        Marshal.FreeHGlobal(nativeResource);
        nativeResource = IntPtr.Zero;
    }
}

#endregion

In your case, your dispose method will be this:

public void Dispose()
{
    Dispose(true);
    GC.SuppressFinalize(this);
}

~MainWindow()
{
    Dispose();
}

protected virtual void Dispose(bool disposing)
{
    if (disposing) 
    {
        if (viewModel != null)
        {
            viewModel.RequestClose -= CloseWindow;
            viewModel.Dispose();
            viewModel = null;
        }
    }
}

As pointed out by Dennis, you'll need to keep the finalizer to ensure that Dispose gets called when the MainWindow is closed, for example in the event of the application being exited.

Mike Eason
  • 9,525
  • 2
  • 38
  • 63
  • There's one tiny problem. **Who** will call `Dispose` in the sample from OP? – Dennis Nov 10 '15 at 13:53
  • @Dennis That is an excellent point. If OP isn't managing `Dispose` (which I assume he isn't), he could add a **destructor** in his `MainWindow`. – Mike Eason Nov 10 '15 at 14:01
  • Do you know, that finalizer (destructor) should release only *unmanaged* resources (while event is a pure *managed* resource)? Also, do you know, that one can't predict, when finalizer will be called? – Dennis Nov 10 '15 at 14:13