Josh Smith对RelayCommand的实施有缺陷吗?

Gre*_*g D 41 wpf weak-references mvvm relaycommand icommand

考虑参考Josh Smith的文章WPF Apps with Model-View-ViewModel Design Pattern,特别是a的示例实现RelayCommand(图3).(无需阅读整篇文章以了解此问题.)

在一般情况下,我想实现是优秀的,但我有一个代表团的问题CanExecuteChanged订阅到CommandManagerRequerySuggested事件.各州的文件RequerySuggested:

由于此事件是静态的,因此它只会作为弱引用保留在处理程序中.侦听此事件的对象应该对其事件处理程序保持强引用,以避免它被垃圾回收.这可以通过拥有私有字段并在附加到此事件之前或之后将处理程序指定为值来实现.

然而,示例实现RelayCommand不会对订阅的处理程序维护任何此类:

public event EventHandler CanExecuteChanged
{
    add { CommandManager.RequerySuggested += value; }
    remove { CommandManager.RequerySuggested -= value; }
}
Run Code Online (Sandbox Code Playgroud)
  1. 这是否泄漏了弱RelayCommand客户端的漏洞,要求用户RelayCommand了解自己的实施CanExecuteChanged并维护一个实时参考?
  2. 如果是这样,例如,修改实现RelayCommand类似于以下内容以减轻CanExecuteChanged订户的潜在过早GC 是否有意义:

    // 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; 
        }
    }
    
    Run Code Online (Sandbox Code Playgroud)

Dav*_*itt 44

我在Josh 对他的" 理解路由命令 "文章的评论中找到了答案:

[...]您必须在CanExecuteChanged事件中使用WeakEvent模式.这是因为可视元素将挂钩该事件,并且由于在应用程序关闭之前命令对象可能永远不会被垃圾收集,因此存在内存泄漏的非常可能的情况.[...]

这个论点似乎是CanExecuteChanged实现者必须只对注册的处理程序进行弱处理,因为WPF Visuals愚蠢地解除了自己.通过委托CommandManager已经执行此操作的人员,可以轻松实现这一点.大概是出于同样的原因.

  • 遗憾的是,我在过去 6 年里没有接触过 C#,所以我不知道。 (2认同)

Dan*_*rth 9

我也相信这个实现是有缺陷的,因为它肯定泄漏了对事件处理程序的弱引用.这实际上非常糟糕.
我正在使用MVVM Light工具包并在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;
    }
}
Run Code Online (Sandbox Code Playgroud)

但是,如果我像这样更改它,它将工作:

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;
    }
}
Run Code Online (Sandbox Code Playgroud)

唯一的区别?正如CommandManager.RequerySuggested我在一个字段中保存事件处理程序的文档中所示.


Tho*_*que 7

好吧,根据Reflector,它在RoutedCommand课堂上以相同的方式实现,所以我想它一定没问题......除非WPF团队中有人犯了错误;)


小智 5

我认为这是有缺陷的.

通过将事件重新路由到CommandManager,您将获得以下行为

这可确保WPF命令基础结构询问所有RelayCommand对象是否可以在它询问内置命令时执行.

但是,当您希望通知绑定到单个命令的所有控件重新评估CanExecute状态时会发生什么?在他的实现中,您必须转到CommandManager,意思是

重新评估应用程序中的每个命令绑定

这包括所有与bean无关的问题,评估CanExecute有副作用的那些(例如数据库访问或长时间运行的任务),等待收集的那些......就像使用大锤一样驾驶一个friggen钉子.

你必须认真考虑这样做的后果.