Marek M.
Marek M.

Reputation: 3951

Prism's DelegateCommand memory leak

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

Answers (2)

user5420778
user5420778

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

Marek M.
Marek M.

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

Related Questions