我开始意识到,根据上下文,我希望“等于”具有两种不同的含义。在权衡此处的输入以及here https://stackoverflow.com/questions/9707918/simplify-overriding-equals-gethashcode-in-c-sharp-for-better-maintainabilit,针对我的具体情况,我决定如下:
我没有凌驾于一切之上Equals()
and GetHashCode()
,而是保留共同但绝不是普遍存在的惯例Equals()
意味着类的身份平等,并且Equals()
意味着结构体的值相等。这一决定的最大驱动因素是散列集合中对象的行为(Dictionary<T,U>
, HashSet<T>
,...)如果我偏离了这个惯例。
这个决定让我仍然错过了价值平等的概念(如MSDN 上讨论过 http://msdn.microsoft.com/en-us/library/dd183755.aspx)
当你定义一个类或结构时,你决定它是否有意义
创建值相等(或等价)的自定义定义
方式。通常,当以下对象时,您可以实现值相等
类型预计会被添加到某种集合中,或者当
它们的主要目的是存储一组字段或属性。
渴望价值平等(或者我称之为“等价”)概念的一个典型案例是在单元测试中。
Given
public class A
{
int P1 { get; set; }
int P2 { get; set; }
}
[TestMethod()]
public void ATest()
{
A expected = new A() {42, 99};
A actual = SomeMethodThatReturnsAnA();
Assert.AreEqual(expected, actual);
}
测试将会失败,因为Equals()
正在测试引用相等性。
当然可以修改单元测试以单独测试每个属性,但这将等价概念从类移到了类的测试代码中。
为了将这些知识封装在类中,并为测试等效性提供一致的框架,我定义了一个我的对象实现的接口
public interface IEquivalence<T>
{
bool IsEquivalentTo(T other);
}
实现通常遵循以下模式:
public bool IsEquivalentTo(A other)
{
if (object.ReferenceEquals(this, other)) return true;
if (other == null) return false;
bool baseEquivalent = base.IsEquivalentTo((SBase)other);
return (baseEquivalent && this.P1 == other.P1 && this.P2 == other.P2);
}
当然,如果我有足够多的类和足够的属性,我可以编写一个帮助器,通过反射构建表达式树来实现IsEquivalentTo()
.
最后,我实现了一个扩展方法来测试两个的等价性IEnumerable<T>
:
static public bool IsEquivalentTo<T>
(this IEnumerable<T> first, IEnumerable<T> second)
If T
实施IEquivalence<T>
使用该接口,否则Equals()
用于比较序列的元素。允许回退到Equals()
让它工作,例如和ObservableCollection<string>
除了我的业务对象。
现在,我的单元测试中的断言是
Assert.IsTrue(expected.IsEquivalentTo(actual));