Reputation: 14118
Consider this struct:
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);
}
}
When I do this in a unit test:
Assert.AreEqual(new MyNumber(123), 123);
It's green.
But this test fails:
Assert.AreEqual(123, new MyNumber(123));
Why is this so? I'm guess it's because the int class determines the equality, whereas in the first case, my class determines it. But my class is implicitly convertible to int
, so shouldn't that help?
How can I make the Assert.AreEqual work in both ways? I'm using MSTest by the way.
Update
Implementing IEquatable<int>
or IComparable<int>
doesn't help.
Upvotes: 4
Views: 1360
Reputation: 106916
The first assertion will invoke MyNumber.Equals
and you have implemented in a way where it will succeed if the argument to compare to is an Int32
and it is equal to value of MyNumber
.
The second assertion will invoke Int32.Equals
and it will fail because the argument to compare to is an MyNumber
which Int32
does not know about or understand.
You cannot make your second unit test succeed because you assert that an Int32
should be equal to your value. It cannot be because it is different. It is the code in Int32.Equals
that decides if the second value is equal. You have implemented some casting operators that you can unit test so these assertions should work:
Assert.AreEqual(123, (int) new MyNumber(123));
Assert.AreEqual((MyNumber) 123, new MyNumber(123));
Even though the casts are implemented implicit
they will not automatically be invoked by Assert.AreEquals
because this method expect two parameters of type Object
and you will have to invoke them explicitly as I did above.
Because of the special handling in your MyNumber.Equals
you now have a type that is not commutative with regards to equals, e.g. MyNumber(123) equals Int32(123)
is true but Int32(123) equals MyNumber(123)
is false. You should avoid that so I recommend that you remove the special case handling of ints in MyNumber.Equals
and instead rely on the implicit casts which will work for you most of the time. And when they do not you will have to make an explicit cast.
Upvotes: 6
Reputation: 1500
Implement IComparable<int>
in your MyNumber
struct as follows:
public int CompareTo(int other)
{
return other.CompareTo(Value);
}
This will ensure all Asserts work as expected such as:
Assert.AreEqual(new MyNumber(123), 123);
Assert.AreEqual(123, new MyNumber(123));
Assert.Greater(124, new MyNumber(123));
Assert.Less(124, new MyNumber(125));
Upvotes: 0
Reputation:
To quote from Object.Equals
:
The following statements must be true for all implementations of the Equals(Object) method. In the list, x, y, and z represent object references that are not null.
...
x.Equals(y) returns the same value as y.Equals(x).
Your Equals
implementation breaks this hard requirement. ((object)x).Equals(123)
must return the same value as ((object)123).Equals(x)
, if x
is not null
, regardless of what type it has.
There's a whole lot of code out there that, correctly, assumes that it doesn't matter which of the two objects is asked to perform the comparison. Design your code so that that assumption is not invalidated.
Effectively, this means that you must design your class in such a way that it doesn't compare equal to any integer type, no matter how much you might prefer otherwise.
Upvotes: 2