考虑引用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; }
}
RelayCommand
的客户端,要求RelayCommand
的用户了解CanExecuteChanged
的实现并自行维护实时引用?如果是这样,例如,将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;
}
}
答案 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;