wpf mvvm weak-references icommand relaycommand

wpf - ¿La implementación de Josh Smith del RelayCommand es defectuosa?



mvvm weak-references (5)

Considere la referencia de las aplicaciones de WPF del artículo de Josh Smith con el patrón de diseño Model-View-ViewModel , específicamente la implementación de ejemplo de un RelayCommand (en la Figura 3). (No es necesario leer todo el artículo para esta pregunta).

En general, creo que la implementación es excelente, pero tengo una pregunta sobre la delegación de CanExecuteChanged suscripciones RequerySuggested evento RequerySuggested del RequerySuggested . La documentación para RequerySuggested declara:

Como este evento es estático, solo se mantendrá en el controlador como una referencia débil. Los objetos que escuchan este evento deben mantener una fuerte referencia a su controlador de eventos para evitar que se recolecte basura. Esto se puede lograr teniendo un campo privado y asignando el controlador como el valor antes o después de adjuntar a este evento.

Sin embargo, la implementación de muestra de RelayCommand no mantiene ninguna de las características del manejador suscrito:

public event EventHandler CanExecuteChanged { add { CommandManager.RequerySuggested += value; } remove { CommandManager.RequerySuggested -= value; } }

  1. ¿Esto RelayCommand la referencia débil hasta el RelayCommand del RelayCommand , que requiere que el usuario del RelayCommand comprenda la implementación de CanExecuteChanged y mantenga una referencia en vivo por sí mismo?
  2. Si es así, ¿tiene sentido, por ejemplo, modificar la implementación de RelayCommand para que sea similar a lo siguiente para mitigar el potencial GC prematuro del suscriptor CanExecuteChanged :

    // This event never actually fires. It''s purely lifetime mgm''t. private event EventHandler canExecChangedRef; public event EventHandler CanExecuteChanged { add { CommandManager.RequerySuggested += value; this.canExecChangedRef += value; } remove { this.canExecChangedRef -= value; CommandManager.RequerySuggested -= value; } }


Bueno, según Reflector, se implementa de la misma manera en la clase RoutedCommand , así que supongo que debe estar bien ... a menos que alguien en el equipo de WPF haya cometido un error;)


Creo que es defectuoso.

Al volver a enrutar los eventos al CommandManager, obtiene el siguiente comportamiento

Esto garantiza que la infraestructura de comando de WPF pregunte a todos los objetos de RelayCommand si pueden ejecutarse siempre que solicite los comandos incorporados.

Sin embargo, ¿qué sucede cuando desea informar a todos los controles vinculados a un solo comando para volver a evaluar el estado de CanExecute? En su implementación, debe dirigirse al CommandManager, es decir

Cada enlace de comando en su aplicación es reevaluado

Eso incluye todos los que no importan una colina de frijoles, aquellos en los que evaluar CanExecute tiene efectos secundarios (como el acceso a bases de datos o tareas de larga ejecución), los que están esperando ser recolectados ... Es como usar un mazo para conducir un clavo friggen

Tienes que considerar seriamente las ramificaciones de hacer esto.


Encontré la respuesta en el comment de Josh sobre su artículo " Comprender los comandos enrutados ":

[...] debe usar el patrón WeakEvent en su evento CanExecuteChanged. Esto se debe a que los elementos visuales engancharán ese evento, y dado que el objeto de comando nunca será basura hasta que la aplicación se apague, existe un potencial muy real para una pérdida de memoria. [...]

El argumento parece ser que los implementadores de CanExecuteChanged solo deben mantener débilmente a los manejadores registrados, ya que las CanExecuteChanged WPF son estúpidas para desengancharse. Esto se implementa más fácilmente delegando al CommandManager , quien ya hace esto. Presumiblemente por la misma razón.


Me puede estar faltando el punto aquí, pero no lo siguiente constituye la fuerte referencia al controlador de eventos en el contructor?

_canExecute = canExecute;


También creo que esta implementación es defectuosa , porque definitivamente filtra la referencia débil al controlador de eventos. Esto es algo realmente muy malo.
Estoy utilizando el kit de herramientas MVVM Light y el RelayCommand implementado en el mismo y se implementa como en el artículo.
El siguiente código nunca invocará a OnCanExecuteEditChanged :

private static void OnCommandEditChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { var @this = d as MyViewBase; if (@this == null) { return; } var oldCommand = e.OldValue as ICommand; if (oldCommand != null) { oldCommand.CanExecuteChanged -= @this.OnCanExecuteEditChanged; } var newCommand = e.NewValue as ICommand; if (newCommand != null) { newCommand.CanExecuteChanged += @this.OnCanExecuteEditChanged; } }

Sin embargo, si lo cambio así, funcionará:

private static EventHandler _eventHandler; private static void OnCommandEditChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { var @this = d as MyViewBase; if (@this == null) { return; } if (_eventHandler == null) _eventHandler = new EventHandler(@this.OnCanExecuteEditChanged); var oldCommand = e.OldValue as ICommand; if (oldCommand != null) { oldCommand.CanExecuteChanged -= _eventHandler; } var newCommand = e.NewValue as ICommand; if (newCommand != null) { newCommand.CanExecuteChanged += _eventHandler; } }

¿La unica diferencia? Tal como se indica en la documentación de CommandManager.RequerySuggested , CommandManager.RequerySuggested el controlador de eventos en un campo.