wpf - .NET 委托(delegate)平等?

标签 wpf mvvm delegates equality relaycommand

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

---背景动机--

动机与对 WPF 的 ViewModel 进行单元测试有关。介绍。一种常见的模式是,我有一个 ViewModel 具有 ObservableCollection,并且我想要一个单元测试来证明该集合中的数据是我所期望的给定一些源数据(也需要转换为 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);
    }

--- 回到委托(delegate)平等 ----

这是 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 委托(delegate)设置为相同的方法(_canExecute 在两种情况下都为空),单元测试在此行失败:
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)}}

谁能解释我缺少什么以及解决方法是什么?

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

正如 Mehrdad 指出的,调试 session 中的 get_CloseCommand 起初看起来有点奇怪。它实际上只是一个属性获取,但它确实提出了一个问题,即如果我需要做一些技巧来使其工作,为什么委托(delegate)的平等是有问题的。

MVVM 的一些要点是将演示文稿中可能有用的任何内容公开为属性,因此您可以使用 WPF 绑定(bind)。我正在测试的特定类在其层次结构中有一个 WorkspaceViewModel,它只是一个已经具有关闭命令属性的 ViewModel。这是代码:

公共(public)抽象类 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,并且我使用 equals 进行了修改以使单元测试工作。

@Merhdad
这是仅当我在相等比较中使用 Trickster 的 delegate.Method 时才有效的单元测试。

[测试夹具]
公共(public)类 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 想法的最新编辑

调试器输出
?valueOfThisObject
{Smack.Wpf.ViewModel.RelayCommand}
基础 {SharpArch.Core.DomainModel.ValueObject}:{Smack.Wpf.ViewModel.RelayCommand}
_canExecute:空
_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();
    }

最佳答案

你是用匿名函数还是其他东西创建委托(delegate)?这些是根据 C# 规范(第 7.9.8 节)的确切委托(delegate)相等规则:

Delegate equality operators

Two delegate instances are considered equal as follows: If either of the delegate instances is null, they are equal if and only if both are null.
If the delegates have different runtime type they are never equal. If both of the delegate instances have an invocation list (§15.1), those instances are equal if and only if their invocation lists are the same length, and each entry in one’s invocation list is equal (as defined below) to the corresponding entry, in order, in the other’s invocation list. The following rules govern the equality of invocation list entries:
If two invocation list entries both refer to the same static method then the entries are equal.
If two invocation list entries both refer to the same non-static method on the same target object (as defined by the reference equality operators) then the entries are equal.
Invocation list entries produced from evaluation of semantically identical anonymous-function-expressions with the same (possibly empty) set of captured outer variable instances are permitted (but not required) to be equal.



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

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

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

关于wpf - .NET 委托(delegate)平等?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/1626680/

相关文章:

java - 是否可以使用 @NotifyChange 而不是 BindUtils.postNotifyChange?

silverlight - 无法从文本中创建 ‘System.Windows.Input.ICommand’

ios - 通过按钮以编程方式将 UITableViewController 替换为 UIViewController,并使用里面的 tableView

iphone - 访问委托(delegate)时出现“无法识别的选择器”错误

c# - WPF:隐藏时将文本绑定(bind)到标题 DataGridColumn 中

.net - 没有 ListBox.SelectionMode ="None",是否有另一种方法可以禁用列表框中的选择?

c# - 用于基于选项卡控件的应用程序的 MVVM

c# - WPF Canvas 在绘制很多时卡住

c# - WCF 数据服务 GUI 性能和使用 ICollectionView 进行过滤

ios4 - 代码块能完全取代委托(delegate)吗?