The coding exercise illustrates a number of properties of equality in C#:
Object.Equals()
==
and !=
. This is considered more idiomatic than using the Equals()
method which is also available with these types. Java programmers should be alert, when dealing with strings, to the fact that ==
compares by value in C# but by reference in Java when returning to their former language.Equals()
method inherited from object
. If your goal with the equality test is to ensure that two objects are the exact same instance then relying on object
's implementation will suffice. If not, you need to override object.Equals()
.Equals()
must be overridden.Equals()
will contain equality tests on members of simple types using ==
and reference types with recursive calls to Equals()
.class StatusBar
{
private readonly int width = 200, height = 20;
public override bool Equals(object other)
{
// ... null and type checks and performance optimisations
return width == (other as StatusBar).width && height == (other as StatusBar).height;
}
}
class Window
{
private readonly string title = "Main";
private readonly StatusBar statusBar = new StatusBar();
public override bool Equals(object other)
{
// ... null and type checks and performance optimisations
return title == (other as Window).title && statusBar.Equals((other as Window).statusBar);
}
}
object.ReferenceEquals()
is used to compare two objects to detect if they are one and the same instance. This provides clarity and is a necessity where Equals()
and ==
have been overridden/overloaded.var winA = new Window(); // above code shows that all windows are equal
var winB = new Window();
ReferenceEquals(winA, winB);
// => false
var winC = winA;
ReferenceEquals(winA, winC);
// => true
public override bool Equals(object obj)
IDEs typically generate the overload protected bool Equals(FacialFeatures other)
for use when inheritance is involved. A derived class can call the base classe's Equals()
and then add its own test.==
unless you have overloaded the ==
operator, as well as the Equals()
method in your class (see the operator-overloading
exercise) or you care only that the references are equal.struct
s are dealt with in the structs
exercise.tuple
s is dealt with in the tuples
exercise.protected bool Equals(T other)
{
return field1 == other.field1 && field2.Equals(other.field2);
}
public override bool Equals(object obj)
{
if (ReferenceEquals(null, obj)) return false;
if (ReferenceEquals(this, obj)) return true;
if (obj.GetType() != this.GetType()) return false;
return Equals((T) obj);
}
SequenceEquals()
but in the absence of LINQ it is a matter of iterating through both collections and comparing items individually.==
and !=
with your own classes see the operator-overloading
exercise.Object.GetHashCode()
object.GetHashCode()
returns a hash code in the form of a 32 bit integer. The hash code is used by dictionary and set classes such as Dictionary<T>
and HashSet<T>
to store and retrieve objects in a performant manner. In the case of dictionaries the hashing relates to the keys.Equals()
you will also override GetHashCode()
. There is a relationship between Equals()
and GetHashCode()
that must hold true for correct behavior of dictionary and hash set classes and any others that use a hash code. You are expected to implement the method so that no traps are laid for maintainers who might add a hash code based collection at a later time.Equal()
returns true) then GetHashCode()
for the two objects must return the same value. This does not apply in the reverse direction. It is not symmetrical. Picture a lookup function that first goes to a "bucket" based on the hash code and then picks out the particular item using the equality test.HashCode.Combine()
passing in the values used in the equality test (or a subset). Bear in mind the more information you provide to Combine()
the more performant the hash implementation is likely to be.public class Assessment
{
private int rating;
private Person boss;
public override int GetHashCode()
{
return HashCode.Combine(rating, boss);
}
}
To improve performance slightly, especially where objects belong to collections you can add an overloaded member public bool Equals(T other)
.
This will save a certain amount of type checking for reference types and will save a boxing step for value types as they will not need to be converted to an object (boxed) as an argument to public override bool Equals(object other)
.
If you add the interface IEquatable<T>
to your class this will require the overload to be implemented. Unless your code contains routines that take objects of type IEquatable<T>
(and presumably do something interesting relating to equality irrespective of the implementing class) there is really no other compelling to include the interface.
IEqualityComparer<T>
If you have a class that can be uniquely identified in two different ways, say a Person
class that has a SSID and a unique email address then .NET provides a means to allow two different collections to use different hash-code and equality tests. Each can have an different implementation of IEqualityComparer<T>
with its own an Equals()
and a GetHashCode()
method. You can have a dictionary keyed on SSID and another keyed on email address.
Where IEqualityComparer<T>
is in play you would typically still override Equals()
and GetHashCode()
on your item class to avoid problems outside the collection classes.
One consideration when using IEqualityComparer<T>
is that private methods etc. will not be available for the equality test.
If only one hashed collection is in play then it may be better to avoid IEqualityComparer<T>
and encapsulate equality and hash code in the object itself. It is not ideal, in the first place, that a key dependency such as that between object and hashed collection cannot be enforced by the compiler but with the hashing and comparison logic elsewhere it would be even easier for a maintainer to make changes to a class's members without regard to the consequences for the collection.
One primitive that can challenge the unwary coder is testing the equality of floating-point values. This is discussed in the about.md document for the floating-point-numbers
concept.
This article shows some of the decisions that have to be made with regard to equality and inheritance. They will only concern you if you are manipulating instances of a class and a derived class and need to test the equality between the two.