I was recently bit by a weird thing with lambda expression and variable captures. The code was a WPF/MVVM application using .NET 4.5 (VS2012). I was using different constructors of my viewmodel to setup the callback for a RelayCommand
(this command would then be bound to a menu item in my view)
In essence, I had the following code:
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; }
}
and then created instances of the above using:
// From some other viewmodel's code:
new MyViewModel(() => new SomeViewModel());
new MyViewModel(() => new SomeOtherViewModel());
These were then bound to a WPF Menu - each menu item had a MyViewModel instance as its data context . The weird thing was that the menus only worked once. Regardless of which of the items I tried, it would call the appropriate Func<ViewModelBase>
- but only one time. If I tried to select another menu item or even the same item again, it simply didn't work. Nothing got called and no output in the VS debug output about any errors.
I'm aware of issues with variable captures in loops, so I made a guess that this issue was related so changed my VM to:
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; }
}
and called it the same way. Now everything works as it should.
Just for fun, I also worked around the whole Func<ViewModelBase>
constructor by creating the appropriate Action
outside of the MyViewModel
constructor:
// This code also works, even without the _creator field in MyViewModel
new MyViewModel(() => SetMainContent(new SomeViewModel()));
new MyViewModel(() => SetMainContent(new SomeOtherViewModel()));
So I managed to get it working, but I'm still curious why it works like this. Why doesn't the compiler properly capture the Func<ViewModelBase>
in the constructor?
I'm guessing the following code would also work
public MyViewModel(Func<ViewModelBase> viewModelCreator)
{
var action = () => { creator = viewModelCreator; SetMainContent(creator()); };
MyCommand = new RelayCommand(action);
}
If so, then the reason it isn't working the first way is that you aren't actually closing around the viewModelCreator
variable, you're closing around the result of calling it.
I'm still playing around with the code in LINQPad, but it doesn't actually seem like I'm getting the same issue that you are. Perhaps it's something specific to RelayCommand
. Could you post more of your code?
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With