C# 为什么Assert.AreEqual(x,y)失败了,但是Assert.AreEqual(y,x)没有';T

C# 为什么Assert.AreEqual(x,y)失败了,但是Assert.AreEqual(y,x)没有';T,c#,struct,assert,equality,C#,Struct,Assert,Equality,考虑这个结构: public struct MyNumber { private readonly int _value; public MyNumber(int myNumber) { _value = myNumber; } public int Value { get { return _value; } } public override bool Equals(object obj)

考虑这个结构:

public struct MyNumber
{
    private readonly int _value;

    public MyNumber(int myNumber)
    {
        _value = myNumber;
    }

    public int Value
    {
        get { return _value; }
    }

    public override bool Equals(object obj)
    {
        if (obj is MyNumber)
            return this == (MyNumber) obj;

        if (obj is int)
            return Value == (int)obj;

        return false;
    }

    public override string ToString()
    {
        return Value.ToString();
    }

    public static implicit operator int(MyNumber myNumber)
    {
        return myNumber.Value;
    }

    public static implicit operator MyNumber(int myNumber)
    {
        return new MyNumber(myNumber);
    }
}
当我在单元测试中执行此操作时:

Assert.AreEqual(new MyNumber(123), 123);
它是绿色的

但这项测试失败了:

Assert.AreEqual(123, new MyNumber(123));
为什么会这样?我猜这是因为int类决定了等式,而在第一种情况下,我的类决定了等式。但是我的类可以隐式地转换为
int
,所以这不应该有帮助吗

如何使Assert.Are在两种方式中都起相同的作用?顺便说一下,我正在使用MSTest

更新


实现
IEquatable
IComparable
没有帮助。

第一个断言将调用
MyNumber.Equals
,如果要比较的参数是
Int32
,并且它等于
MyNumber
的值,则实现将成功

第二个断言将调用
Int32.Equals
,它将失败,因为要比较的参数是
MyNumber
,而
Int32
不知道或不理解该参数

您不能使第二个单元测试成功,因为您断言
Int32
应该等于您的值。这不可能,因为它是不同的。决定第二个值是否相等的是
Int32.Equals
中的代码。您已经实现了一些可以进行单元测试的强制转换运算符,因此这些断言应该可以工作:

Assert.AreEqual(123, (int) new MyNumber(123));
Assert.AreEqual((MyNumber) 123, new MyNumber(123));
即使强制转换是通过
implicit
实现的,它们也不会被
Assert.AreEquals
自动调用,因为此方法需要两个类型为
Object
的参数,您必须像我上面所做的那样显式调用它们


由于您的
MyNumber.Equals
中的特殊处理,您现在有了一个与Equals不可交换的类型,例如
MyNumber(123)等于Int32(123)
为true,但
Int32(123)等于MyNumber(123)
为false。您应该避免这种情况,因此我建议您在
MyNumber.Equals
中删除int的特殊情况处理,而改用隐式强制转换,这在大多数情况下都适用。如果没有,则必须进行显式转换。

MyNumber
结构中实现
IComparable
,如下所示:

    public int CompareTo(int other)
    {
        return other.CompareTo(Value);
    }
这将确保所有资产按预期工作,例如:

    Assert.AreEqual(new MyNumber(123), 123);
    Assert.AreEqual(123, new MyNumber(123));
    Assert.Greater(124, new MyNumber(123));
    Assert.Less(124, new MyNumber(125));
引自:

对于Equals(Object)方法的所有实现,以下语句必须为true。在列表中,x、y和z表示不为空的对象引用

  • x、 Equals(y)返回与y.Equals(x)相同的值

您的
Equals
实现打破了这一硬要求
((对象)x).Equals(123)
必须返回与
((对象)123)相同的值。Equals(x)
,如果
x
不是
null
,则无论其类型如何

有很多代码,正确地说,假设两个对象中的哪一个被要求执行比较并不重要。设计您的代码,使该假设不会失效


实际上,这意味着您必须以这样一种方式来设计类,即它不会与任何整数类型进行比较,无论您多么希望使用其他方法。

不知道是否值得实现
IEquatable
?@Charleh:如果您要将值存储在集合中,则值得实现
IEquatable
。它将加速操作,如
包含
索引
等。不
可计算
@Charleh,
可比较
。这将确保类也可以使用
更大的
更少的
断言。您使用的是哪个单元测试框架?您是使用Microsoft.VisualStudio.TestTools.UnitTesting.Assert进行断言还是使用不同的单元测试框架?好问题,我确实在使用MSTest。但是如果MyNumber是class,我认为实现接口IEquatable,那么这两种断言都可以。是吗?@Davecz:我相信
Assert.AreEqual
基本上映射到静态
对象.Equals
,在做一些简单的检查后,它将调用
objA.Equals(objB)
。我不相信通用接口
IEquatable
在这个过程中被使用。正如我在下面提到的,实现
IComparable
将解决OP的问题。这个答案不包括OP的问题“我如何使Assert.AreEqual在两种方式下都工作?”@RobEpstein:
IComparable
是关于小于,等于或大于,例如用于排序的。当比较与此问题有关的相等性时,不使用此接口。此结构按照比较两个
MyNumber
s的断言中的设计工作。此结构不工作。正如Martin在其回答的评论中提到的,iCopy用于排序和排序。它不是用Assert.AreEqual调用的。这可能在您的单元测试框架中起作用,这显然是因为出现了
Assert.Greater
Assert.Less
与问题中使用的MSTest单元测试框架不同,但如果您使用MSTest,它将失败。我回答时的原始问题没有提到使用测试框架,所以我提供了一个在NUnit中完美运行的答案。我将尝试找到一个在MSTest中工作的解决方案。