Упрощение RelayCommand / DelegateCommand в WPF MVVM ViewModels

Если вы делаете MVVM и используете команды, вы часто увидите свойства ICommand в ViewModel, которые поддерживаются полями private RelayCommand или DelegateCommand, как в этом примере из исходной статьи MVVM на MSDN:

RelayCommand _saveCommand;
public ICommand SaveCommand
{
    get
    {
        if (_saveCommand == null)
        {
            _saveCommand = new RelayCommand(param => this.Save(),
                param => this.CanSave );
        }
        return _saveCommand;
    }
}

однако, это много беспорядка, и делает настройку новых команд довольно утомительной (я работаю с некоторыми ветеранами разработчиков WinForms, которые отказываются от всего этого ввода). Поэтому я хотел упростить его и немного углубился. Я установил точка останова в первой строке блока get{} и увидела, что он попал только при первой загрузке моего приложения-позже я могу запускать столько команд, сколько хочу, и эта точка останова никогда не попадает-поэтому я хотел упростить это, чтобы удалить некоторый беспорядок из моих ViewModels и заметил, что следующий код работает одинаково:

public ICommand SaveCommand
{
    get
    {
        return new RelayCommand(param => this.Save(), param => this.CanSave );
    }
}

однако я недостаточно знаю о C# или сборщике мусора, чтобы знать, может ли это вызвать проблемы, такие как создание чрезмерного мусора в некоторых случаях. Это создаст какие-либо проблемы?

6 ответов


Это точно так же, как если бы вы предложили - скажем integer - свойство, которое вычисляет некоторое постоянное значение. Вы можете либо вычислить его для каждого вызова get-метода, либо создать его при первом вызове, а затем кэшировать его, чтобы вернуть кэшированное значение для последующих вызовов. Поэтому, если геттер вызывается не более одного раза, это не имеет никакого значения, если он вызывается часто, вы потеряете некоторую (не большую) производительность, но вы не получите реальных проблем.

Я лично как сокращенно MSDN-путь, как это:

RelayCommand _saveCommand;
public ICommand SaveCommand
{
  get
  {
    return _saveCommand ?? (_saveCommand = new RelayCommand(param => this.Save(),
                                                            param => this.CanSave ));
  }
}

Я обнаружил, что вам нужен исходный способ из MSDN, если у вас есть несколько элементов управления, которые вызывают одни и те же команды, иначе каждый элемент управления будет создавать свою собственную RelayCommand. Я не осознавал этого, потому что мое приложение имеет только один элемент управления для каждой команды.

поэтому, чтобы упростить код в ViewModels, я создам класс-оболочку команды, который хранит (и лениво создает) все RelayCommands и бросает его в мой класс ViewModelBase. Таким образом, пользователям не нужно напрямую создавать экземпляры RelayCommand или DelegateCommand объекты и не нужно ничего знать о них:

    /// <summary>
    /// Wrapper for command objects, created for convenience to simplify ViewModel code
    /// </summary>
    /// <author>Ben Schoepke</author>
    public class CommandWrapper
    {
    private readonly List<DelegateCommand<object>> _commands; // cache all commands as needed

    /// <summary>
    /// </summary>
    public CommandWrapper()
    {
        _commands = new List<DelegateCommand<object>>();
    }

    /// <summary>
    /// Returns the ICommand object that contains the given delegates
    /// </summary>
    /// <param name="executeMethod">Defines the method to be called when the command is invoked</param>
    /// <param name="canExecuteMethod">Defines the method that determines whether the command can execute in its current state.
    /// Pass null if the command should always be executed.</param>
    /// <returns>The ICommand object that contains the given delegates</returns>
    /// <author>Ben Schoepke</author>
    public ICommand GetCommand(Action<object> executeMethod, Predicate<object> canExecuteMethod)
    {
        // Search for command in list of commands
        var command = (_commands.Where(
                            cachedCommand => cachedCommand.ExecuteMethod.Equals(executeMethod) &&
                                             cachedCommand.CanExecuteMethod.Equals(canExecuteMethod)))
                                             .FirstOrDefault();

        // If command is found, return it
        if (command != null)
        {
            return command;
        }

        // If command is not found, add it to the list
        command = new DelegateCommand<object>(executeMethod, canExecuteMethod);
        _commands.Add(command);
        return command;
    }
}

этот класс также лениво создается классом ViewModelBase, поэтому ViewModels, у которых нет никаких команд, избегают дополнительных распределений.


одна вещь, которую я делаю, - это позволить Visual Studio печатать для меня. Я только что создал фрагмент кода, который позволяет мне создать RelayCommand, введя

rc Tab сохранить Enter

rc-это ярлык фрагмента кода вкладка загружает текст, который вы вводите, что вы хотите, и он создает все другие формулировки.

Как только вы посмотрите на один фрагмент кода и создадите свой собственный, Вы никогда не вернетесь :)

дополнительные информация о создании фрагментов кода:http://msdn.microsoft.com/en-us/library/ms165394.aspx


почему бы вам не написать просто:

private readonly RelayCommand _saveCommand = new RelayCommand(param => this.Save(),
                param => this.CanSave );;

public ICommand SaveCommand { get { return _saveCommand; } }

когда вы предоставляете свойство ICommand в viewmodel и у него нет резервного поля, это нормально, если вы привязываетесь к этому полю только один раз.

метод GetCommand CommandWrapper вернет команду, если она уже создана.


когда вы выставляете свойство ICommand на viewmodel и у него нет резервного поля, это нормально, если вы привязываетесь к этому полю только один раз. В принципе, когда ваша форма загружается и выполняет начальные привязки, это единственный раз, когда она собирается получить доступ к свойству get вашей команды.

есть много раз, когда вы свяжете команду только один раз.

Если вы привязываете одну и ту же команду к нескольким элементам управления, вам понадобится заднее поле.