无论如何,我认为这是个问题。我正在使用一个RelayCommand,它用两个委托装饰一个ICommand。一个是_canExecute的Predicate,另一个是_execute方法的Action。

---背景动机 -

动机与单元测试ViewModels有关的 WPF 演示文稿。一个常见的模式是我有一个具有ObservableCollection的ViewModel,我想要一个单元测试来证明该集合中的数据是我期望的一些源数据(也需要转换为ViewModel的集合)。即使两个集合中的数据在调试器中看起来相同,但由于ViewModel的RelayCommand上的相等失败,看起来测试失败了。这是失败的单元测试的一个例子:

[Test]
    public void Creation_ProjectActivities_MatchFacade()
    {
        var all = (from activity in _facade.ProjectActivities
                   orderby activity.BusinessId
                   select new ActivityViewModel(activity, _facade.SubjectTimeSheet)).ToList();

        var models = new ObservableCollection<ActivityViewModel>(all);
        CollectionAssert.AreEqual(_vm.ProjectActivities, models);
    }

---回到委托平等----

以下是RelayCommand的代码 - 它基本上是对Josh Smith的想法的直接剽窃,为了解决这个问题,我添加了一个平等实现:

public class RelayCommand : ICommand, IRelayCommand
{
    readonly Action<object> _execute;
    readonly Predicate<object> _canExecute;

    /// <summary>Creates a new command that can always execute.</summary>
    public RelayCommand(Action<object> execute) : this(execute, null) { }

    /// <summary>Creates a new command which executes depending on the logic in the passed predicate.</summary>
    public RelayCommand(Action<object> execute, Predicate<object> canExecute) {
        Check.RequireNotNull<Predicate<object>>(execute, "execute");

        _execute = execute;
        _canExecute = canExecute;
    }

    [DebuggerStepThrough]
    public bool CanExecute(object parameter) { return _canExecute == null ? true : _canExecute(parameter); }

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

    public void Execute(object parameter) { _execute(parameter); }

    public override bool Equals(object obj)
    {
        if (ReferenceEquals(null, obj)) return false;
        if (ReferenceEquals(this, obj)) return true;
        if (obj.GetType() != typeof(RelayCommand)) return false;
        return Equals((RelayCommand)obj);
    }

    public bool Equals(RelayCommand other)
    {
        if (ReferenceEquals(null, other)) return false;
        if (ReferenceEquals(this, other)) return true;
        return Equals(other._execute, _execute) && Equals(other._canExecute, _canExecute);
    }

    public override int GetHashCode()
    {
        unchecked
        {
            return ((_execute != null ? _execute.GetHashCode() : 0) * 397) ^ (_canExecute != null ? _canExecute.GetHashCode() : 0);
        }
    }

}

在我有效地将_execute委托设置为相同方法的单元测试中(在两种情况下_canExecute都为null),单元测试在此行失败:

return Equals(other._execute, _execute) && Equals(other._canExecute, _canExecute)

调试器输出:

?_execute
{Method = {Void <get_CloseCommand>b__0(System.Object)}}
base {System.MulticastDelegate}: {Method = {Void CloseCommand>b__0(System.Object)}}

?other._execute
{Method = {Void <get_CloseCommand>b__0(System.Object)}} 
base {System.MulticastDelegate}: {Method = {Void CloseCommand>b__0(System.Object)}}

任何人都可以解释我错过的内容以及解决方法是什么吗?

---- EDITED REMARKS ----

正如Mehrdad所指出的,调试会话中的get_CloseCommand起初看起来有点奇怪。它实际上只是一个属性获取,但它确实提出了为什么如果我需要做一些技巧来使它工作,代表的相等性是有问题的。

MVVM的一些观点是将表示中可能有用的内容公开为属性,因此您可以使用WPF绑定。我正在测试的特定类有一个WorkspaceViewModel在它的heirarchy,它只是一个已经有一个close命令属性的ViewModel。这是代码:

公共抽象类WorkspaceViewModel:ViewModelBase     {

    /// <summary>Returns the command that, when invoked, attempts to remove this workspace from the user interface.</summary>
    public ICommand CloseCommand
    {
        get
        {
            if (_closeCommand == null)
                _closeCommand = new RelayCommand(param => OnRequestClose());

            return _closeCommand;
        }
    }
    RelayCommand _closeCommand;

    /// <summary>Raised when this workspace should be removed from the UI.</summary>
    public event EventHandler RequestClose;

    void OnRequestClose()
    {
        var handler = RequestClose;
        if (handler != null)
            handler(this, EventArgs.Empty);
    }

    public bool Equals(WorkspaceViewModel other) {
        if (ReferenceEquals(null, other)) return false;
        if (ReferenceEquals(this, other)) return true;
        return Equals(other._closeCommand, _closeCommand) && base.Equals(other);
    }

    public override int GetHashCode() {
        unchecked {
            {
                return (base.GetHashCode() * 397) ^ (_closeCommand != null ? _closeCommand.GetHashCode() : 0);
            }
        }
    }
}

你可以看到close命令是一个RelayCommand,并且我用等于的方法来进行单元测试。

@Merhdad 这是单元测试,只有在我使用Trickster的delegate.Method进行相等比较时才有效。

[的TestFixture]     公共类WorkspaceViewModelTests     {         private WorkspaceViewModel vm1;         private WorkspaceViewModel vm2;

    private class TestableModel : WorkspaceViewModel
    {

    }

    [SetUp]
    public void SetUp() {
        vm1 = new TestableModel();
        vm1.RequestClose += OnWhatever;
        vm2 = new TestableModel();
        vm2.RequestClose += OnWhatever;
    }

    private void OnWhatever(object sender, EventArgs e) { throw new NotImplementedException(); }


    [Test]
    public void Equality() {
        Assert.That(vm1.CloseCommand.Equals(vm2.CloseCommand));
        Assert.That(vm1.Equals(vm2));
    }


}

-----使用MERHDAD的最新编辑<!>“S IDEA

调试器输出     ?valueOfThisObject     {} Smack.Wpf.ViewModel.RelayCommand     base {SharpArch.Core.DomainModel.ValueObject}:{Smack.Wpf.ViewModel.RelayCommand}     _canExecute:null     _execute:{Method = {Void _executeClose(System.Object)}}

?valueToCompareTo
{Smack.Wpf.ViewModel.RelayCommand}
base {SharpArch.Core.DomainModel.ValueObject}: {Smack.Wpf.ViewModel.RelayCommand}
_canExecute: null
_execute: {Method = {Void _executeClose(System.Object)}}

?valueOfThisObject.Equals(valueToCompareTo)
false

这是将代码更改为:

后的结果
    public ICommand CloseCommand
    {
        get
        {
            if (_closeCommand == null)
                _closeCommand = new RelayCommand(_executeClose);

            return _closeCommand;
        }
    }
    RelayCommand _closeCommand;

    void _executeClose(object param) {
        OnRequestClose();
    }
有帮助吗?

解决方案

您是使用匿名函数创建委托吗?这些是根据C#规范(<!>#167; 7.9.8)确切的委托相等规则:

  

委托相等运算符

     

两个委托实例被认为是如下:   如果任一委托实例null,当且仅当两者都static 时,它们是相等的。
  如果代理具有不同的运行时类型,则它们永远不会相等。   如果两个委托实例都有一个调用列表(<!>#167; 15.1),那些实例是相等的,当且仅当它们的调用列表长度相同,并且每个条目都在一个<!>#8217; s调用列表中在另一个<!>#8217; s调用列表中按顺序与相应的条目相等(如下定义)。   以下规则控制调用列表条目的相等性:
  如果两个调用列表条目都引用相同的new RelayCommand(param => OnCloseCommand())方法,则条目相同。
  如果两个调用列表条目都引用相同目标对象上的相同非OnCloseCommand方法(由引用相等运算符定义),则条目相等。点击   通过对具有相同(可能为空)的捕获的外部变量实例的语义相同的匿名函数表达式 进行评估而生成的调用列表条目是允许的(但不是必需的) )是平等的。

因此,在您的情况下,委托实例可能在两个不同的对象中引用相同的方法,或者引用两个匿名方法。


UPDATE:的确,问题是您在调用true时没有传递相同的方法引用。毕竟,这里指定的lambda表达式实际上是一个匿名方法(您没有将方法引用传递给CloseCommand;您正在传递一个匿名方法的引用,该方法接受一个参数并调用get_CloseCommand)。正如上面引用规范引文的最后一行所提到的,没有必要比较这两个代表返回<get_CloseCommand>b__0

旁注: <=>属性的getter简称为<=>而不是<=>。这是编译器为<=>方法(<=> getter)中的匿名方法生成的方法名称。这进一步证明了我上面提到的观点。

其他提示

我现在对其他线路一无所知,但如果

CollectionAssert.AreEqual(_vm.ProjectActivities, models);

失败只是因为使用了ReferenceEquality?

您已重写了RelayCommand的比较,但未覆盖ObservableCollection。

看起来似乎代表参考也使用了等式。

尝试通过Delegate.Method进行比较。

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top