Ist Josh Smith Umsetzung des RelayCommand fehlerhaft?
-
21-09-2019 - |
Frage
die Referenz Josh Smith‘Artikel WPF Apps Mit der Model-View-Viewmodel Design-Muster , insbesondere die beispielhafte Implementierung eines RelayCommand
(in Figur 3). (Keine Notwendigkeit, durch den gesamten Artikel für diese Frage zu lesen.)
Generell denke ich, die Umsetzung ist ausgezeichnet, aber ich habe eine Frage über die Delegation von CanExecuteChanged
Abonnements für die CommandManager
Veranstaltung RequerySuggested
. Die Dokumentation für RequerySuggested
lautet:
Da dieses Ereignis statisch ist, wird es nur halten, auf die Behandlungsroutine als schwaches Referenz. Objekte, die hören für Dieses Ereignis sollte eine starke halten Referenz auf ihre Event-Handler vermeidet es Müll gesammelt werden. Diese kann, indem ein durchgeführt werden Privat Feld und Zuordnen der Handler als Wert vor oder nach Anbringen auf dieses Ereignis.
Doch die Beispielimplementierung von RelayCommand
hält keine solche an den abonnierten Handler:
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
- Ist dies das schwache Referenzleck bis zu dem
RelayCommand
des Kunden zu verlangen, dass der Benutzer desRelayCommand
der Umsetzung derCanExecuteChanged
verstehen und pflegen einer Live-Referenz selbst? -
Wenn ja, ist es sinnvoll zu sein, zum Beispiel, ändern Sie die Implementierung von
RelayCommand
so etwas wie die folgenden zu mildern das Potenzial vorzeitige GC desCanExecuteChanged
Teilnehmers zu sein:// 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; } }
Lösung
ich glauben, dass diese Implementierung fehlerhaft ist , weil es auf jeden Fall den schwachen Verweis auf den Event-Handler-Lecks. Das ist etwas wirklich sehr schlecht.
Ich bin mit dem MVVM Light Toolkit und den RelayCommand
implementiert darin, und es wird ebenso wie in dem Artikel implementiert.
Der folgende Code wird nie invoke 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;
}
}
Allerdings, wenn ich es so ändern, wird es funktionieren:
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;
}
}
Der einzige Unterschied? Genau wie in der Dokumentation von CommandManager.RequerySuggested
angegeben ich die Event-Handler in einem Feld speicherte.
Andere Tipps
Ich habe die Antwort in Joshs Kommentar auf seine " Legendes Routed Commands " Artikel:
[...] müssen Sie das WeakEvent Muster in Ihrem CanExecuteChanged verwenden Veranstaltung. Dies liegt daran, visuelle Elemente wird dieses Ereignis Haken und seit das Befehlsobjekt vielleicht nie Müll, bis der App gesammelt werden schalte mich ab, dort für ein Speicherleck ein sehr reales Potential ist. [...]
Das Argument scheint, dass CanExecuteChanged
Implementierer zu sein nur schwach an den registrierten Handler halten müssen, da WPF Visuals
zu dumm sind, sich zu aushängen. Dies geschieht am einfachsten durch die Delegation an die CommandManager
umgesetzt, die dies bereits der Fall ist. Vermutlich aus dem gleichen Grund.
Nun, nach Reflector ist es die gleiche Art und Weise in der RoutedCommand
Klasse implementiert, so dass ich denke, es in Ordnung sein muss ... es sei denn, jemand in dem WPF-Team einen Fehler gemacht;)
Ich glaube, es fehlerhaft ist.
Durch die Ereignisse an den Befehlsmanager Umleiten, Sie erhalten das folgende Verhalten
Damit wird sichergestellt, dass der WPF befehl Infrastruktur fragt allen RelayCommand Objekte, wenn sie ausgeführt werden kann, wann immer es fragt die integrierten Befehle.
Doch was passiert, wenn Sie alle Regler auf einen einzigen Befehl neu zu bewerten Status CanExecute gebunden informieren wollen? In seiner Umsetzung, müssen Sie auf die Befehlsmanager gehen, was bedeutet,
Jeder einzelne Befehl in der Anwendung verbindlich wird neu bewertet
, dass alle diejenigen umfasst, die einen Hügel von Bohnen spielen keine Rolle, wo die, die CanExecute Auswertung hat Nebenwirkungen (wie Datenbankzugriff oder lang laufende Tasks), diejenigen, die werden darauf warten, gesammelt ... Es ist wie mit einem Vorschlaghammer einen friggen Nagel einzuschlagen.
Sie müssen ernsthaft die Auswirkungen, dies zu tun betrachten.
Ich kann den Punkt hier fehlt aber nicht die nach dem starken Bezug auf die Event-Handler in der contructor dar?
_canExecute = canExecute;