C# 来自lambda的具有构造函数参数的RelayCommand

C# 来自lambda的具有构造函数参数的RelayCommand,c#,lambda,closures,mvvm-light,C#,Lambda,Closures,Mvvm Light,如果在XAML文件中,我将按钮绑定到以下类中的“Command”,则单击按钮不会导致执行DoIt: class Thing() { public Thing(Foo p1) { Command = new RelayCommand(() => DoIt(p1)); } private DoIt(Foo p) { p.DoSomething(); } public ICommand Command { get; private set; } }

如果在XAML文件中,我将按钮绑定到以下类中的“Command”,则单击按钮不会导致执行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; }
}
为什么前者失败了,而后者却如期发挥作用

可能相关:

编辑:为了澄清,以下内容也适用于我。然而,我仍然想知道为什么第二个例子做了我所期望的,而第一个没有

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; }
}
类的东西
{
公共事物
{
Command=new-GalaSoft.MvvmLight.Command.RelayCommand(DoIt);
}
私人无效文件(bool p)
{
p、 剂量测定法(p);
}
public System.Windows.Input.ICommand命令{get;private set;}
}

然后将按钮绑定到“命令”上。

这是一个老问题,但我最近偶然发现了这个话题,值得回答

这种奇怪行为的原因源自
RelayCommand
的MVVM Light实现。execute和canexecute处理程序存储为中继命令中的
WeakAction\u execute
WeakFunc\u canexecute
WeakAction
试图在UI出于某种原因仍引用该命令时,允许对viewmodels进行GC清理

跳过一些细节,底线是:将viewmodel方法指定为处理程序非常有效,因为只要viewmodel保持活动状态,
WeakAction
就会保持活动状态。对于动态创建的
操作
,情况有所不同。如果对该操作的唯一引用位于
RelayCommand
内,则只有弱引用存在,GC可以随时收集该操作,从而将整个
RelayCommand
变成一块死砖块

好的,是时候了解细节了。
WeakAction
的实现并不是盲目地存储对操作的弱引用-这将导致许多引用消失。相反,将存储弱的
Delegate.Target
引用和
Delegate.MethodInfo
的组合。对于静态方法,该方法将通过强引用存储

现在,这导致了三类lambda:

  • 静态方法:
    ()=>我不访问任何东西\u nonstatic()
    将作为强引用存储
  • 成员变量上的闭包:
    ()=>DoIt(field)
    闭包方法将在viewmodel类中创建,操作目标是viewmodel,只要viewmodel保持活动状态,闭包方法就会保持活动状态
  • 局部变量上的闭包:
    ()=>DoIt(p1)
    闭包将创建一个单独的类实例来存储捕获的变量。这个单独的实例将是操作目标,并且不会有任何对它的强引用-GC会在某个时候清理

  • 重要提示:据我所知,这种行为可能会随着Roslyn而改变:因此,现在使用case(2)的工作代码有可能会变成使用Roslyn的非工作代码。但是,我没有测试这个假设,它的结果可能完全不同。

    我不想接受绑定中的参数。也就是说,我想给它一个构造函数参数,我想在DoIt中使用该参数的值。我可以使DoIt无参数,将构造函数参数p1分配给一个字段,并使用DoIt中的字段(而不是将字段的值作为参数传递给DoIt),但我想确切地了解这里发生了什么。@。问题是创建lamda表达式会创建一个匿名内部方法,然后调用该方法。您可以这样看:
    public Thing(Foo p1){Command=new RelayCommand(AnonymousMeth());}private void AnonymousMeth(){DoIt(p1);}
    因此,lamda表达式生成的方法不知道如何处理p1。您可以(如正确描述的)通过使用私有字段来避免此问题。