Evaluating equality for a type would mean to define the conditions any two instances of it, must meet in order for them to be considered equal.
.NET by default, gives you reference equality out of the box - two different instances of a type would never evaluate to be equal because they would essentially point to different memory locations.
Having said that, .NET also gives us the possibility to override the reference equality provided by default and define our definition of when two instances of a type be considered equal. These are made available through the System.Object
contract -
virtual bool Equals(object? obj)
static bool Equals (object? objA, object? objB)
static bool ReferenceEquals (object? objA, object? objB)
Few things to remember here is ReferenceEquals
is internally called by the default implementation of Equals(object? obj)
to give us the reference equality and also the static bool Equals (object? objA, object? objB)
internally calls the default virtual Equals. The static implementation is given as an additional sanity check if we ever try to evaluate a null instance for equality.
So if we want to override the default equality, we override the above methods in our type.
Also if we look closely, the argument for the Equals method is object
and all the types in .NET derive from object class, this would definitely come with an overhead of casting our type (for which we are defining equality). To make things efficient, .NET also gives us the IEquatable contract which ensures type safety.
The last thing to remember before we move on to the actual purpose of the post, is the == operator we use to check if two instances are equal. Well this is not from .NET and it is a C# operator.
If we define our own equality for one of our types by overriding the previously mentioned three methods and the IEquatable contract and not override the == operator then we are bound to get ambiguous results. The C# operator == by default for a reference type will always evaluate the reference equality
Consider the Person type here, lets assume that for two person instances to be equal we will just look at the Name and not the City value. Two Person instances with same Name value are equal. We define the equality here as follows
public class Person : IEquatable<Person>
{
public string Name { get; set; } = string.Empty;
public string City { get; set; } = string.Empty;
public static bool operator ==(Person left, Person right) => left.Equals(right);
public static bool operator !=(Person left, Person right) => !left.Equals(right);
public bool Equals(Person? other) => other?.Name == Name;
public override bool Equals(object? obj)
{
if (obj is Person)
{
return Equals((Person)obj);
}
return false;
}
public override int GetHashCode() => Name.GetHashCode();
}
Now consider the below snippet, if we were to write a generic method which checks if the two person instances are equal we probably would do something like this
var person1 = new Person { Name = "Doofus", City = "London" };
var person2 = new Person { Name = "Doofus", City = "Madrid" };
Console.WriteLine( $"Are they equal through Equals ? {IsThePersonSameCheckWithEquals(person1, person2)}");
Console.WriteLine($"Are they equal through Operator? {IsThePersonSameCheckWithOperator(person1, person2)}");
static bool IsThePersonSameCheckWithEquals<T>(T person1, T person2)
where T:class
{
return person1.Equals( person2);
}
static bool IsThePersonSameCheckWithOperator<T>(T person1, T person2)
where T : class
{
return person1 == (person2);
}
If we observe the outputs, the equality evaluates to false with == operator in spite of us overriding it in our type Person
.
For Generics, the compiler assumes that there is no overload for the == operator (and also disregards the one we wrote ourselves) and checks for reference equality instead
We should instead use the Equals to compare instances in a generic code implementation
Top comments (0)