2012-10-04 13 views
10

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?

+1

Hai visto le differenze nell'IL generato per i due approcci? Potrebbe dare qualche suggerimento. – nicodemus13

+0

Se 'SetMainContent' è nella classe' ViewModelBase', come si chiama nella lambda nell'ultimo esempio di codice che funziona? – Pat

+0

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 –

risposta

2

Sto indovinando il seguente codice dovrebbe anche funzionare

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

Se è così, allora il motivo per cui non funziona il primo modo è che non sono in realtà chiudendo intorno alla variabile viewModelCreator, è' ri chiudendo intorno al risultato di chiamarlo.

Sto ancora giocando con il codice in LINQPad, ma in realtà non mi sembra di avere lo stesso problema che tu sei. Forse è qualcosa di specifico per RelayCommand. Potresti pubblicare più del tuo codice?

+0

Grazie per la risposta. RelayCommand proviene da MVVM light: http://bit.ly/QRLCES –

+1

Hmm .. Sto provando a riprodurlo su un altro computer (usando VS2010) usando il codice della mia stessa domanda ma non lo vedo .. Devo andare ritorna al mio codice originale e vedo qual è la differenza (il mio codice domanda è semplificato - e potrebbe essere che ho semplificato troppo e rimosso il problema nel processo) –

Problemi correlati