我想将对象与其他对象进行比较,以了解它们是否相等。因此,实现这一点的方法似乎是在我的类中实现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%可验证,而不需要特定的代码部分来增强或帮助您的测试方法。(除非所述代码仅限于测试项目本身,并且不包含在任何正在测试的实际代码中。