Подтвердить что ты не робот

Является ли Джош Смит реализацией RelayCommand ошибочным?

Рассмотрим ссылку статьи WPF Джоша Смита с шаблоном проектирования Model-View-ViewModel, в частности пример реализации RelayCommand (На рисунке 3). (Нет необходимости читать всю статью по этому вопросу.)

В целом, я считаю, что реализация отличная, но у меня есть вопрос о делеции CanExecuteChanged подписки на событие CommandManager RequerySuggested. Документация для RequerySuggested гласит:

Поскольку это событие статично, оно будет удерживать только обработчик как слабый Справка. Объекты, которые слушают это событие должно ссылки на их обработчик событий на избегайте его сбора мусора. Эта может быть достигнуто путем частного поля и присвоения обработчик как значение до или после присоединение к этому событию.

Однако примерная реализация RelayCommand не поддерживает такой подписанный обработчик:

public event EventHandler CanExecuteChanged
{
    add { CommandManager.RequerySuggested += value; }
    remove { CommandManager.RequerySuggested -= value; }
}
  • Происходит ли эта утечка слабая ссылка с клиентом RelayCommand, требуя, чтобы пользователь RelayCommand понимал реализацию CanExecuteChanged и поддерживал собственную ссылку?
  • Если это так, имеет смысл, например, изменить реализацию RelayCommand, чтобы быть чем-то вроде следующего, чтобы смягчить потенциальную преждевременную GC подписчика CanExecuteChanged:

    // This event never actually fires.  It purely lifetime mgm't.
    private event EventHandler canExecChangedRef;
    public event EventHandler CanExecuteChanged
    {
        add 
        { 
            CommandManager.RequerySuggested += value;
            this.canExecChangedRef += value;
        }
        remove 
        {
            this.canExecChangedRef -= value;
            CommandManager.RequerySuggested -= value; 
        }
    }
    
4b9b3361

Ответ 1

Я тоже считаю, что эта реализация ошибочна, потому что она определенно утешает слабую ссылку на обработчик событий. Это действительно очень плохо.
Я использую инструментарий MVVM Light и RelayCommand, реализованный в нем, и он реализован так же, как и в статье.
Следующий код никогда не будет вызывать OnCanExecuteEditChanged:

private static void OnCommandEditChanged(DependencyObject d, 
                                         DependencyPropertyChangedEventArgs e)
{
    var @this = d as MyViewBase;
    if (@this == null)
    {
        return;
    }

    var oldCommand = e.OldValue as ICommand;
    if (oldCommand != null)
    {
        oldCommand.CanExecuteChanged -= @this.OnCanExecuteEditChanged;
    }
    var newCommand = e.NewValue as ICommand;
    if (newCommand != null)
    {
        newCommand.CanExecuteChanged += @this.OnCanExecuteEditChanged;
    }
}

Однако, если я изменю его так, он будет работать:

private static EventHandler _eventHandler;

private static void OnCommandEditChanged(DependencyObject d,
                                         DependencyPropertyChangedEventArgs e)
{
    var @this = d as MyViewBase;
    if (@this == null)
    {
        return;
    }
    if (_eventHandler == null)
        _eventHandler = new EventHandler(@this.OnCanExecuteEditChanged);

    var oldCommand = e.OldValue as ICommand;
    if (oldCommand != null)
    {
        oldCommand.CanExecuteChanged -= _eventHandler;
    }
    var newCommand = e.NewValue as ICommand;
    if (newCommand != null)
    {
        newCommand.CanExecuteChanged += _eventHandler;
    }
}

Единственное различие? Как указано в документации CommandManager.RequerySuggested, я сохраняю обработчик событий в поле.

Ответ 2

Я нашел ответ в Josh comment в разделе "Общие сведения о маршрутизированных командах"

[...] вам нужно использовать шаблон WeakEvent в CanExecuteChanged мероприятие. Это связано с тем, что визуальные элементы будут захватывать это событие, и поскольку объект команды никогда не будет собирать мусор, пока приложение не будет отключается, существует реальный потенциал утечки памяти. [...]

Аргумент, по-видимому, заключается в том, что CanExecuteChanged разработчики должны слабо удерживать только зарегистрированные обработчики, так как WPF Visuals глупо отцепляться. Это проще всего реализовать, делегируя CommandManager, который уже делает это. Предположительно по той же причине.

Ответ 3

Ну, в соответствии с Reflector он реализован таким же образом в классе RoutedCommand, поэтому я думаю, что это должно быть ОК... если кто-то из команды WPF не допустил ошибку;)

Ответ 4

Я считаю, что он ошибочен.

Переделав события в CommandManager, вы получите следующее поведение

Это гарантирует, что команда WPF инфраструктура запрашивает все RelayCommand объекты, если они могут выполняться всякий раз он запрашивает встроенные команды.

Однако, что происходит, когда вы хотите сообщить всем элементам управления, связанным с одной командой, для переоценки статуса CanExecute? В его реализации вы должны перейти в CommandManager, что означает

Каждая привязка команды в вашем приложении переоценивается

Это включает все те, которые не имеют значения для холма beans, те, где оценка CanExecute имеет побочные эффекты (такие как доступ к базе данных или длительные задачи), те, которые ждут сбора... Это похоже на использование кувалды для вождения гвоздя фрегата.

Вы должны серьезно рассмотреть последствия этого.

Ответ 5

Мне может не хватать точку здесь, но не следующие ли они являются сильной ссылкой на обработчик событий в конструкторе?

    _canExecute = canExecute;