我正在寻找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();
}
这样每个委托都被传递给静态事件处理程序,而不是由relay命令本身保存。我对此实现的问题在于它依赖于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
?答案 0 :(得分:3)
您可以使用WeakEventManager。
public event EventHandler CanExecuteChanged
{
add
{
RelayCommandWeakEventManager.AddHandler(this, value);
}
remove
{
RelayCommandWeakEventManager.RemoveHandler(this, value);
}
}
private class RelayCommandWeakEventManager : WeakEventManager
{
private RelayCommandWeakEventManager()
{
}
public static void AddHandler(RelayCommand source, EventHandler handler)
{
if (source == null)
throw new ArgumentNullException("source");
if (handler == null)
throw new ArgumentNullException("handler");
CurrentManager.ProtectedAddHandler(source, handler);
}
public static void RemoveHandler(RelayCommand source,
EventHandler handler)
{
if (source == null)
throw new ArgumentNullException("source");
if (handler == null)
throw new ArgumentNullException("handler");
CurrentManager.ProtectedRemoveHandler(source, handler);
}
private static RelayCommandWeakEventManager CurrentManager
{
get
{
Type managerType = typeof(RelayCommandWeakEventManager);
RelayCommandWeakEventManager manager =
(RelayCommandWeakEventManager)GetCurrentManager(managerType);
// at first use, create and register a new manager
if (manager == null)
{
manager = new RelayCommandWeakEventManager();
SetCurrentManager(managerType, manager);
}
return manager;
}
}
/// <summary>
/// Return a new list to hold listeners to the event.
/// </summary>
protected override ListenerList NewListenerList()
{
return new ListenerList<EventArgs>();
}
/// <summary>
/// Listen to the given source for the event.
/// </summary>
protected override void StartListening(object source)
{
EventSource typedSource = (RelayCommand) source;
typedSource.canExecuteEventhandler += new EventHandler(OnSomeEvent);
}
/// <summary>
/// Stop listening to the given source for the event.
/// </summary>
protected override void StopListening(object source)
{
EventSource typedSource = (RelayCommand) source;
typedSource.canExecuteEventhandler -= new EventHandler(OnSomeEvent);
}
/// <summary>
/// Event handler for the SomeEvent event.
/// </summary>
void OnSomeEvent(object sender, EventArgs e)
{
DeliverEvent(sender, e);
}
}
此代码从https://msdn.microsoft.com/en-us/library/aa970850%28v=vs.110%29.aspx
无耻地解除(并改编)答案 1 :(得分:0)
根据Aron的回答,我选择了一个涉及弱事件的解决方案,但开发方式不同,以减少代码量并使构建块更可重用。
以下实现混合了#34; classic&#34;一,从MvvmLight获取一些想法,我正在使用WeakEvent类,它是根据Daniel Grunwald在以下(优秀!!!)文章中介绍的模式开发的。 http://www.codeproject.com/Articles/29922/Weak-Events-in-C
RelayCommand本身实现如下:
public class RelayCommand : ICommand
{
private readonly Action _execute;
private readonly Func<bool> _canExecute;
private WeakEvent<EventHandler> _canExecuteChanged;
/// <summary>
/// Initializes a new instance of the RelayCommand class that
/// can always execute.
/// </summary>
/// <param name="execute">The execution logic.</param>
/// <exception cref="ArgumentNullException">If the execute argument is null.</exception>
public RelayCommand(Action execute)
: this(execute, null)
{
}
/// <summary>
/// Initializes a new instance of the RelayCommand class.
/// </summary>
/// <param name="execute">The execution logic.</param>
/// <param name="canExecute">The execution status logic.</param>
/// <exception cref="ArgumentNullException">If the execute argument is null.</exception>
public RelayCommand(Action execute, Func<bool> canExecute)
{
if (execute == null)
{
throw new ArgumentNullException("execute");
}
_execute = execute;
_canExecute = canExecute;
_canExecuteChanged = new WeakEvent<EventHandler>();
}
/// <summary>
/// Occurs when changes occur that affect whether the command should execute.
/// </summary>
public event EventHandler CanExecuteChanged
{
add
{
_canExecuteChanged.Add(value);
}
remove
{
_canExecuteChanged.Remove(value);
}
}
/// <summary>
/// Raises the <see cref="CanExecuteChanged" /> event.
/// </summary>
[SuppressMessage(
"Microsoft.Performance",
"CA1822:MarkMembersAsStatic",
Justification = "The this keyword is used in the Silverlight version")]
[SuppressMessage(
"Microsoft.Design",
"CA1030:UseEventsWhereAppropriate",
Justification = "This cannot be an event")]
public void RaiseCanExecuteChanged()
{
_canExecuteChanged.Raise(this, EventArgs.Empty);
}
/// <summary>
/// Defines the method that determines whether the command can execute in its current state.
/// </summary>
/// <param name="parameter">This parameter will always be ignored.</param>
/// <returns>true if this command can be executed; otherwise, false.</returns>
public bool CanExecute(object parameter)
{
return (_canExecute == null) || (_canExecute());
}
/// <summary>
/// Defines the method to be called when the command is invoked.
/// </summary>
/// <param name="parameter">This parameter will always be ignored.</param>
public virtual void Execute(object parameter)
{
if (CanExecute(parameter))
{
_execute();
}
}
}
请注意,我没有对_execute和_canExecute委托进行弱引用。当代理是闭包时,使用对委托的弱引用会导致各种各样的问题,因为它们的目标对象没有被任何对象引用,并且它们会死掉#34; die&#34;即刻。我希望这些代表无论如何都拥有RelayCommand的所有者,因此他们的生命期望与RelayCommand相同。
CanExecuteChanged事件是使用WeakEvent实现的,因此即使侦听器没有取消注册,relay命令也不会影响它们的生命周期。