Title: Make a complex number class with equality in C#
The example Make a complex number class with overloaded operators in C# builds a simple Complex class that includes overloaded +, -, *, and / operators that let you combine Complex objects. For example, a program can execute the following code.
Complex A = new Complex(1, 2);
Complex B = new Complex(3, 4);
Complex C = A + B;
Two operators that it didn't define are == and !=. Those are a little different. Note that if you define one of these operators you must define them both. For a simple class such as this one, that's easy because A == B is true when A != B is false. That means you can define one and then use its negation to define the other.
There are two notions of equality for objects: reference equality and value equality. Reference equality means two references point to the same object. Value equality means two objects contain the same values. For example, if two Employee objects person1 and person2 refer to the same object, they have reference equality. If they refer to different objects that happen to have the same FirstName, LastName, and other properties, then they have value equality.
Note that two objects with reference equality always have value equality because they refer to the same object. The reverse is not true.
With all that in mind, Microsoft has several recommendations about equality.
- Reference equality operators == and != are already defined by default so if that's what you want, you don't need to do anything else.
- Microsoft recommends against implementing value equality operators for classes that are not invariant. In other words, if it is possible to change the values of the object's properties after the object is set, then you should stick with reference equality. (I'm not completely sure why they make this recommendation. Perhaps it's because two objects that are equal at one time may become not equal later.)
The Complex class we've been building is not invariant because you can set its Re and Im fields in your code, so Microsoft would recommend against using value equality. I'm going to do it anyway because it makes sense for complex numbers. (In fact, Microsoft would probably recommend that the whole thing be converted into a structure instead of a class with Re and Im implemented as read-only properties.)
If you override the == and != operators, you should also override the inherited Equals and GetHashCode methods. If you don't override those methods, Visual Studio issues a warning. The Equals method should basically do whatever == does. The GetHashCode method should return an int value that a program can use for hashing.
Furthermore, Microsoft makes these recommendations about the Equals method:
- Identity: x.Equals(x) should always be true.
- Commutative: x.Equals(y) should be the same as y.Equals(x).
- Transitive: If x.Equals(y) is true and y.Equals(z) is true, then x.Equals(z) should also be true.
- Repeated calls to x.Equals(y) should all return the same value if x and y have not changed.
- The value x.Equals(null) should be false for any (non-null) object x.
- Equals should not throw exceptions.
- In addition to overriding Equals(object), you should provide Equals(type) for the class's type.
The following code shows how the Complex class overrides the == operator.
// Return A == B.
public static bool operator ==(Complex A, Complex B)
{
if (object.ReferenceEquals(A, null))
return object.ReferenceEquals(B, null);
if (object.ReferenceEquals(B, null)) return false;
// Compare the field values.
return ((A.Re == B.Re) && (A.Im == B.Im));
}
This operator uses the object.ReferenceEquals method to test reference equality between A and null. If A is null, then B must be also to be equal.
If A and B are not null, then the code compares their Re and Im values.
The other equality methods follow easily from ==.
// Return A != B.
public static bool operator !=(Complex A, Complex B)
{
return !(A == B);
}
// Return true if the objects contain the same values.
public bool Equals(Complex B)
{
return this == B;
}
public override bool Equals(object obj)
{
return Equals(obj as Complex);
}
The != operator calls == and negates the result.
The Equals method simply calls ==.
The second Equals method uses the as keyword to convert an object into a Complex or null if the object cannot be converted into a Complex. It then passes the resulting value into the first Equals method.
The final piece in this example is the implementation of GetHashCode.
// Return a hash code for this object.
public override int GetHashCode()
{
return Re.GetHashCode() ^ Im.GetHashCode();
}
This code returns the exclusive OR of the hash codes of the value's real and imaginary parts. Exactly what value this has isn't too important as long it's fairly "random" so different "typical" values are unlikely to have the same hash code.
Hashing classes such as Dictionary use the hash code as a quick test to see if two objects might be equal. If you add a new key to a Dictionary, it compares the hash code for the new item to those that are already in the hash table. If there is a match, the Dictionary uses the Equals method to see if the values are actually equal.
Yes, equality testing is confusing! Stay tuned for more on complex numbers.
Download the example to experiment with it and to see additional details.
|