Sono stato recentemente morso da una cosa strana con espressione lambda e cattura variabile. Il codice era un'applicazione WPF/MVVM che utilizza .NET 4.5 (VS2012). Stavo usando diversi costruttori della mia ViewModel per impostare la richiamata per un RelayCommand
(questo comando sarebbe quindi essere associato a una voce di menu a mio avviso)Perché devo catturare il lambda in una variabile di campo in chiamata al costruttore
In sostanza, ho avuto il seguente codice:
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; }
}
e poi ha creato le istanze di cui sopra utilizzando:
// From some other viewmodel's code:
new MyViewModel(() => new SomeViewModel());
new MyViewModel(() => new SomeOtherViewModel());
Questi sono stati poi legati a un menu WPF - ciascuna voce di menu aveva un'istanza MyViewModel come il suo contesto dati. La cosa strana era che i menu funzionavano solo una volta. Indipendentemente da quale degli elementi che ho provato, chiamerebbe l'appropriato Func<ViewModelBase>
- ma solo una volta. Se ho provato a selezionare un'altra voce di menu o anche lo stesso elemento di nuovo, semplicemente non ha funzionato. Nulla è stato chiamato e nessun output nell'output di debug VS su eventuali errori.
Sono a conoscenza di problemi con catture variabili in loop, così ho fatto una supposizione che questo problema è stato legato in modo cambiato il mio 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; }
}
e lo ha chiamato nello stesso modo. Ora tutto funziona come dovrebbe.
Solo per divertimento, ho anche lavorato in giro per tutta Func<ViewModelBase>
costruttore creando l'appropriato Action
esterno del MyViewModel
costruttore:
// This code also works, even without the _creator field in MyViewModel
new MyViewModel(() => SetMainContent(new SomeViewModel()));
new MyViewModel(() => SetMainContent(new SomeOtherViewModel()));
così sono riuscito a farlo funzionare, ma io sono ancora curioso il motivo per cui funziona così Perché il compilatore non acquisisce correttamente il Func<ViewModelBase>
nel costruttore?
Hai visto le differenze nell'IL generato per i due approcci? Potrebbe dare qualche suggerimento. – nicodemus13
Se 'SetMainContent' è nella classe' ViewModelBase', come si chiama nella lambda nell'ultimo esempio di codice che funziona? – Pat
SetMainContent utilizza i messaggi (da MVVMLight) per inviare l'istanza viewmodel al viewmodel della finestra principale. Lo assegnerà quindi a una proprietà Content che viene sottoposta a rendering nell'interfaccia utente. Proverò a trovare un esempio di codice più completo che mostri questo problema –