2012-10-04 13 views
10

? Me ha picado algo extraño con la expresión lambda y las capturas variables. El código era una aplicación WPF/MVVM que usa .NET 4.5 (VS2012). Yo estaba usando diferentes constructores de mi modelo de vista de configurar la devolución de llamada para un RelayCommand (este comando sería entonces estar unido a un elemento de menú en mi opinión)¿Por qué tengo que capturar la lambda a una variable de campo en la llamada al constructor

En esencia, tenía el siguiente código:

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; } 
} 

y luego creó instancias del anterior usando:

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

Estos fueron obligados a un menú de WPF - cada elemento del menú tenía una instancia MyViewModel como su contexto de datos. Lo extraño fue que los menús solo funcionaron una vez. Independientemente de cuál de los artículos probé, llamaría al Func<ViewModelBase> apropiado, pero solo una vez. Si traté de seleccionar otra opción del menú o incluso el mismo artículo nuevamente, simplemente no funcionó. No se ha llamado nada y no hay salida en la salida de depuración de VS sobre ningún error.

Soy consciente de los problemas con las capturas de variables en bucles, así que hice una conjetura que esta cuestión se relaciona de modo cambiado de máquina virtual para:

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; } 
} 

y lo llamó de la misma manera. Ahora todo funciona como debería.

Sólo por diversión, también trabajé alrededor de toda la Func<ViewModelBase> constructor mediante la creación de la adecuada ActionMyViewModel fuera del constructor:

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

Así que me las arreglé para conseguir que funcione, pero todavía estoy curioso por qué funciona así ¿Por qué el compilador no captura correctamente el Func<ViewModelBase> en el constructor?

+1

¿Has mirado las diferencias en el IL generado para los dos enfoques? Puede dar algunas pistas. – nicodemus13

+0

Si 'SetMainContent' está en la clase' ViewModelBase', ¿cómo lo está llamando en la lambda en el último ejemplo de código que funciona? – Pat

+0

SetMainContent usa mensajes (desde MVVMLight) para enviar la instancia de viewmodel al modelo de vista de la ventana principal. Luego lo asignará a una propiedad de contenido que se procesa en la interfaz de usuario. Trataré de encontrar un ejemplo de código más completo que muestre este problema –

Respuesta

2

Estoy adivinando el siguiente código también funcionaría

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

Si es así, entonces la razón por la que no está funcionando la primera manera es que usted no está realmente cerrando alrededor de la variable viewModelCreator, que' estamos cerrando el resultado de llamarlo.

todavía estoy jugando con el código en LINQPad, pero en realidad no parece que estoy recibiendo el mismo problema que tú. Tal vez es algo específico de RelayCommand. ¿Podrías publicar más de tu código?

+0

Gracias por la respuesta. RelayCommand es de MVVM light: http://bit.ly/QRLCES –

+1

Hmm .. Tratando de reproducirlo en otra computadora (usando VS2010) usando el código de mi propia pregunta pero no lo veo .. Tengo que irme volver a mi código original y ver cuál es la diferencia (mi código de pregunta se simplifica, y es posible que simplifique demasiado y elimine el problema en el proceso) –

Cuestiones relacionadas