Josh Smith对RelayCommand的执行有缺陷吗?

请参考Josh Smith的文章WPF Apps与Model-View-ViewModeldevise模式 ,特别是RelayCommand的示例实现(在图3中)。 (无需阅读整个文章的这个问题。)

一般来说,我认为这个实现是非常好的,但是我有一个关于将CanExecuteChanged订阅委托给CommandManagerRequerySuggested事件的问题。 RequerySuggested的文档状态如下:

由于这个事件是静态的,所以它只能作为一个弱引用持有处理程序。 监听这个事件的对象应该对它们的事件处理器保持强有力的引用,以避免垃圾收集。 这可以通过在附加到该事件之前或之后拥有私有字段并将该处理程序指定为值来实现。

然而, RelayCommand的示例实现并不维护订阅的处理程序:

 public event EventHandler CanExecuteChanged { add { CommandManager.RequerySuggested += value; } remove { CommandManager.RequerySuggested -= value; } } 
  1. 这是否RelayCommand弱引用泄漏到RelayCommand的客户端,要求RelayCommand的用户了解RelayCommand的实现并自行维护一个实时引用?
  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; } } 

我也相信这个实现是有缺陷的 ,因为它肯定会泄漏对事件处理程序的弱引用。 这实际上是非常糟糕的。
我正在使用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的文档中指出的,我将事件处理程序保存在一个字段中。

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

[…]您必须在CanExecuteChanged事件中使用WeakEvent模式。 这是因为可视元素会挂钩该事件,并且由于命令对象可能永远不会被垃圾收集,直到应用程序closures,所以存在内存泄漏的真实可能性。 […]

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

那么,根据Reflector,它在RoutedCommand类中以相同的方式实现,所以我想这一定是OK的…除非WPF团队中有RoutedCommand了一个错误;)

我相信这是有缺陷的。

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

这可以确保WPF指挥基础设施询问所有RelayCommand对象是否可以在询问内置命令时执行。

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

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

这包括所有那些无关紧要的问题,评估CanExecute有副作用(如数据库访问或长时间运行的任务)的那些,等待被收集的那些…就像使用大锤开一只冷冻的指甲。

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

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

  _canExecute = canExecute;