Wpf 中继命令内存泄漏
我正在寻找Wpf 中继命令内存泄漏,wpf,mvvm,memory-leaks,mvvm-light,relaycommand,Wpf,Mvvm,Memory Leaks,Mvvm Light,Relaycommand,我正在寻找RelayCommand的实现。我考虑的原始实现是经典的(我们称之为实现A) 公共类RelayCommand:ICommand { 私有只读谓词canExecute; 私有只读操作执行; 私有事件处理程序canExecuteEventhandler; 公共中继命令(操作执行) :此(执行,空) { } 公共RelayCommand(操作执行,谓词canExecute) { if(execute==null) { 抛出新的ArgumentNullException(“执行
RelayCommand
的实现。我考虑的原始实现是经典的(我们称之为实现A)
公共类RelayCommand:ICommand
{
私有只读谓词canExecute;
私有只读操作执行;
私有事件处理程序canExecuteEventhandler;
公共中继命令(操作执行)
:此(执行,空)
{
}
公共RelayCommand(操作执行,谓词canExecute)
{
if(execute==null)
{
抛出新的ArgumentNullException(“执行”);
}
this.execute=execute;
this.canExecute=canExecute;
}
公共事件事件处理程序CanExecuteChanged
{
添加
{
this.canExecuteEventhandler+=值;
}
去除
{
this.canExecuteEventhandler-=值;
}
}
[调试步骤至]
公共布尔CanExecute(对象参数)
{
返回this.canExecute==null?true:this.canExecute(参数);
}
[调试步骤至]
public void Execute(对象参数)
{
执行(参数);
}
public void InvokeCanExecuteChanged()
{
如果(this.canExecute!=null)
{
如果(this.canExecuteEventhandler!=null)
{
this.canExecuteEventhandler(this,EventArgs.Empty);
}
}
}
}
这是我自2009年左右开始在Silverlight中开发以来使用的实现。我也在WPF应用程序中使用过它。
最近我了解到,当绑定到命令的视图的生命周期比命令本身短时,它会出现内存泄漏问题。显然,当按钮绑定到命令时,它当然会注册到CanExecuteChanged
事件处理程序,但从未注销。默认事件处理程序保存对委托的强引用,委托保存对按钮本身的强引用,因此RelayCommand
使按钮保持活动状态,这是内存泄漏。
我发现的另一个实现使用了CommandManager
。CommandManager
会公开一个RequerySuggested
事件,并且在内部只保存对代理的弱引用。因此,事件的定义可以实现如下(实现B)
公共事件事件处理程序CanExecuteChanged
{
添加{CommandManager.RequerySuggested+=value;}
删除{CommandManager.RequerySuggested-=value;}
}
public void raisecancecutechanged()
{
CommandManager.InvalidateRequestSuggested();
}
因此,每个委托都被传递到静态事件处理程序,而不是由中继命令本身持有。这个实现的问题在于它依赖于CommandManager
来知道何时引发事件。此外,当调用raisecannexectechanged
时,命令管理器会为所有relayCommand
引发此事件,而不是专门针对启动该事件的命令
我发现的最后一个实现来自MvvmLight,事件定义如下(实现C):
公共事件事件处理程序CanExecuteChanged
{
添加
{
如果(_canExecute!=null)
{
//以线程安全的方式将事件处理程序添加到本地处理程序备份字段
事件处理程序句柄2;
EventHandler canExecuteChanged=\u requerySuggestedLocal;
做
{
手柄2=canExecuteChanged;
eventhandler3=(EventHandler)Delegate.Combine(handler2,value);
canExecuteChanged=System.Threading.Interlocated.CompareExchange(
参考requerySuggestedLocal,
handler3,
手柄2);
}
而(canExecuteChanged!=手柄2);
CommandManager.RequerySuggested+=值;
}
}
去除
{
如果(_canExecute!=null)
{
//以线程安全的方式从本地备份字段中删除事件处理程序
事件处理程序句柄2;
EventHandler canExecuteChanged=此值。\u requerySuggestedLocal;
做
{
手柄2=canExecuteChanged;
EventHandler handler3=(EventHandler)委托。删除(handler2,值);
canExecuteChanged=System.Threading.Interlocated.CompareExchange(
请参阅此项。_RequerySuggestizedLocal,
handler3,
手柄2);
}
而(canExecuteChanged!=手柄2);
CommandManager.RequerySuggested-=值;
}
}
}
因此,除了命令管理器之外,它还在本地保存委托,并执行一些魔术来支持线程安全
我的问题是:
CommandManager
解决问题的实现您可以使用VentManager
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);
}
}
公共事件事件处理程序CanExecuteChanged
{
添加
{
RelayCommandWeakEventManager.AddHandler(这个值);
}
去除
{
RelayCommandDesieventManager.RemoveHandler(此值);
}
}
私有类RelayCommandWealteVentManager:WealteVentManager
{
private RelayCommandVentManager()专用关系
{
}
公共静态void AddHandler(RelayCommand源、EventHandler)
{
if(source==null)
抛出新的ArgumentNullException(“源”);
if(handler==null)
抛出新的ArgumentNullException(“处理程序”);
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();
}
}
}