为什么我必须在调用构造函数时将lambda捕获到字段变量

时间:2012-10-04 12:24:04

标签: c# wpf lambda closures

我最近对lambda表达式和变量捕获有点奇怪。代码是使用.NET 4.5(VS2012)的WPF / MVVM应用程序。我正在使用我的viewmodel的不同构造函数来设置RelayCommand的回调(此命令将绑定到我视图中的菜单项)

实质上,我有以下代码:

public class MyViewModel : ViewModelBase
{
    public MyViewModel(Action menuCallback)
    {
        MyCommand = new RelayCommand(menuCallback);
    }

    public MyViewModel(Func<ViewModelBase> viewModelCreator)
    // I also tried calling the other constructor, but the result was the same
    //  : this(() => SetMainContent(viewModelCreator())
    {
        Action action = () => SetMainContent(viewModelCreator());
        MyCommand = new RelayCommand(action);
    }

    public ICommand MyCommand { get; private set; }
}

然后使用以下方法创建上述实例:

// From some other viewmodel's code:
new MyViewModel(() => new SomeViewModel());
new MyViewModel(() => new SomeOtherViewModel());

然后将它们绑定到WPF菜单 - 每个菜单项都有一个MyViewModel实例作为其数据上下文。奇怪的是菜单只运行一次。无论我尝试过哪个项目,它都会调用相应的Func<ViewModelBase> - 但只有一次。如果我尝试再次选择另一个菜单项或甚至同一项,它根本无法正常工作。 VS调试输出中没有任何调用和输出有关任何错误。

我知道循环中变量捕获的问题,所以我猜测这个问题是相关的,所以我将VM更改为:

public class MyViewModel : ViewModelBase
{
    public MyViewModel(Action buttonCallback)
    {
        MyCommand = new RelayCommand(buttonCallback);
    }
    private Func<ViewModelBase> _creator;
    public MyViewModel(Func<ViewModelBase> viewModelCreator)
    {
        // Store the Func<> to a field and use that in the Action lambda
        _creator = viewModelCreator;
        var action = () => SetMainContent(_creator());
        MyCommand = new RelayCommand(action);
    }

    public ICommand MyCommand { get; private set; }
}

并以同样的方式调用它。现在一切正常。

为了好玩,我还通过在Func<ViewModelBase>构造函数之外创建适当的Action来解决整个MyViewModel构造函数:

// This code also works, even without the _creator field in MyViewModel
new MyViewModel(() => SetMainContent(new SomeViewModel()));
new MyViewModel(() => SetMainContent(new SomeOtherViewModel()));

所以我设法让它工作,但我仍然很好奇它为什么会这样。为什么编译器没有正确捕获构造函数中的Func<ViewModelBase>

1 个答案:

答案 0 :(得分:2)

我猜测以下代码也可以使用

public MyViewModel(Func<ViewModelBase> viewModelCreator)
{
    var action = () => { creator = viewModelCreator; SetMainContent(creator()); };
    MyCommand = new RelayCommand(action);
}

如果是这样的话,那么第一种方式不起作用的原因是你实际上并没有围绕viewModelCreator变量进行关闭,而是关闭了调用它的结果。

我仍在使用LINQPad中的代码,但实际上我似乎并没有遇到与您相同的问题。也许这是RelayCommand特有的。你能发布更多的代码吗?