.NET委托平等?

Ber*_*ryl 1 wpf delegates equality mvvm 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);
    }
Run Code Online (Sandbox Code Playgroud)

---回到代表平等----

这是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);
        }
    }

}
Run Code Online (Sandbox Code Playgroud)

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

return Equals(other._execute, _execute) && Equals(other._canExecute, _canExecute)
Run Code Online (Sandbox Code Playgroud)

调试器输出:

?_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)}}
Run Code Online (Sandbox Code Playgroud)

任何人都可以解释我错过了什么,修复是什么?

----编辑评论----

正如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);
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

您可以看到close命令是一个RelayCommand,并且我使用equals来进行单元测试.

@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));
    }


}
Run Code Online (Sandbox Code Playgroud)

-----使用MERHDAD的最新编辑"S IDEA

debugger out put?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
Run Code Online (Sandbox Code Playgroud)

这是将代码更改为以下内容后的结果:

    public ICommand CloseCommand
    {
        get
        {
            if (_closeCommand == null)
                _closeCommand = new RelayCommand(_executeClose);

            return _closeCommand;
        }
    }
    RelayCommand _closeCommand;

    void _executeClose(object param) {
        OnRequestClose();
    }
Run Code Online (Sandbox Code Playgroud)

Meh*_*ari 6

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

委托平等运营商

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

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


更新:确实,问题是你在调用时没有传递相同的方法引用new RelayCommand(param => OnCloseCommand()).毕竟,这里指定的lambda表达式实际上是一个匿名方法(您没有传递方法引用OnCloseCommand;您正在传递对带有单个参数和调用的匿名方法的引用OnCloseCommand).如上面引用规范的最后一行所述,没有必要比较这两个代表的回报true.

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