dlf
dlf

Reputation: 9383

ItemsControl keeps reference to previous DataContext after DataContext set to null

Update: To clarify, this is a question about memory leaks, not about the UI failing to refresh to reflect the new null DataContext (that part works fine).

It appears that if you set an ItemsControl's DataContext to null, it will continue to hold a reference to its old datacontext until such time as you assign it a new one that is not null. You can confirm this with the code at the bottom of the question. Click "Set DataContext to null", then click "Collect garbage" as many times as you want. The "Foo finalized" message will never appear. Then click "Set DataContext to empty object", collect garbage again, and you'll see the finalizer run right away.

I had a memory leak in my application due to a faulty assumption that one of my ItemsControls (a DataGrid) would release all its references to its old datacontext after TheGrid.DataContext = null. Why doesn't it? Is this expected behavior?

Update: The path to root provided by the VS2015 Diagnostic Tools after clicking "Set DataContext to null" looks like this. I don't think anything after the Foo itself comes from my code:

XAML:

<Window x:Class="WpfApplication.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        mc:Ignorable="d"
        Title="MainWindow" Height="350" Width="525">

    <StackPanel>
        <ItemsControl ItemsSource="{Binding Foos}" />

        <Button Content="Set DataContext to null" Click="SetDataContextToNullClicked"/>
        <Button Content="Set DataContext to empty object" Click="SetDataContextToEmptyObjectClicked" />
        <Button Content="Collect garbage" Click="CollectGarbageClicked"/>
    </StackPanel>
</Window>

C#:

namespace WpfApplication
{
    using System;
    using System.Collections.Generic;
    using System.Diagnostics;
    using System.Linq;
    using System.Windows;

    public class Foo
    {
        ~Foo()
        {
            Debug.Print("Foo finalized");
        }
    }

    public class FoosViewModel // implementing INotifyPropertyChanged here doesn't help
    {
        public FoosViewModel(IEnumerable<Foo> foos)
        {
            Foos = foos;
        }

        public IEnumerable<Foo> Foos { get; }
    }

    public partial class MainWindow
    {
        public MainWindow()
        {
            DataContext = new FoosViewModel(Enumerable.Repeat(new Foo(), 1));
            InitializeComponent();
        }

        private void SetDataContextToNullClicked(object sender, RoutedEventArgs e)
        {
            DataContext = null;
        }

        private void SetDataContextToEmptyObjectClicked(object sender, RoutedEventArgs e)
        {
            DataContext = new FoosViewModel(Enumerable.Empty<Foo>());
        }

        private void CollectGarbageClicked(object sender, RoutedEventArgs e)
        {
            GC.Collect();
            GC.WaitForPendingFinalizers();
        }
    }
}

Upvotes: 0

Views: 875

Answers (1)

Abdulkarim Kanaan
Abdulkarim Kanaan

Reputation: 1763

you can get explanation in here https://stackoverflow.com/a/19511796/2696230

Using list will result in a strong reference ( a memory leak ). the simple solution is just to change List to ObservableCollection, unless the view (itself) is released

Upvotes: 1

Related Questions