2010-06-24 11 views
18

Si está ejecutando MVVM y utilizando comandos, a menudo verá las propiedades de ICommand en ViewModel que están respaldadas por los campos privados RelayCommand o DelegateCommand, como este ejemplo del artículo original de MVVM en MSDN:Simplificar el comando RelayCommand/Delegate en WPF MVVM ViewModels

RelayCommand _saveCommand; 
public ICommand SaveCommand 
{ 
    get 
    { 
     if (_saveCommand == null) 
     { 
      _saveCommand = new RelayCommand(param => this.Save(), 
       param => this.CanSave); 
     } 
     return _saveCommand; 
    } 
} 

Sin embargo, esta es una gran cantidad de desorden, y hace que la creación de nuevos comandos bastante tedioso (yo trabajo con algunos desarrolladores de Windows Forms veteranos que se resisten a todo esto de escribir). Así que quería simplificarlo y profundizar un poco. Establecí un punto de interrupción en la primera línea del bloque get {} y vi que solo se golpeó cuando mi aplicación se cargó por primera vez. Luego puedo disparar tantos comandos como quiera y este punto de interrupción nunca se golpea, así que quería simplificar esto para eliminar algunos desorden de mis ViewModels y se dio cuenta de que el siguiente código funciona de la misma:

public ICommand SaveCommand 
{ 
    get 
    { 
     return new RelayCommand(param => this.Save(), param => this.CanSave); 
    } 
} 

sin embargo, no sé lo suficiente sobre C# o el recolector de basura para saber si esto podría causar problemas, como generar basura excesiva en algunos casos. ¿Esto planteará algún problema?

Respuesta

8

Descubrí que necesita la forma original de MSDN si tiene varios controles que invocan los mismos comandos, de lo contrario cada control tendrá su propio RelayCommand. No me di cuenta de esto porque mi aplicación solo tiene un control por comando.

Para simplificar el código en ViewModels, crearé una clase contenedora de comandos que almacena (y crea instancias perezosamente) todos los RelayCommands y los lanza en mi clase ViewModelBase. De esta manera los usuarios no tienen que crear instancias de objetos directamente RelayCommand o DelegateCommand y no necesitan saber nada acerca de ellos:

/// <summary> 
    /// Wrapper for command objects, created for convenience to simplify ViewModel code 
    /// </summary> 
    /// <author>Ben Schoepke</author> 
    public class CommandWrapper 
    { 
    private readonly List<DelegateCommand<object>> _commands; // cache all commands as needed 

    /// <summary> 
    /// </summary> 
    public CommandWrapper() 
    { 
     _commands = new List<DelegateCommand<object>>(); 
    } 

    /// <summary> 
    /// Returns the ICommand object that contains the given delegates 
    /// </summary> 
    /// <param name="executeMethod">Defines the method to be called when the command is invoked</param> 
    /// <param name="canExecuteMethod">Defines the method that determines whether the command can execute in its current state. 
    /// Pass null if the command should always be executed.</param> 
    /// <returns>The ICommand object that contains the given delegates</returns> 
    /// <author>Ben Schoepke</author> 
    public ICommand GetCommand(Action<object> executeMethod, Predicate<object> canExecuteMethod) 
    { 
     // Search for command in list of commands 
     var command = (_commands.Where(
          cachedCommand => cachedCommand.ExecuteMethod.Equals(executeMethod) && 
              cachedCommand.CanExecuteMethod.Equals(canExecuteMethod))) 
              .FirstOrDefault(); 

     // If command is found, return it 
     if (command != null) 
     { 
      return command; 
     } 

     // If command is not found, add it to the list 
     command = new DelegateCommand<object>(executeMethod, canExecuteMethod); 
     _commands.Add(command); 
     return command; 
    } 
} 

Esta clase también se perezosamente instancia de la clase ViewModelBase, por lo ViewModels que no tienen ningún comando evitará las asignaciones adicionales.

+4

Si realmente está preocupado por el uso de la memoria, ¿no tendría sentido solo crear instancias de los comandos que va a utilizar? Por ejemplo, si tiene una VM que expone 3 comandos, y solo se usa uno, simplemente descarte el resto. Si está utilizando todos sus comandos de máquina virtual (que debería ser), entonces su sistema de carga lenta usará MÁS memoria y MÁS tiempo de procesador, lo que en primer lugar frustra el objetivo de optimización, especialmente para los sistemas integrados. –

7

Una cosa que hago es dejar que Visual Studio me escriba. Acabo de crear un fragmento de código que me permite crear un RelayCommand escribiendo

rc Tab Guardar Introduzca

rc es el fragmento de código de acceso directo pestaña carga el texto que escriba lo que quiere y crea todas las demás palabras.

Una vez que se mira en un fragmento de código y crear su propia que nunca volverá :)

Para obtener más información sobre la creación de fragmentos de código: http://msdn.microsoft.com/en-us/library/ms165394.aspx

+1

No sé por qué nadie más ha mencionado esto. Esto es lo que hago y es, de lejos, la mejor manera de manejar los códigos repetitivos para los comandos. Todas esas cosas perezosas y perezosas en realidad no ahorran tiempo o código en el largo plazo. –

+0

¿Te importa compartir tu fragmento? –

+1

Aquí está el mío http://pastebin.com/YHD8AUjR –

17

Esto es exactamente lo mismo que si le ofrecen a - digamos entero - propiedad que calcula algún valor constante. Puede calcularlo para cada llamada en el método get o puede crearlo en la primera llamada y luego almacenarlo en caché, para devolver el valor en caché para llamadas posteriores. Así que si el getter se llama a lo sumo una vez, no hace ninguna diferencia, si se llama con frecuencia, perderá algo de rendimiento (no mucho), pero no tendrá problemas reales.

personalmente me gusta abreviar el MSDN vías como esto:

RelayCommand _saveCommand; 
public ICommand SaveCommand 
{ 
    get 
    { 
    return _saveCommand ?? (_saveCommand = new RelayCommand(param => this.Save(), 
                  param => this.CanSave)); 
    } 
} 
+1

Buena llamada. Me gusta su uso del operador nulo coalesce. Siempre me olvido de eso. –

1

¿Por qué no escribir simplemente:

private readonly RelayCommand _saveCommand = new RelayCommand(param => this.Save(), 
       param => this.CanSave);; 

public ICommand SaveCommand { get { return _saveCommand; } } 
+0

Supongo que es para evitar la creación de un objeto sin la necesidad de, pero realmente no sé la memoria que utiliza u otros efectos secundarios de la creación de un comando ... – jpsstavares

+0

Queríamos crear instancias perezosas para ahorrar memoria. Terminé agregando algún código a una clase ViewModelBase que almacena una Lista <> de RelayCommands y tiene un método llamado GetCommand(). Entonces cuando implementamos un ViewModel, todo lo que necesitamos hacer para crear un comando es crear una propiedad ICommand que llame a GetCommand() con los delegados execute y canExecute, y el implementador de ViewModel no necesita saber nada sobre el comando RelayCommand/Delegate. –

+5

No comenzaré a optimizar un comando porque la huella de memoria de un comando es demasiado baja. – jbe

0

Al exponer la propiedad ICommand en su modelo de vista y que doesn No tiene un campo de respaldo, esto está bien, siempre y cuando solo se una a este campo una vez. Básicamente, cuando su formulario se carga y realiza los enlaces iniciales, esta es la única vez que va a acceder a la propiedad get de su comando.

Hay muchas ocasiones en las que enlazará un comando solo una vez.

Si vincula el mismo comando a más de un control, necesitará el campo de respaldo.

1

Cuando expone la propiedad ICommand en su viewmodel y no tiene un campo de respaldo, está bien, siempre que solo se una a este campo una vez.

El método GetCommand de CommandWrapper devolverá el comando si ya se creó.