I believe getting something as simple as checking objects for equality correct is a bit tricky with .NET's design.
For Struct
1) Implement IEquatable<T>
. It improves performance noticeably.
2) Since you're having your own Equals
now, override GetHashCode
, and to be consistent with various equality checking override object.Equals
as well.
3) Overloading ==
and !=
operators need not be religiously done since the compiler will warn if you unintentionally equate a struct with another with a ==
or !=
, but its good to do so to be consistent with Equals
methods.
public struct Entity : IEquatable<Entity>
{
public bool Equals(Entity other)
{
throw new NotImplementedException("Your equality check here...");
}
public override bool Equals(object obj)
{
if (obj == null || !(obj is Entity))
return false;
return Equals((Entity)obj);
}
public static bool operator ==(Entity e1, Entity e2)
{
return e1.Equals(e2);
}
public static bool operator !=(Entity e1, Entity e2)
{
return !(e1 == e2);
}
public override int GetHashCode()
{
throw new NotImplementedException("Your lightweight hashing algorithm, consistent with Equals method, here...");
}
}
For Class
From MS:
Most reference types should not overload the equality operator, even if they override Equals.
To me ==
feels like value equality, more like a syntactic sugar for Equals
method. Writing a == b
is much more intuitive than writing a.Equals(b)
. Rarely we'll need to check reference equality. In abstract levels dealing with logical representations of physical objects this is not something we would need to check. I think having different semantics for ==
and Equals
can actually be confusing. I believe it should have been ==
for value equality and Equals
for reference (or a better name like IsSameAs
) equality in the first place. I would love to not take MS guideline seriously here, not just because it isn't natural to me, but also because overloading ==
doesn't do any major harm. That's unlike not overriding non-generic Equals
or GetHashCode
which can bite back, because framework doesn't use ==
anywhere but only if we ourself use it. The only real benefit I gain from not overloading ==
and !=
will be the consistency with design of the entire framework over which I have no control of. And that's indeed a big thing, so sadly I will stick to it.
With reference semantics (mutable objects)
1) Override Equals
and GetHashCode
.
2) Implementing IEquatable<T>
isn't a must, but will be nice if you have one.
public class Entity : IEquatable<Entity>
{
public bool Equals(Entity other)
{
if (ReferenceEquals(this, other))
return true;
if (ReferenceEquals(null, other))
return false;
//if your below implementation will involve objects of derived classes, then do a
//GetType == other.GetType comparison
throw new NotImplementedException("Your equality check here...");
}
public override bool Equals(object obj)
{
return Equals(obj as Entity);
}
public override int GetHashCode()
{
throw new NotImplementedException("Your lightweight hashing algorithm, consistent with Equals method, here...");
}
}
With value semantics (immutable objects)
This is the tricky part. Can get easily messed up if not taken care..
1) Override Equals
and GetHashCode
.
2) Overload ==
and !=
to match Equals
. Make sure it works for nulls.
2) Implementing IEquatable<T>
isn't a must, but will be nice if you have one.
public class Entity : IEquatable<Entity>
{
public bool Equals(Entity other)
{
if (ReferenceEquals(this, other))
return true;
if (ReferenceEquals(null, other))
return false;
//if your below implementation will involve objects of derived classes, then do a
//GetType == other.GetType comparison
throw new NotImplementedException("Your equality check here...");
}
public override bool Equals(object obj)
{
return Equals(obj as Entity);
}
public static bool operator ==(Entity e1, Entity e2)
{
if (ReferenceEquals(e1, null))
return ReferenceEquals(e2, null);
return e1.Equals(e2);
}
public static bool operator !=(Entity e1, Entity e2)
{
return !(e1 == e2);
}
public override int GetHashCode()
{
throw new NotImplementedException("Your lightweight hashing algorithm, consistent with Equals method, here...");
}
}
Take special care to see how it should fare if your class can be inherited, in such cases you will have to determine if a base class object can be equal to a derived class object. Ideally, if no objects of derived class is used for equality checking, then a base class instance can be equal to a derived class instance and in such cases, there is no need to check Type
equality in generic Equals
of base class.
In general take care not to duplicate code. I could have made a generic abstract base class (IEqualizable<T>
or so) as a template to allow re-use easier, but sadly in C# that stops me from deriving from additional classes.