无论如何,我认为这是个问题。我正在使用一个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();
}
答案 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进行比较。