.NET委托平等?

时间:2009-10-26 18:56:51

标签: wpf mvvm delegates equality relaycommand

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

---背景动机 -

动机与单元测试ViewModel进行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();
    }

2 个答案:

答案 0 :(得分:6)

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

  

委托相等运算符

     

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

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


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

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

答案 1 :(得分:1)

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

CollectionAssert.AreEqual(_vm.ProjectActivities, models);

失败只是因为使用了ReferenceEquality?

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

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

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