Итак, я использую WPF 3.5 с MVVM + метод DataTemplate для загрузки 2 представлений на GUI. При профилировании памяти я заметил, что элементы, сгенерированные как часть контейнера элементов управления items, сохраняются в памяти и не получают GCed даже после выгрузки представления!
Я только что провел тесты и обнаружил, что это воспроизводимо даже для самого простого кода... Вы, ребята, можете проверить сами.
XAML:
<Window x:Class="ContentControlVMTest.Window2"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:ContentControlVMTest"
Title="Window2" Height="300" Width="300">
<DockPanel LastChildFill="True">
<CheckBox Click="CheckBox_Click" Content="Test1?"
DockPanel.Dock="Top" Margin="5"/>
<ContentControl x:Name="contentControl">
<ContentControl.Resources>
<DataTemplate DataType="{x:Type local:Test3}">
<TextBlock Text="{Binding C}" Margin="5"/>
</DataTemplate>
<DataTemplate DataType="{x:Type local:Test1}">
<DockPanel LastChildFill="True" Margin="5">
<TextBlock Text="{Binding A}"
DockPanel.Dock="Top"
Margin="5"/>
<ListBox ItemsSource="{Binding Bs}"
DisplayMemberPath="B"
Margin="5"/>
</DockPanel>
</DataTemplate>
</ContentControl.Resources>
</ContentControl>
</DockPanel>
</Window>
Code Behind:
public class Test3
{
public string C { get; set; }
}
public class Test2
{
public string B { get; set; }
}
public class Test1
{
public string A { get; set; }
private List<Test2> _Bs;
public List<Test2> Bs
{
get
{
return _Bs;
}
set
{
_Bs = value;
}
}
}
public partial class Window2 : Window
{
public Window2()
{
InitializeComponent();
this.KeyDown += Window_KeyDown;
}
private void Window_KeyDown
(object sender, System.Windows.Input.KeyEventArgs e)
{
if (Keyboard.IsKeyDown(Key.LeftCtrl))
if (Keyboard.IsKeyDown(Key.LeftShift))
if (Keyboard.IsKeyDown(Key.LeftAlt))
if (Keyboard.IsKeyDown(Key.G))
{
GC.Collect(2, GCCollectionMode.Forced);
GC.WaitForPendingFinalizers();
GC.Collect(2, GCCollectionMode.Forced);
GC.WaitForPendingFinalizers();
GC.Collect(3, GCCollectionMode.Forced);
GC.WaitForPendingFinalizers();
GC.Collect(3, GCCollectionMode.Forced);
}
}
private void CheckBox_Click(object sender, RoutedEventArgs e)
{
if (((CheckBox)sender).IsChecked.GetValueOrDefault(false))
{
var x = new Test1() { A = "Test1 A" };
x.Bs = new List<Test2>();
for (int i = 1; i < 10000; i++ )
{
x.Bs.Add(new Test2() { B = "Test1 B " + i });
}
contentControl.Content = x;
}
else
{
contentControl.Content = new Test3() { C = "Test3 C" };
}
}
}
Я выполняю принудительный GC с помощью Left Shift + Alt + Ctrl + G. Все элементы для Test1
или Test3
представления и View Model становятся мертвыми после их корректной выгрузки. Так что это соответствует ожиданиям.
Но коллекция, созданная в модели Test1
(которая имеет объекты Test2
), остается зажатой в памяти. И это указывает на то, что массив используется контейнером items listbox, потому что он показывает количество девиртуализированных элементов из listbox! Этот прикрепленный массив меняет свой размер, когда мы сворачиваем или восстанавливаем представление в режиме просмотра Test1
! Один раз это было 16 элементов, а в другой раз - 69 элементов при профилировании.
Это означает, что WPF выполняет пиннинг элементов, созданных в элементах управления! Кто-нибудь может объяснить это? Есть ли у этого какой-нибудь существенный недостаток?
Большое спасибо.