2013-10-22 62 views
4

當我將ListBox的ItemsSource綁定到List時,綁定引擎在控制消失後保留到列表元素上。這會導致所有列表元素留在內存中。使用ObservalbleCollection時問題消失。爲什麼會發生?綁定到列表導致內存泄漏

窗口標籤

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

後面的代碼裏面的XAML:

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

編輯:要檢查的人istances的漏水,我用螞蟻和.Net內存分析器。兩者都顯示按下GC按鈕後,只有綁定引擎持有對人物的引用。

+0

你是什麼意思的「控制不見了」?它變得無形?它被卸載了嗎? – helb

+0

你如何知道這裏的內存泄漏?你用什麼工具來描述這個? –

+0

如何識別內存在使用List時發生泄漏。 –

回答

5

Ahhh找到了你。現在我明白你的意思了。

您將內容設置爲空,因此您殺死了強制列表框,但仍然將ItemsSource綁定到List,因此ListBox內存未完全釋放。

這不幸是一個衆所周知的問題,並且在MSDN上也有很好的記錄。

如果您沒有綁定到DependencyProperty或實現INotifyPropertyChanged或ObservableCollection的對象,則綁定可能會泄漏內存,並且在完成後您必須解除綁定。

這是因爲如果對象不是DependencyProperty或者沒有實現INotifyPropertyChanged或者沒有實現INotifyCollectionChanged(普通列表沒有實現這個),那麼它通過PropertyDescriptors的AddValueChanged方法使用ValueChanged事件。這會導致CLR從PropertyDescriptor創建一個強對象,並且在大多數情況下,CLR將在全局表中保留對PropertyDescriptor的引用。

因爲綁定必須繼續偵聽更改。當目標保持使用狀態時,此行爲將保持PropertyDescriptor和對象之間的引用處於活動狀態。這可能會導致對象和對象所引用的任何對象的內存泄漏。

問題是...是否實施INotifyPropertyChanged的人?

+0

Person沒有實現INotifyPropertyChanged。但我對這個衆所周知的問題有不同的理解。我明白提供屬性的類應該實現INotifyPropertyChanged,它的作用。 「它通過PropertyDescriptors的AddValueChanged方法使用ValueChanged事件」只有在提供屬性的類是有意義的時候纔有意義,對嗎? – user1182735

+0

不,不是,你幾乎在那裏。像這樣思考:ViewModel中的每個屬性都有一個類型。該類型可能是另一個應實現INotifyPropertyChanged的類。 ViewModel本身也應該實現INotifyPropertyChanged。當綁定入口時,它需要屬性的類型並尋找像INotifyPropertyChanged或INotifyCollectionChanged這樣的接口。而ObservableCollection會在List <>類型中沒有接口。如果沒有找到接口,它將需要強大的參考(不是在任何情況下)。有時綁定需要一個弱引用。總結其擁有財產的類型。 –

+0

我非常感謝您花時間回覆我的問題。我測試了你的原始理論,它與「實施INotifyPropertyChanged的人」有關。它沒有。 Person對象是否保存在內存中與Person是否實現INotifyPropertyChanged無關。它只取決於列表的類型。類型列表意味着它保持,類型ObservableCollection意味着它消失。 – user1182735

0

我看了一下JustTrace內存分析器的例子,除了一個顯而易見的問題之外,爲什麼要殺死視圖模型/無效DataContext並讓視圖運行(在99.9%的情況下,您會終止View和DataContext - 因此ViewModel和綁定去範圍自動)這是我發現的。

這將正常工作,如果您修改例子:

  • 與視圖模型的新實例取代的DataContext,符合市場預期,人的現有實例走出去的範圍爲MS.Internal.Data.DataBingingEngine刷新所有綁定,即使它們是不由WeakPropertyChangedEventManager管理的強引用,或者:
  • ViewModel將IEnumerable的新實例替換爲List,即新Person [0] /簡單地爲null並提高INCP。ViewModel上的PropertyChanged(「List」)

上述修改證明您可以安全地在綁定中使用IEnumerable/IEnumerable。 BTW,Person類不需要執行INPC - TypeDescriptor綁定/ Mode = OneTime在這種情況下沒有任何區別,我也證實了這一點。順便說一句,綁定到IEnumerable/IEnumerable/IList被包裝到EnumerableCollectionView內部類。不幸的是,我沒有機會通過MS.Internal/System.ComponentModel代碼來找出ObservableCollection爲什麼在設置DataContext = null時工作,這可能是因爲微軟在退訂CollectionChanged時做了特別的處理。隨時通過MS.Internal/ComponentModel :)浪費幾個寶貴的生命小時希望它可以幫助