Reputation: 3951
With this code you can reproduce my problem 100% times. I created a 'parent' ViewModel:
using Prism.Commands;
using Prism.Mvvm;
using System.Collections.ObjectModel;
namespace WpfApplication1 {
public class ViewModel : BindableBase {
public ObservableCollection<ChildViewModel> Items { get; set; }
public DelegateCommand<ChildViewModel> CloseItem { get; set; }
public ViewModel() {
CloseItem = new DelegateCommand<ChildViewModel>(OnItemClose);
Items = new ObservableCollection<ChildViewModel>(new[] {
new ChildViewModel { Name = "asdf" },
new ChildViewModel { Name = "zxcv" },
new ChildViewModel { Name = "qwer" },
new ChildViewModel { Name = "fdgz" },
new ChildViewModel { Name = "hgjkghk" }
});
}
private void OnItemClose(ChildViewModel obj) {
Items.Remove(obj);
}
}
}
Which holds an ObservableCollection
of ChildViewModel
instances:
using Prism.Mvvm;
namespace WpfApplication1 {
public class ChildViewModel : BindableBase {
public string Name { get; set; }
protected byte[] leakArr = new byte[1024 * 1024 * 10];
}
}
As you can see I declare a 10mb array in each ChildViewModel
. Now the view:
public partial class MainWindow : Window {
public MainWindow() {
InitializeComponent();
DataContext = new ViewModel();
}
}
<Window x:Class="WpfApplication1.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"
xmlns:local="clr-namespace:WpfApplication1"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<TabControl ItemsSource="{Binding Items}">
<TabControl.ItemTemplate>
<DataTemplate>
<DockPanel>
<TextBlock Text="{Binding Path=Name}" DockPanel.Dock="Left" Margin="2,3,0,2" />
<Button DockPanel.Dock="Right" BorderThickness="0" Background="Transparent"
Margin="10,2,2,2"
Command="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type TabControl}}, Path=DataContext.CloseItem}"
CommandParameter="{Binding}">
<Button.Content>
<TextBlock FontWeight="Bold" Text="X" />
</Button.Content>
</Button>
</DockPanel>
</DataTemplate>
</TabControl.ItemTemplate>
</TabControl>
</Window>
Looking at VS2015 memory diagnostic tools I can see that even after every ChildViewModel
had beed removed from Items
collection it's still present in memory with one object (for each vm) still holding a reference to it - a Button
control. What's funny is that if I replace Button
's Command declaration to:
Click="Button_Click"
private void Button_Click(object sender, RoutedEventArgs e) {
(DataContext as ViewModel).CloseItem.Execute(((sender as Button).DataContext as ChildViewModel));
}
There's no memory leak - meaning that the debugging tools aren't showing any disposed ChildViewModel
instances. I don't know if that's a Prism-specific issue, or if it's some kind of a wpf quirk.
I'm using the latest version of both .net and prism library.
Upvotes: 2
Views: 1196
Reputation:
Did you check to see if a custom ICommand implementation had the same behavior?
It is possible we introduced this memory leak when we removed the weakreferences because of a related bug.
Could you please report this as an issue here:
https://github.com/PrismLibrary/Prism/issues
We will investigate and we will look into it.
EDIT: I'm happy to hear this wasn't a Prism issue :)
Upvotes: 2
Reputation: 3951
It seems that I found a solution for this problem (in my app at least) - I stopped binding directly to a ChildViewModel
instance, and instead I'm binding CommandParameter
to the Name
property of ChildViewModel
. Then, in ViewModel
I'm just going through Items collection and removing item that has a matching property value. Now VS diagnostic tools are not showing any objects which should have been GCed and weren't and therefore memory leak is gone.
Prism team is analyzing this problem: https://github.com/PrismLibrary/Prism/issues/345
Upvotes: 2