参照 JoshSmithの記事WPFApps With the Model-View-ViewModel Design Pattern 、具体的にはRelayCommand
の実装例(図3)を検討してください。 (この質問については、記事全体を読む必要はありません。)
一般的に、実装は優れていると思いますが、CanExecuteChanged
のCommandManager
イベントへのRequerySuggested
サブスクリプションの委任について質問があります。 RequerySuggested
のドキュメント は次のように述べています。
このイベントは静的であるため、弱参照としてのみハンドラーを保持します。このイベントをリッスンするオブジェクトは、ガベージコレクションを回避するために、イベントハンドラーへの強力な参照を保持する必要があります。これは、プライベートフィールドを用意し、このイベントにアタッチする前または後にハンドラーを値として割り当てることで実現できます。
しかし、RelayCommand
のサンプル実装は、サブスクライブされたハンドラーに対してそのようなものを維持していません。
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
RelayCommand
のクライアントにリークし、RelayCommand
のユーザーがCanExecuteChanged
の実装を理解し、ライブ参照を維持する必要がありますか?もしそうなら、例えば、RelayCommand
サブスクライバーの潜在的な時期尚早なGCを軽減するために、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;
}
}
私もこの実装に欠陥があると思いますイベントハンドラへの弱参照を確実にリークするためです。これは実際には非常に悪いことです。
MVVMLightツールキットとそこに実装されているRelayCommand
を使用しており、記事と同じように実装されています。
次のコードは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;
}
}
ただし、このように変更すると、機能します。
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;
}
}
唯一の違いは? CommandManager.RequerySuggested
のドキュメントに示されているように、イベントハンドラーをフィールドに保存しています。
Joshの コメント 彼の " ルーティングされたコマンドの理解 "の記事で答えを見つけました:
[...] CanExecuteChangedイベントでWeakEventパターンを使用する必要があります。これは、視覚要素がそのイベントをフックし、アプリがシャットダウンするまでコマンドオブジェクトがガベージコレクションされない可能性があるため、メモリリークの可能性が非常に高いためです。 [...]
WPF CanExecuteChanged
は愚かで自分自身をフック解除するため、Visuals
実装者は登録されたハンドラーを弱く保持する必要があるという議論のようです。これは、すでにこれを行っているCommandManager
に委任することで最も簡単に実装できます。おそらく同じ理由で。
リフレクターによると、それはRoutedCommand
クラスでも同じように実装されているので、大丈夫だと思います... WPFチームの誰かがミスをしない限り;)
欠陥があると思います。
イベントをCommandManagerに再ルーティングすることにより、次の動作が得られます。
これにより、WPFコマンドインフラストラクチャは、組み込みコマンドを要求するたびに実行できるかどうかをすべてのRelayCommandオブジェクトに要求します。
ただし、CanExecuteステータスを再評価するために、単一のコマンドにバインドされているすべてのコントロールに通知する場合はどうなりますか?彼の実装では、CommandManagerに移動する必要があります。つまり、
アプリケーション内のすべてのコマンドバインディングが再評価されます
これには、豆の山に関係のないもの、CanExecuteの評価に副作用(データベースアクセスや長時間実行タスクなど)があるもの、収集されるのを待っているものなどが含まれます...それはスレッジハンマーを使用するようなものですフリッゲンネイルを打ち込む。
これを行うことの影響を真剣に検討する必要があります。
私はここで要点を見逃しているかもしれませんが、以下はコンストラクターのイベントハンドラーへの強い参照を構成していませんか?
_canExecute = canExecute;