绑定到列表会导致内存泄漏

时间:2013-10-22 07:10:33

标签: c# wpf memory binding memory-leaks

当我将ListBox的ItemsSource绑定到List时,绑定引擎在控件消失后保持列表元素。这会导致所有列表元素保留在内存中。使用ObservalbleCollection时问题就消失了。为什么会这样?

窗口标记内的xaml

<Grid>
    <StackPanel>
        <ContentControl Name="ContentControl">
            <ListBox ItemsSource="{Binding List, Mode=TwoWay}" DisplayMemberPath="Name"/>
        </ContentControl>
        <Button Click="Button_Click">GC</Button>
    </StackPanel>
</Grid>

代码背后:

public MainWindow()
    {
        InitializeComponent();
        DataContext = new ViewModel();
    }

private void Button_Click(object sender, RoutedEventArgs e)
    {
        this.DataContext = null;
        ContentControl.Content = null;
        GC.Collect();
        GC.WaitForPendingFinalizers();
    }

视图模型

class ViewModel : INotifyPropertyChanged
{
    //Implementation of INotifyPropertyChanged ...

    //Introducing ObservableCollection as type resolves the problem
    private IEnumerable<Person> _list = 
            new List<Person> { new Person { Name = "one" }, new Person { Name = "two" } };

    public IEnumerable<Person> List
    {
        get { return _list; }
        set
        {
            _list = value;
            RaisePropertyChanged("List");
        }
    }

class Person
{
    public string Name { get; set; }
}

编辑:为了检查人员泄漏,我使用了ANTS和.Net内存分析器。两者都显示在按下GC按钮后,只有绑定引擎保持对人物对象的引用。

2 个答案:

答案 0 :(得分:6)

啊,啊,你找到了。现在我明白你的意思了。

您将内容设置为null,因此您终止了强制ListBox但仍然将ItemsSource绑定到List,因此ListBox内存未完全释放。

遗憾的是,这是一个众所周知的问题,也在MSDN上有详细记载。

如果您没有绑定到DependencyProperty或实现INotifyPropertyChanged或ObservableCollection的对象,那么绑定可能会泄漏内存,并且您必须在完成后取消绑定。

这是因为如果对象不是DependencyProperty或者没有实现INotifyPropertyChanged或者没有实现INotifyCollectionChanged(正常列表没有实现这个),那么它通过PropertyDescriptors AddValueChanged方法使用ValueChanged事件。这会导致CLR从PropertyDescriptor创建一个强引用到对象,在大多数情况下,CLR将在全局表中保留对PropertyDescriptor的引用。

因为绑定必须继续侦听更改。当目标保持使用时,此行为使PropertyDescriptor和对象之间的引用保持活动状态。这可能导致对象中的内存泄漏以及对象引用的任何对象。

问题是......是实施INotifyPropertyChanged的人吗?

答案 1 :(得分:1)

我看了一下你的JustTrace内存分析器的例子,除了一个明显的问题,为什么你会杀死视图模型/ nullify DataContext并让视图保持运行(在99.9%的情况下你会杀掉View和DataContext - 因此ViewModel和Bindings自动变换范围这里是我找到的。

如果您将示例修改为:

,它将正常工作
  • 将DataContext替换为新的视图模型实例,正如预期的那样,现有的Person实例超出了范围,因为MS.Internal.Data.DataBingingEngine刷新所有绑定,即使它们是强引用,也不是由WeakPropertyChangedEventManager管理,或者:
  • ViewModel用新的IEnumerable实例替换List,即新的Person [0] /只是null并在ViewModel上提升INCP.PropertyChanged(&#34; List&#34;)

以上修改证明您可以安全地在绑定中使用IEnumerable / IEnumerable。 BTW,Person类不需要实现INPC - TypeDescriptor绑定/ Mode = OneTime在这种情况下没有任何区别,我也验证了。 BTW,绑定到IEnumerable / IEnumerable / IList被包装到EnumerableCollectionView内部类中。不幸的是,我没有机会通过MS.Internal / System.ComponentModel代码来找出ObservableCollection在设置DataContext = null时的工作原理,可能是因为微软的人在取消订阅CollectionChanged时做了特别处理。通过MS.Internal / ComponentModel可以浪费一些宝贵的一生时间:)希望它有所帮助