不一致的行为:带有闭包捕获局部变量的 MVVM RelayCommandWPF

Inconsistent behavior: MVVM RelayCommandWPF with closure capturing local variable

我试图理解我在 MVVM RelayCommand 中看到的一些奇怪行为,它的行为是捕获局部变量的闭包。

最小可行代码示例:

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>

当您点击 "Hit me" 按钮时,标签会变为一个数字,该数字会随着每次后续点击而递增。

由于 i 仅由 RelayCommand 操作使用,我想将声明作为局部变量移动到构造函数中。但是当我这样做时,我会得到非常奇怪的行为:该命令根本不会触发,或者触发一次然后停止。

有趣的是,如果我取消 RelayCommand 并将闭包连接到按钮的 Click 事件,那么无论我在何处定义 i,它都有效。所以这一定是 RelayCommand 处理闭包的方式。

有什么猜测吗?

问题是传递给命令的闭包最终得到 garbage-collected。感谢 this Stack Overflow answer and this MVVMLight documentation item

您传递给 RelayCommand 的命令操作和启用函数存储在弱引用中,因此除非 RelayCommand 之外的其他内容保留在它们上面,否则它们有时会 garbage-collected观点。如果您的操作或启用函数是闭包,解决方案是使用 keepTargetAlive 构造函数参数。