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.
==
operator is that it's really two operators; when it's used with types for which overrides exist, it uses the override; otherwise if the operands are reference types it's a reference-equality check. Since==
is bound statically rather than virtually, even when used with generics, this behavior can cause unexpected results. In vb.net, separate operators are used for overridable equality and reference equality, avoiding such ambiguity. – supercat