在MVVM中使用dispose取消订阅事件

本文关键字:取消 事件 dispose MVVM | 更新日期: 2023-09-27 18:21:17

实际上,我正试图通过从ViewModel启动事件来关闭我的窗口。一切都很好,很棒,但我知道我必须取消订阅我的活动,以避免内存泄露。因此我实现了CCD_ 2接口,并且在CCD_。

以下是我的代码:

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;
    }
}

我需要知道的是:

  1. 那个代码正确吗
  2. 何时调用GC并执行dispose方法
  3. 做这样的事有更好的方法吗

在MVVM中使用dispose取消订阅事件

但我知道我必须取消订阅我的活动以避免内存泄漏

当短命对象订阅了一个长命对象的事件(或静态事件),并且以后没有取消订阅时,就会发生内存泄漏(例如,请参阅此答案)。我想,这不是你的情况。

何时调用GC并执行处置方法

GC不调用IDisposable.Dispose(例如,请参阅此答案)。完全如果您没有任何明确调用MainWindow.Dispose的代码,它将永远不会被调用。

有没有更好的方法来做这样的事情

我会避开IDisposable和活动。这里的附加行为更方便,IMO(至少,这是可重复使用的):

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();
            }
        }
}

窗口XAML中的某个位置:

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

其中IsClosingInitiated是来自视图模型的属性:

public class SomeViewModel
{
     // ...
     private void Foo()
     {
         // ...
         IsClosingInitiated = true;
     }
}

只有当源和处理程序的生存期不同时,才需要取消订阅事件,否则它们同时超出范围,并被垃圾收集在一起。

因此,在这种情况下,不需要IDisposable。无论如何,如果您实现IDisposable,您需要显式地调用它,否则您无法控制何时调用它。

实际上,当Window.CloseWindow订阅事件时,它会使视图模型指向窗口。

反之亦然,因为在Window中存在ViewModel字段。

窗口和视图模型都相互参照。

如果没有其他引用,垃圾回收将完成任务。

如果某些代码调用Dispose,则会调用它。

据我所知,除非用using或显式调用Dispose 来创建窗口,否则这种情况不会发生

这里最好的方法是不实现IDisposable/Dispose:保持简单。

问候

我认为使用事件是一种完全可以接受的方法。要获得更完整的处置模式,请使用以下代码段:

#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

在您的情况下,您的处理方法是:

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;
        }
    }
}

正如Dennis所指出的,您需要保留终结器,以确保在MainWindow关闭时(例如在应用程序退出的情况下)调用Dispose