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
    解决问题的实现
  • 在实现C中执行的技巧对于避免与线程安全相关的错误是否真的很有必要?它是如何解决的

  • 您可以使用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();
            }
        }
    }