我正在寻找RelayCommand
的实现方式。我考虑过的原始实现是经典的方法(我们称之为实现A)。
public class RelayCommand : ICommand
{
private readonly Predicate<object> canExecute;
private readonly Action<object> execute;
private EventHandler canExecuteEventhandler;
public RelayCommand(Action<object> execute)
: this(execute, null)
{
}
public RelayCommand(Action<object> execute, Predicate<object> canExecute)
{
if (execute == null)
{
throw new ArgumentNullException("execute");
}
this.execute = execute;
this.canExecute = canExecute;
}
public event EventHandler CanExecuteChanged
{
add
{
this.canExecuteEventhandler += value;
}
remove
{
this.canExecuteEventhandler -= value;
}
}
[DebuggerStepThrough]
public bool CanExecute(object parameter)
{
return this.canExecute == null ? true : this.canExecute(parameter);
}
[DebuggerStepThrough]
public void Execute(object parameter)
{
this.execute(parameter);
}
public void InvokeCanExecuteChanged()
{
if (this.canExecute != null)
{
if (this.canExecuteEventhandler != null)
{
this.canExecuteEventhandler(this, EventArgs.Empty);
}
}
}
}
自从2009年开始开发Silverlight以来,我一直使用这个实现方式。我也在WPF应用程序中使用过它。 最近我了解到,在绑定到命令的视图的生命周期短于命令本身的情况下,它存在内存泄漏问题。显然,当一个按钮绑定到命令时,它会注册到
CanExecuteChanged
事件处理程序,但从未取消注册。默认的事件处理程序保持对委托的强引用,委托又对按钮本身保持强引用,因此RelayCommand
会使按钮保持活动状态,这是一个内存泄漏。
我找到的另一种实现方式使用了CommandManager
。CommandManager
公开了一个RequerySuggested
事件,并且在内部仅保留对委托的弱引用。因此,可以将事件的定义实现如下(实现B)。public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public void RaiseCanExecuteChanged()
{
CommandManager.InvalidateRequerySuggested();
}
为了使每个委托都传递到静态事件处理程序中,而不是由中继命令本身持有。我对这种实现的问题在于它依赖于
CommandManager
知道何时引发事件。此外,当调用 RaiseCanExecuteChanged
时,命令管理器会为所有 RelayCommands
引发此事件,而不是特定引发事件的那一个。我找到的最后一个实现来自 MvvmLight,其中事件被定义如下(实现 C):
public event EventHandler CanExecuteChanged
{
add
{
if (_canExecute != null)
{
// add event handler to local handler backing field in a thread safe manner
EventHandler handler2;
EventHandler canExecuteChanged = _requerySuggestedLocal;
do
{
handler2 = canExecuteChanged;
EventHandler handler3 = (EventHandler)Delegate.Combine(handler2, value);
canExecuteChanged = System.Threading.Interlocked.CompareExchange<EventHandler>(
ref _requerySuggestedLocal,
handler3,
handler2);
}
while (canExecuteChanged != handler2);
CommandManager.RequerySuggested += value;
}
}
remove
{
if (_canExecute != null)
{
// removes an event handler from local backing field in a thread safe manner
EventHandler handler2;
EventHandler canExecuteChanged = this._requerySuggestedLocal;
do
{
handler2 = canExecuteChanged;
EventHandler handler3 = (EventHandler)Delegate.Remove(handler2, value);
canExecuteChanged = System.Threading.Interlocked.CompareExchange<EventHandler>(
ref this._requerySuggestedLocal,
handler3,
handler2);
}
while (canExecuteChanged != handler2);
CommandManager.RequerySuggested -= value;
}
}
}
除了命令管理器外,它还在本地保存委托并进行一些魔术操作以支持线程安全。
我的问题是:
- 这些实现中哪一个实际上解决了内存泄漏问题。
- 是否有一种实现方式可以在不依赖于
CommandManager
的情况下解决该问题? - 实现C中所做的技巧是否真的有必要避免与线程安全相关的错误,它又是如何解决的?