在WPF应用程序中导致UI内存泄漏的ICommand绑定

Ric*_*ley 9 c# linq wpf

我正在构建一个使用LINQ to SQL连接到SQL Server数据库的WPF应用程序.

应用程序的主窗口包含ListView一系列详细视图.所述ItemSourceListView结合到暴露于根视图模型的属性细节视图模型的对象的集合.每个细节视图模型对象组成若干ICommand属性以及暴露细节模型对象的属性,该属性反过来暴露UI中显示的各种数据字段.

使用ANTS内存分析器进行分析表明,泄漏的对象是详细模型对象中包含的对象,以及它们绑定到的一些UI类.来自先前刷新的这些对象的实例不是垃圾收集的.

ANTS有一个工具,允许用户跟踪引用链,以确定保留不需要的内存的原因.当我使用它时,我发现所有出现的链都有一个ICommand.因此,我删除了违规行为ICommand,发现内存泄漏消失了.

不幸的是,我需要ICommand实现一些重要的功能.让我感到困惑的是它首先如何引用细节模型对象 - 它们是详细视图模型对象中两个完全独立的实例变量.

这是详细视图模型对象的构造函数(对RootViewModel的引用用于连接到ICommands的一些方法中的回调.我原先怀疑这可能导致循环引用链,这可能是导致问题,但删除它似乎没有任何影响.)

public CarDataViewModel(CarData carDataItem, RootViewModel parentViewModel)
    {

        _parentViewModel = parentViewModel;
        CarDataModel = carDataItem;
        CompetingCheckboxStatus = CarDataModel.CurrentCar.Competing;
        AcknowledgeAlarm = new ParameterlessCommand(AcknowledgeAlarmClicked);
        Acknowledge = new ParameterlessCommand(AcknowledgeClicked);
        ShowReport = new ParameterlessCommand(ShowReportClicked);
        Cancel = new ParameterlessCommand(CancelClicked);
    }
Run Code Online (Sandbox Code Playgroud)

这里是设置绑定的xaml - AcknowledgeAlarm是ICommand,CarDataModel是详细模型对象:

<ListView x:Name="itemGridView"Grid.Row="1"ScrollViewer.HorizontalScrollBarVisibility="Disabled" ItemsSource="{Binding CarDataViewModels}" IsSynchronizedWithCurrentItem="True" Margin="0,0,0,0">
        <ListView.ItemTemplate>
            <DataTemplate>
                </DataTemplate.Resources>
                <Button Command="{Binding AcknowledgeAlarm}">
                    <Border DataContext="{Binding CarDataModel}" BorderBrush="{StaticResource GrayFadeBrush}" Background="White" BorderThickness="5">
                        <Grid> . . .
Run Code Online (Sandbox Code Playgroud)

Dan*_*iel 10

CanExecuteChanged事件处理是泄漏可能牵连.

WPF希望ICommand实现使用对事件处理程序的弱引用.您正在使用正常的.NET事件,该事件使用强引用,这可能导致此泄漏.

你创建ParameterlessCommand实例的方式似乎意味着CanExecute永远都是真的,你根本不需要这个事件.你实际上是在任何地方发射事件,还是OnCanExecuteChanged未使用的代码?

如果没有,请将事件定义替换为:

public event EventHandler CanExecuteChanged { add {} remove {} }
Run Code Online (Sandbox Code Playgroud)

这样,事件不会存储任何处理程序,并且视图模型避免了对UI元素的强引用.

如果您需要引发事件,最简单的解决方案是使用CommandManager.RequerySuggested,它与ICommand所期望的弱事件语义相匹配:

public event EventHandler CanExecuteChanged {
    add {
        CommandManager.RequerySuggested += value;
    }
    remove {
        CommandManager.RequerySuggested -= value;
    }
}
Run Code Online (Sandbox Code Playgroud)

您应该做的另一件事是INotifyPropertyChanged在视图模型中实现(如果您还没有这样做),并使用它而不是NameChanged为每个属性设置单独的等事件.这是因为当视图模型中的引用返回到UI元素时,WPF中处理各个属性的逻辑会导致内存泄漏:http://support.microsoft.com/kb/938416

INotifyPropertyChanged即使您实际上没有任何更改事件,您也需要实施AFAIK .


我的猜测是,修复这两个问题中的任何一个都会使泄漏消失:错误实现CanExecuteChanged会导致视图模型的强引用被查看,这正是缺乏INotifyPropertyChanged导致泄漏的情况.

但解决这两个问题是个好主意; 不只是其中之一.