私は最近、ラムダ式と変数のキャプチャに関する奇妙なことに悩まされていました。コードは、.NET 4.5 (VS2012) を使用した WPF/MVVM アプリケーションでした。ビューモデルのさまざまなコンストラクターを使用してコールバックをセットアップしていました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>
が、1回だけです。別のメニュー項目または同じ項目をもう一度選択しようとしても、うまくいきませんでした。エラーに関する 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>
コンパイラがコンストラクタで を適切にキャプチャしないのはなぜですか?