如果在XAML文件中,我将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; }
}
答案 0 :(得分:2)
这是一个老问题,但我最近偶然发现了这个话题,值得回答。
这种奇怪行为的原因来自RelayCommand
的MVVM Light实现。 execute和canexecute处理程序在relay命令中存储为WeakAction _execute
和WeakFunc<bool> _canExecute
。 WeakAction
试图在UI仍然由于某种原因引用该命令时允许GC清理视图模型。
跳过一些细节,底线是:指定一个viewmodel方法作为处理程序工作得很好,因为只要viewmodel保持活动状态,WeakAction
就会保持活着状态。对于动态创建的Action
,情况就不同了。如果对该操作的唯一引用位于RelayCommand
内,则只存在弱引用,GC可以随时收集操作,将整个RelayCommand
变为死砖。
好的,时间详情。 WeakAction
的实现不是盲目地存储对行为的弱引用 - 这将导致许多消失的引用。相反,存储弱Delegate.Target
引用和Delegate.MethodInfo
的组合。对于静态方法,该方法将通过强引用存储。
现在,这导致了三类lambda:
() => I_dont_access_anything_nonstatic()
将存储为强引用() => DoIt(field)
闭包方法将在viewmodel类中创建,操作目标是viewmodel,只要viewmodel保持活动状态,它就会保持活动状态。() => DoIt(p1)
闭包将创建一个单独的类实例来存储捕获的变量。这个单独的实例将成为行动目标,并且不会有任何强烈的参考 - GC会在某个时候清理重要提示:据我所知,这种行为可能会随着Roslyn发生变化:Delegate caching behavior changes in Roslyn所以今天有案例(2)的工作代码有可能变成不工作与Roslyn的代码。但是,我没有测试这个假设,它可能会完全不同。
答案 1 :(得分:0)
你的问题是调用Method DoIt是在lamda表达式创建的另一个匿名方法中。你的表达
() => DoIt(p1);
创建一个没有参数的匿名方法(看作第一个大括号中没有提供变量)。
我建议您使用mvvm-light中的通用构造函数来创建Command:
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绑定到&#34; Command&#34;。