gpt4 book ai didi

wpf - RelayCommand 内存泄漏

转载 作者:行者123 更新时间:2023-12-03 04:21:24 32 4
gpt4 key购买 nike

我正在寻找 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 应用程序中使用过它。最近我了解到,在绑定(bind)到命令的 View 的生命周期比命令本身短的情况下,它存在内存泄漏问题。显然,当按钮绑定(bind)到命令时,它当然会注册到 CanExecuteChanged 事件处理程序,但永远不会取消注册。默认事件处理程序持有对委托(delegate)的强引用,而委托(delegate)持有对按钮本身的强引用,因此 RelayCommand 使按钮保持事件状态,这是内存泄漏。

我发现的另一个实现使用CommandManagerCommandManager 公开一个 RequerySuggested 事件,并且在内部仅保留对委托(delegate)的弱引用。所以事件的定义可以实现如下(实现B)

public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}

public void RaiseCanExecuteChanged()
{
CommandManager.InvalidateRequerySuggested();
}

这样每个委托(delegate)都会传递给静态事件处理程序,而不是由中继命令本身持有。我的这个实现的问题是它依赖 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;
}
}
}

因此,除了命令管理器之外,它还在本地保存委托(delegate)并执行一些魔术来支持线程安全。

我的问题是:

  1. 其中哪些实现真正解决了内存泄漏问题。
  2. 是否有一种实现可以在不依赖 CommandManager 的情况下解决该问题?
  3. C实现中使用的技巧是否真的有必要避免与线程安全相关的错误?它是如何解决的?

最佳答案

您可以使用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 中提取(并改编)的。

关于wpf - RelayCommand 内存泄漏,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/29146166/

32 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com