如果我实现 IEquatable<T>,我是否会失去按引用比较的选项?



我想将对象与其他对象进行比较,以了解它们是否相等。因此,实现这一点的方法似乎是在我的类中实现IEquatable接口。

但我不确定这会对我班的行为产生什么影响。现在,在我的代码中,我使用以下方式通过引用来比较两个对象:

if(myObject1 == myObject2)
{
// code when both objects are the same. 
// Set values in some properties and do some actions according that.
}
else
{
// code when both objects are no the same. 
// Set values in some properties and do some actions according that.
}

但在一些特殊情况下,主要是在测试中,我想比较两个对象,如果所有属性都相等,则考虑相等,但在这种情况下,我不知道这是否会影响我的主代码,在主代码中,我是通过引用进行比较的。

另一种选择是实现一个以这种方式进行比较的方法,但我不知道这是一个好主意,还是实现IEquatable接口更好。

谢谢。

这里发生了一些不同的事情。

第一个是IEquatable<T>,而不是直接与==算子相关的。如果实现了IEquatable<T>,但没有覆盖==运算符,那么==将继续执行当前的操作:通过引用比较对象。

IEquatable<T>为您提供了一个Equals(T)方法,仅此而已。就其本身而言,它不会影响Equals(object)(您也需要实现它(,也不会影响==!=

因此,让我们假设您确实重载了==运算符,以调用我们的Equals方法:

public static bool operator ==(Foo left, Foo right) => Equals(left, right);
public static bool operator !=(Foo left, Foo right) => !Equals(left, right);

这只更改了两个Foo实例之间的==运算符。你仍然可以写:

if ((object)myObject1 == (object)myObject2))

这将回到使用object==方法,该方法通过参考进行比较。

另一种方法是:

if (ReferenceEquals(myObject1, myObject2))

它只是做同样的事情。


还要注意,为类实现IEquatable<T>是罕见的:这真的没有意义。类已经有了一个Equals(object)方法和一个需要重写的GetHashCode()方法,添加Equals(T)方法不会给您带来太多好处。

然而,IEquatable<T>对结构体很有用:它们还有一个Equals(object)方法需要重写,但如果你真的调用它,那么你最终会装箱,因为它接受object。如果您在这里实现IEquatable<T>,那么获得一个Equals(T)方法,您可以在不装箱的情况下调用该方法。


所有这些都表明,我会按照在应用程序中工作的方式编写代码,并在测试项目中做任何特定的测试。这意味着,如果您的对象应该在代码中通过引用进行比较,我不会向对象本身添加任何新内容。

在测试项目中,您可以编写自己的方法来检查对象的两个实例是否具有相同的属性(作为自定义bool AreFoosEqual(Foo f1, Foo f2),或者作为完整的IEqualityComparer<Foo>实例(。然后,您可以让它完全满足您的测试需要,而不用担心破坏您的应用程序。

您还可以将测试方法编写为一系列断言,这告诉您哪个属性不正确,以及区别是什么。这可以为您提供更丰富的测试输出:

public static void AssertFoosEquals(Foo f1, Foo f2)
{
Assert.AreEqual(f1.Foo, f2.Foo, "Foo property mismatch");
Assert.AreEqual(f1.Bar, f2.Bar, "Bar property mismtach");
}

如果您想以不同的方式比较相同的对象,我建议使用实现IEqualityComparer<T>:的比较器

public class MyClassTestComparer : IEqualityComparer<MyClass> {
public bool Equals(MyClass x, MyClass y) {
if (ReferenceEquals(x, y))
return true;
else if (null == x || null == y)
return false;
return x.Propery1 == y.Property1 && 
x.Propery2 == y.Property2 &&
x.ProperyN == y.PropertyN; 
}
public int GetHashCode(MyClass obj) {
return obj == null 
? 0 
: obj.Propery1.GetHashCode() ^ obj.Propery2.GetHashCode();
}
}

然后你可以选择合适的比较器

public static IEqualityComparer<MyClass> MyClassComparer {
if (we_should_use_test_comparer)
return new MyClassTestComparer();
else
return EqualityComparer<MyClass>.Default;  
} 

最后if将成为

if (MyClassComparer.Equals(myObject1, myObject2)) {
// Equals: by reference or by properties (in test)
}

进行单元测试时->

类似:

public void TestSomething()
{
var expectedValue1 = "SomeExpectedValue";
var actualValue = instance.Method();
Assert.Equal(expectedValue1, actualValue);
}

然后,如果您返回的是一个对象而不是一个值,那么您"简单地"断言您想要查看的属性:

public void TestSomething()
{
var expectedValue1 = "SomeExpectedValue";
TestableObject subject = instance.Method();
Assert.Equal(expectedValue1, subject.Somevalue);
}

如果您想要更通用的设置,可以使用通用流编写反射,该反射查看对象上的所有属性,并尝试将它们与另一个提供的对象匹配。

或者你可以下载一个已经允许你这样做的nuget工具包。

我不会为了测试而覆盖任何功能。意大利面条代码就是这样。理想情况下,您的代码应该通过单元测试100%可验证,而不需要特定的代码部分来增强或帮助您的测试方法。(除非所述代码仅限于测试项目本身,并且不包含在任何正在测试的实际代码中。

最新更新