来自 lambda 的带有构造函数参数的 RelayCommand
RelayCommand from lambda with constructor parameters
如果在 XAML 文件中,我从以下 class 中将 Button 绑定到 "Command",则单击 Button 不会导致执行 DoIt:
class Thing()
{
public Thing(Foo p1)
{
Command = new RelayCommand(() => DoIt(p1));
}
private DoIt(Foo p)
{
p.DoSomething();
}
public ICommand Command { get; private set; }
}
但是,如果我从 p1 初始化一个字段并将该字段作为参数传递给 lambda 中的方法调用,它确实有效:
class Thing()
{
private Foo field;
public Thing(Foo p1)
{
field = p1;
Command = new RelayCommand(() => DoIt(field));
}
private DoIt(Foo p)
{
p.DoSomething();
}
public ICommand Command { get; private set; }
}
为什么前者失败,而后者却如预期的那样工作?
可能相关:How do closures work behind the scenes? (C#)
编辑:澄清一下,以下内容也适用于我。但是,我还是很想知道为什么第二个例子如我所料,而第一个却没有。
class Thing()
{
private Foo field;
public Thing(Foo p1)
{
field = p1;
Command = new RelayCommand(DoIt);
//Command = new RelayCommand(() => DoIt()); Equivalent?
}
private DoIt()
{
field.DoSomething();
}
public ICommand Command { get; private set; }
}
你的问题是调用方法 DoIt 是在另一个由 lamda 表达式创建的匿名方法中。你的表情
() => DoIt(p1);
创建一个不带参数的匿名方法(因为第一个大括号中没有提供变量)。
我建议您使用 mvvm-light 中的通用构造函数来创建命令:
class Thing
{
public Thing()
{
Command = new GalaSoft.MvvmLight.Command.RelayCommand<bool>(DoIt);
}
private void DoIt(bool p)
{
p.DoSomething(p);
}
public System.Windows.Input.ICommand Command { get; private set; }
}
然后只需将 Button 绑定到 "Command"。
这是一个老问题,但我最近偶然发现了这个话题,值得回答。
这种奇怪行为的原因源于 RelayCommand
的 MVVM Light 实现。 execute 和 canexecute 处理程序在中继命令中存储为 WeakAction _execute
和 WeakFunc<bool> _canExecute
。 WeakAction
试图在命令仍被 UI 出于某种原因引用时允许 GC 清理视图模型。
跳过一些细节,最重要的是:将视图模型方法分配为处理程序非常有效,因为只要视图模型保持活动状态,WeakAction
就会保持活动状态。对于动态创建的Action
,情况就不同了。如果对该动作的唯一引用在 RelayCommand
内部,则只存在弱引用,GC 可以随时收集该动作,将整个 RelayCommand
变成一块死砖。
好的,是时候了解详情了。 WeakAction
的实现并不是盲目地存储对操作的弱引用——这会导致许多引用消失。相反,存储了弱 Delegate.Target
引用和 Delegate.MethodInfo
的组合。对于静态方法,该方法将通过强引用存储。
现在,这导致了三类 lambda:
- 静态方法:
() => I_dont_access_anything_nonstatic()
将存储为强引用
- 闭包成员变量:
() => DoIt(field)
闭包方法将在视图模型中创建class,动作目标是视图模型,只要视图模型保持活动状态,就会保持活动状态。
- 局部变量闭包:
() => DoIt(p1)
闭包将创建一个单独的class实例来存储捕获的变量。这个单独的实例将是操作目标,不会有任何强引用 - GC 在某个时候清理
重要提示: 据我所知,这种行为可能会随着 Roslyn 的变化而改变: 所以今天的工作代码有可能与案例 (2)使用 Roslyn 变成非工作代码。但是,我没有测试这个假设,它可能会完全不同。
如果在 XAML 文件中,我从以下 class 中将 Button 绑定到 "Command",则单击 Button 不会导致执行 DoIt:
class Thing()
{
public Thing(Foo p1)
{
Command = new RelayCommand(() => DoIt(p1));
}
private DoIt(Foo p)
{
p.DoSomething();
}
public ICommand Command { get; private set; }
}
但是,如果我从 p1 初始化一个字段并将该字段作为参数传递给 lambda 中的方法调用,它确实有效:
class Thing()
{
private Foo field;
public Thing(Foo p1)
{
field = p1;
Command = new RelayCommand(() => DoIt(field));
}
private DoIt(Foo p)
{
p.DoSomething();
}
public ICommand Command { get; private set; }
}
为什么前者失败,而后者却如预期的那样工作?
可能相关:How do closures work behind the scenes? (C#)
编辑:澄清一下,以下内容也适用于我。但是,我还是很想知道为什么第二个例子如我所料,而第一个却没有。
class Thing()
{
private Foo field;
public Thing(Foo p1)
{
field = p1;
Command = new RelayCommand(DoIt);
//Command = new RelayCommand(() => DoIt()); Equivalent?
}
private DoIt()
{
field.DoSomething();
}
public ICommand Command { get; private set; }
}
你的问题是调用方法 DoIt 是在另一个由 lamda 表达式创建的匿名方法中。你的表情
() => DoIt(p1);
创建一个不带参数的匿名方法(因为第一个大括号中没有提供变量)。
我建议您使用 mvvm-light 中的通用构造函数来创建命令:
class Thing
{
public Thing()
{
Command = new GalaSoft.MvvmLight.Command.RelayCommand<bool>(DoIt);
}
private void DoIt(bool p)
{
p.DoSomething(p);
}
public System.Windows.Input.ICommand Command { get; private set; }
}
然后只需将 Button 绑定到 "Command"。
这是一个老问题,但我最近偶然发现了这个话题,值得回答。
这种奇怪行为的原因源于 RelayCommand
的 MVVM Light 实现。 execute 和 canexecute 处理程序在中继命令中存储为 WeakAction _execute
和 WeakFunc<bool> _canExecute
。 WeakAction
试图在命令仍被 UI 出于某种原因引用时允许 GC 清理视图模型。
跳过一些细节,最重要的是:将视图模型方法分配为处理程序非常有效,因为只要视图模型保持活动状态,WeakAction
就会保持活动状态。对于动态创建的Action
,情况就不同了。如果对该动作的唯一引用在 RelayCommand
内部,则只存在弱引用,GC 可以随时收集该动作,将整个 RelayCommand
变成一块死砖。
好的,是时候了解详情了。 WeakAction
的实现并不是盲目地存储对操作的弱引用——这会导致许多引用消失。相反,存储了弱 Delegate.Target
引用和 Delegate.MethodInfo
的组合。对于静态方法,该方法将通过强引用存储。
现在,这导致了三类 lambda:
- 静态方法:
() => I_dont_access_anything_nonstatic()
将存储为强引用 - 闭包成员变量:
() => DoIt(field)
闭包方法将在视图模型中创建class,动作目标是视图模型,只要视图模型保持活动状态,就会保持活动状态。 - 局部变量闭包:
() => DoIt(p1)
闭包将创建一个单独的class实例来存储捕获的变量。这个单独的实例将是操作目标,不会有任何强引用 - GC 在某个时候清理
重要提示: 据我所知,这种行为可能会随着 Roslyn 的变化而改变: