43

考虑参考Josh Smith 的文章 WPF Apps With The Model-View-ViewModel Design Pattern,特别是 a 的示例实现RelayCommand(在图 3 中)。(这个问题不需要通读整篇文章。)

总的来说,我认为实现非常好,但我对'事件的CanExecuteChanged订阅委托有疑问。状态的文档CommandManagerRequerySuggestedRequerySuggested

由于此事件是静态的,它只会将处理程序作为弱引用。侦听此事件的对象应保持对其事件处理程序的强引用,以避免它被垃圾收集。这可以通过拥有一个私有字段并将处理程序分配为附加到此事件之前或之后的值来完成。

然而,示例实现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; 
        }
    }
    
4

5 回答 5

46

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

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

这个论点似乎是CanExecuteChanged实现者必须只对已注册的处理程序有微弱的控制,因为 WPFVisuals愚蠢地解开自己。这很容易通过委托给CommandManager已经这样做的 来实现。大概是出于同样的原因。

于 2011-11-29T15:36:26.643 回答
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我将事件处理程序保存在字段中的文档中所示。

于 2011-09-06T17:06:14.100 回答
7

好吧,根据 Reflector 的说法,它在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 回答