0

I'm trying to understand some odd behavior I'm seeing with a MVVM RelayCommand whose action is a closure capturing a local variable.

Minimum viable code sample:

using GalaSoft.MvvmLight.CommandWpf;

namespace WpfApplication3
{
    public partial class MainWindow
    {
        public RelayCommand DoIt { get; }

        int i = 0;

        public MainWindow()
        {
            DoIt = new RelayCommand( () =>
            {
                System.Console.WriteLine( "doing it!" );
                button.Content = (++i).ToString();
            } );

            InitializeComponent();
        }
    }
}

XAML:

<Window x:Class="WpfApplication3.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        SizeToContent="WidthAndHeight">
    <Button x:Name="button" Content="Hit me" Command="{Binding DoIt, RelativeSource={RelativeSource AncestorType=Window}}"/>
</Window>

When you tap the "Hit me" button, the label changes to a number which increments for every subsequent tap.

Since i is only used by the RelayCommand action, I want to move the declaration to the constructur as a local variable. But when I do, I get very odd behavior: The command either won't fire at all, or fires once and then stops.

Interestingly enough, if I nix the RelayCommand and wire the closure to the button's Click event, it works no matter where I define i. So it must be something in the way RelayCommand is handling the closure.

Any guesses?

josh2112
  • 829
  • 6
  • 22
  • Can you grab the source code and step through it in the debugger? https://github.com/lbugnion/mvvmlight/blob/master/GalaSoft.MvvmLight/GalaSoft.MvvmLight%20(PCL)/Command/RelayCommand.cs – John Zabroski Dec 07 '18 at 20:41
  • Also, can you try creating a delay timed action to run this on the UI thread and see what happens: ((RelayCommand) DoIt).RaiseCanExecuteChanged(); – John Zabroski Dec 07 '18 at 20:44

1 Answers1

0

The problem is the closure passed to the command gets garbage-collected eventually. Credit to this Stack Overflow answer and this MVVMLight documentation item.

The command action and enable function you pass to RelayCommand are stored with weak references, so unless something besides the RelayCommand is holding onto them, they will be garbage-collected at some point. The solution is to use the keepTargetAlive constructor parameter if your action or enable function are closures.

josh2112
  • 829
  • 6
  • 22