【问题标题】:Simplifying RelayCommand/DelegateCommand in WPF MVVM ViewModels简化 WPF MVVM 视图模型中的 RelayCommand/DelegateCommand
【发布时间】:2010-06-24 17:03:53
【问题描述】:

如果您正在执行 MVVM 并使用命令,您会经常在 ViewModel 上看到由私有 RelayCommand 或 DelegateCommand 字段支持的 ICommand 属性,例如 MSDN 上的原始 MVVM 文章中的这个示例:

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

但是,这很混乱,并且使设置新命令相当乏味(我与一些对所有这些输入都犹豫不决的资深 WinForms 开发人员一起工作)。所以我想简化它并挖掘一点。我在 get{} 块的第一行设置了一个断点,发现它只在我的应用程序第一次加载时才被命中——我以后可以根据需要触发任意数量的命令,而这个断点永远不会被命中——所以我想要简化它以消除我的 ViewModel 中的一些混乱,并注意到以下代码的工作方式相同:

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

但是,我对 C# 或垃圾收集器的了解还不够,不知道这是否会导致问题,例如在某些情况下会产生过多的垃圾。这会带来什么问题吗?

【问题讨论】:

    标签: c# wpf mvvm relaycommand delegatecommand


    【解决方案1】:

    这与您提供一个计算某个常数值的属性(比如说整数)完全相同。 您可以为 get 方法的每次调用计算它,也可以在第一次调用时创建它然后缓存它,以便为以后的调用返回缓存的值。 因此,如果 getter 最多被调用一次,它根本没有区别,如果它被频繁调用,你会损失一些(不多)性能,但你不会遇到真正的麻烦。

    我个人喜欢这样缩写 MSDN 方式:

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

    【讨论】:

    • 好电话。我喜欢你对空合并运算符的使用。我总是忘记那个。
    【解决方案2】:

    我发现,如果您有多个调用相同命令的控件,则需要 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 类延迟实例化,因此没有任何命令的 ViewModel 将避免额外的分配。

    【讨论】:

    • 如果你真的很担心你的内存使用,那么只实例化你将要使用的命令不是很有意义吗?例如,如果您的 VM 公开了 3 个命令,并且只使用了一个,那么只需删除其余的。如果您正在使用所有 VM 命令(您应该使用),那么您的延迟加载系统将使用更多的内存和更多的处理器时间,这违背了优化的目的,尤其是对于嵌入式系统。
    【解决方案3】:

    我要做的一件事是让 Visual Studio 为我进行打字。我刚刚创建了一个代码 sn-p,它允许我通过键入来创建一个 RelayCommand

    rc Tab保存回车

    rc 是代码 sn-p 快捷方式 选项卡加载您键入的文本并创建所有其他措辞。

    一旦您查看了一个代码 sn-p 并创建了您自己的代码,您就再也回不去了 :)

    有关创建代码 sn-ps 的更多信息:http://msdn.microsoft.com/en-us/library/ms165394.aspx

    【讨论】:

    • 我不知道为什么没有人提到这个。这就是我所做的,它是迄今为止处理命令样板代码的最佳方式。从长远来看,所有这些懒惰->懒惰的东西实际上并没有节省任何时间或代码。
    【解决方案4】:

    你为什么不直接写:

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

    【讨论】:

    • 我猜是为了防止创建一个不需要的对象,但我真的不知道它使用的内存或创建命令的其他副作用......
    • 我们想要惰性实例化来节省内存。我最终向 ViewModelBase 类添加了一些代码,该类存储 RelayCommands 的 List 并有一个名为 GetCommand() 的方法。因此,当我们实现 ViewModel 时,创建命令所需要做的就是创建一个 ICommand 属性,该属性使用 execute 和 canExecute 委托调用 GetCommand(),而 ViewModel 实现者不需要了解任何有关 RelayCommand/Delegate 命令的信息。
    • 我不会开始优化命令,因为命令的内存占用太低。
    • 明白了。我正在开发一个嵌入式系统,所以我必须尽量减少内存使用,尤其是在像我们的 ViewModelBase 这样频繁使用的基类中。
    【解决方案5】:

    当您在视图模型上公开 ICommand 属性并且它没有支持字段时,这没关系,只要您只绑定到该字段一次。

    如果命令已经创建,CommandWrapper 的 GetCommand 方法将返回该命令。

    【讨论】:

      【解决方案6】:

      当您在视图模型上公开 ICommand 属性并且它没有支持字段时,这没关系,只要您只绑定到该字段一次。基本上,当您的表单加载并执行初始绑定时,这是唯一一次访问您的命令的 get 属性。

      很多时候你只会绑定一个命令。

      如果您将同一命令绑定到多个控件,那么您将需要支持字段。

      【讨论】:

        猜你喜欢
        • 2012-04-13
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-10-07
        • 1970-01-01
        相关资源
        最近更新 更多