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

时间:2010-02-17 14:46:36

标签: wpf mvvm weak-references icommand relaycommand

考虑引用Josh Smith' article WPF Apps With The Model-View-ViewModel Design Pattern,特别是RelayCommand的示例实现(图3)。 (无需阅读整篇文章以了解此问题。)

总的来说,我认为实施非常好,但我对CanExecuteChanged CommandManager事件的RequerySuggested订阅的授权有疑问。 documentation for RequerySuggested州:

  

由于此事件是静态的,它会   只作为弱者持有处理程序   参考。听的对象   这个事件应该保持强势   引用它们的事件处理程序   避免被垃圾收集。这个   可以通过拥有一个   私人领域和分配   handler作为之前或之后的值   附加到这个事件。

然而,RelayCommand的示例实现并没有为订阅的处理程序维护任何内容:

public event EventHandler CanExecuteChanged
{
    add { CommandManager.RequerySuggested += value; }
    remove { CommandManager.RequerySuggested -= value; }
}
  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; 
        }
    }
    

5 个答案:

答案 0 :(得分:44)

我在Josh comment的“Understanding Routed Commands”文章中找到了答案:

  

[...]你必须在CanExecuteChanged中使用WeakEvent模式   事件。这是因为视觉元素将挂钩该事件,从那以后   在应用程序之前,命令对象可能永远不会被垃圾回收   关闭,存在内存泄漏的真正潜力。 [...]

这个论点似乎是CanExecuteChanged实现者​​只能对注册的处理程序保持弱,因为WPF Visuals是愚蠢的解开自己。通过委派给已经执行此操作的CommandManager,可以轻松实现这一点。大概是出于同样的原因。

答案 1 :(得分: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;
    }
}

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

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的文档所示,我将事件处理程序保存在字段中。

答案 2 :(得分:7)

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

答案 3 :(得分:5)

我认为它存在缺陷。

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

  

这确保了WPF的命令   基础设施要求所有RelayCommand   对象,如果他们可以随时执行   它询问内置命令。

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

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

包括那些与bean无关的所有那些,评估CanExecute的副作用(例如数据库访问或长时间运行的任务),那些等待收集的... ...用一把大锤开一块钉子。

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

答案 4 :(得分:0)

我可能在这里忽略了这一点,但是下面没有构成对构造函数中事件处理程序的强引用?

    _canExecute = canExecute;