关于 c#:Inconsistent 行为:MVVM RelayCommandWPF 与闭包捕获局部变量

Inconsistent behavior: MVVM RelayCommandWPF with closure capturing local variable

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

最小可行代码示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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:

1
2
3
4
5
6
<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 处理闭包的方式。

有什么猜测吗?


问题是传递给命令的闭包最终会被垃圾收集。归功于此 Stack Overflow 答案和此 MVVMLight 文档项。

您传递给 RelayCommand 的命令操作和启用函数与弱引用一起存储,因此除非 RelayCommand 之外的其他东西持有它们,否则它们将在某个时候被垃圾收集。如果您的操作或启用函数是闭包,则解决方案是使用 keepTargetAlive 构造函数参数。