wpf 绑定到列表导致内存泄漏
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/19511341/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Binding to list causes memory leak
提问by user1182735
When I bind an ItemsSource of a ListBox to a List the binding engine holds on to the list elements after the control is gone. This causes all the list elements to stay in memory. The problem goes away when using an ObservalbleCollection. Why does this happen?
当我将 ListBox 的 ItemsSource 绑定到 List 时,绑定引擎会在控件消失后保留列表元素。这会导致所有列表元素都留在内存中。使用 ObservalbleCollection 时问题就会消失。为什么会发生这种情况?
The xaml inside the window tag
window标签内的xaml
<Grid>
<StackPanel>
<ContentControl Name="ContentControl">
<ListBox ItemsSource="{Binding List, Mode=TwoWay}" DisplayMemberPath="Name"/>
</ContentControl>
<Button Click="Button_Click">GC</Button>
</StackPanel>
</Grid>
Code behind:
后面的代码:
public MainWindow()
{
InitializeComponent();
DataContext = new ViewModel();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
this.DataContext = null;
ContentControl.Content = null;
GC.Collect();
GC.WaitForPendingFinalizers();
}
ViewModel
视图模型
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; }
}
Edit: To check the leaking of the person istances, I used ANTS and .Net memory profiler. Both show that after pushing the GC-button only the binding engine is holding reference to the person objects.
编辑:为了检查人员实例的泄漏,我使用了 ANTS 和 .Net 内存分析器。两者都表明在按下 GC 按钮后,只有绑定引擎持有对 person 对象的引用。
回答by dev hedgehog
Ahhh got you. Now I understand what you mean.
啊哈得到你了。现在我明白你的意思了。
You set the Content to null and so you kill the compelte ListBox but still the ItemsSource binds to List and so ListBox memory is not completely released.
您将 Content 设置为 null,因此您杀死了完整的 ListBox,但 ItemsSource 仍然绑定到 List,因此 ListBox 内存未完全释放。
That is unfortunately a well known issue and also well documented on MSDN.
不幸的是,这是一个众所周知的问题,并且在 MSDN 上也有详细记录。
If you are not binding to a DependencyProperty or a object that implements INotifyPropertyChanged or ObservableCollection then the binding can leak memory, and you will have to unbind when you are done.
如果您没有绑定到 DependencyProperty 或实现 INotifyPropertyChanged 或 ObservableCollection 的对象,则绑定可能会泄漏内存,并且您必须在完成后取消绑定。
This is because if the object is not a DependencyProperty or does not implement INotifyPropertyChanged or not implementing INotifyCollectionChanged (Normal list is not implementing this) then it uses the ValueChanged event via the PropertyDescriptors AddValueChanged method. This causes the CLR to create a strong reference from the PropertyDescriptor to the object and in most cases the CLR will keep a reference to the PropertyDescriptor in a global table.
这是因为如果对象不是 DependencyProperty 或未实现 INotifyPropertyChanged 或未实现 INotifyCollectionChanged(正常列表未实现此),则它通过 PropertyDescriptors AddValueChanged 方法使用 ValueChanged 事件。这会导致 CLR 创建从 PropertyDescriptor 到对象的强引用,并且在大多数情况下,CLR 将在全局表中保留对 PropertyDescriptor 的引用。
Because the binding must continue to listen for changes. This behavior keeps the reference alive between the PropertyDescriptor and the object as the target remains in use. This can cause a memory leak in the object and any object to which the object refers.
因为绑定必须继续监听变化。当目标仍在使用中时,此行为使 PropertyDescriptor 和对象之间的引用保持活动状态。这可能会导致对象和对象引用的任何对象中的内存泄漏。
The question is...is Person implementing INotifyPropertyChanged?
问题是……人是否正在实施 INotifyPropertyChanged?
回答by Mr.M
I had a look at your example with JustTrace memory profiler and apart from an obvious question why would you kill view model / nullify DataContext and leave view running (in 99.9% of cases you'd kill View and DataContext - hence ViewModel and Bindings go of of scope automatically) here's what I found.
我用 JustTrace 内存分析器查看了您的示例,除了一个明显的问题,为什么要杀死视图模型/取消 DataContext 并保持视图运行(在 99.9% 的情况下,您会杀死 View 和 DataContext - 因此 ViewModel 和 Bindings范围自动)这是我发现的。
It will work fine if you modify your example to:
如果您将示例修改为:
- replace DataContext with new instance of view model, as expected, existing instances of Person go out of scope as MS.Internal.Data.DataBingingEngine flushes all bindings, even they were strong refs not managed by WeakPropertyChangedEventManager , or:
- ViewModel to replace List with new instance of IEnumerable i.e. new Person[0]/simply null and raise INCP.PropertyChanged("List") on the ViewModel
- 用新的视图模型实例替换 DataContext,正如预期的那样,现有的 Person 实例超出范围,因为 MS.Internal.Data.DataBingingEngine 刷新所有绑定,即使它们是不受 WeakPropertyChangedEventManager 管理的强引用,或者:
- ViewModel 将 List 替换为 IEnumerable 的新实例,即 new Person[0]/simply null 并在 ViewModel 上引发 INCP.PropertyChanged("List")
Above modifications prove you can safely use IEnumerable/IEnumerable in binding. BTW, Person class doesn't need to implement INPC neither - TypeDescriptor binding/Mode=OneTime don't make any difference in this case, I verified that too. BTW, bindings to IEnumerable/IEnumerable/IList are wrapped into EnumerableCollectionView internal class. Unfortunatelly, I didn;t have a chance to go through MS.Internal/System.ComponentModel code to find out why ObservableCollection works when setting DataContext = null, probably because Microsoft guys did a special handing when unsubscribing from CollectionChanged. Feel free to waste few precious lifetime hours on going through MS.Internal/ComponentModel :) Hope It helps
以上修改证明您可以安全地在绑定中使用 IEnumerable/IEnumerable。顺便说一句,Person 类也不需要实现 INPC - TypeDescriptor binding/Mode=OneTime 在这种情况下没有任何区别,我也验证了这一点。顺便说一句,对 IEnumerable/IEnumerable/IList 的绑定被包装到 EnumerableCollectionView 内部类中。不幸的是,我没有机会通过 MS.Internal/System.ComponentModel 代码找出为什么 ObservableCollection 在设置 DataContext = null 时起作用,可能是因为 Microsoft 人员在取消订阅 CollectionChanged 时做了特殊处理。随意浪费几个宝贵的生命时间来浏览 MS.Internal/ComponentModel :) 希望它有帮助

