43

Josh Smithの記事「Model-View-ViewModelデザインパターンを使用したWPFアプリ」、具体的にはRelayCommand(図3)の実装例を検討してください。(この質問については、記事全体を読む必要はありません。)

一般的に、実装は優れていると思いますが、のイベントCanExecuteChangedへのサブスクリプションの委任について質問があります。州のドキュメント:CommandManagerRequerySuggestedRequerySuggested

このイベントは静的であるため、弱参照としてのみハンドラーを保持します。このイベントをリッスンするオブジェクトは、ガベージコレクションを回避するために、イベントハンドラーへの強力な参照を保持する必要があります。これは、プライベートフィールドを用意し、このイベントにアタッチする前または後にハンドラーを値として割り当てることで実現できます。

しかし、のサンプル実装はRelayCommand、サブスクライブされたハンドラーに対してそのようなものを維持していません。

public event EventHandler CanExecuteChanged
{
    add { CommandManager.RequerySuggested += value; }
    remove { CommandManager.RequerySuggested -= value; }
}
  1. これにより、弱参照がRelayCommandクライアントにリークされ、ユーザーがライブ参照RelayCommandの実装を理解してCanExecuteChanged維持する必要がありますか?
  2. もしそうなら、例えば、加入者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; 
        }
    }
    
4

5 に答える 5

46

Joshの「ルーティングされたコマンドについて」の記事に対するコメントで答えを見つけました。

[...]CanExecuteChangedイベントでWeakEventパターンを使用する必要があります。これは、ビジュアル要素がそのイベントをフックし、アプリがシャットダウンするまでコマンドオブジェクトがガベージコレクションされない可能性があるため、メモリリークの可能性が非常に高いためです。[...]

WPFは自分自身のフックを解除するのが愚かであるCanExecuteChangedため、実装者は登録されたハンドラーを弱く保持する必要があるという議論のようです。これは、すでにこれを行ってVisualsいるに委任することで最も簡単に実装できます。CommandManagerおそらく同じ理由で。

于 2011-11-29T15:36:26.643 に答える
9

イベントハンドラへの弱参照が確実にリークされるため、この実装には欠陥があると私も信じています。これは実際には非常に悪いことです。
私は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、イベントハンドラーをフィールドに保存しています。

于 2011-09-06T17:06:14.100 に答える
7

リフレクターによると、RoutedCommandクラスでも同じように実装されているので、大丈夫だと思います... WPFチームの誰かがミスをしない限り;)

于 2010-02-17T15:04:38.243 に答える
6

欠陥があると思います。

イベントをCommandManagerに再ルーティングすることにより、次の動作が得られます

これにより、WPFコマンドインフラストラクチャは、組み込みコマンドを要求するたびに実行できるかどうかをすべてのRelayCommandオブジェクトに要求します。

ただし、CanExecuteステータスを再評価するために、単一のコマンドにバインドされているすべてのコントロールに通知する場合はどうなりますか?彼の実装では、CommandManagerに移動する必要があります。つまり、

アプリケーション内のすべてのコマンドバインディングが再評価されます

これには、豆の山に関係のないもの、CanExecuteの評価に副作用(データベースアクセスや長時間実行タスクなど)があるもの、収集されるのを待っているものなどが含まれます...スレッジハンマーを使用するようなものですフリッゲンネイルを打ち込む。

これを行うことの影響を真剣に検討する必要があります。

于 2010-08-04T18:29:30.857 に答える
0

私はここで要点を見逃しているかもしれませんが、以下はコンストラクターのイベントハンドラーへの強力な参照を構成していませんか?

    _canExecute = canExecute;           
于 2010-02-17T15:03:46.583 に答える