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)
你是用匿名函数创建代理吗?根据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)生成的方法名称.这进一步证明了我上面提到的观点.
| 归档时间: |
|
| 查看次数: |
1973 次 |
| 最近记录: |