How to best implement Equals for custom types?
There is a whole set of guidelines on MSDN as well. You should read them well, it is both tricky and important.
A few points I found most helpful:
Value Types don't have Identity, so in a
struct Point
you will usually do a member by member compare.Reference Types usually do have identity, and therefore the Equals test usually stops at ReferenceEquals (the default, no need to override). But there are exceptions, like string and your
class Point2
, where an object has no useful identity and then you override the Equality members to provide your own semantics. In that situation, follow the guidelines to get through the null and other-type cases first.And there are good reasons to keep
GethashCode()
andoperator==
in sync as well.
Is it possible to define equality for named types/structs?
No. You can't modify the equality operator and there is no built-in way to add support for custom types to use ==
syntax. Instead you should compare the pointer values using reflect.DeepEqual
.
Go supports equality checking structs.
type Person struct {
Name string
}
a := Person{"Bill DeRose"}
b := Person{"Bill DeRose"}
a == b // true
It won't work with pointer fields (in the way you want) because the pointer addresses are different.
type Person struct {
Friend *Person
}
a := Person{Friend: &Person{}}
b := Person{Friend: &Person{}}
a == b // false
import "reflect"
a := Person{Friend: &Person{}}
b := Person{Friend: &Person{}}
reflect.DeepEqual(a, b) // true
Keep in mind there are caveats.
In general DeepEqual is a recursive relaxation of Go's == operator. However, this idea is impossible to implement without some inconsistency. Specifically, it is possible for a value to be unequal to itself, either because it is of func type (uncomparable in general) or because it is a floating-point NaN value (not equal to itself in floating-point comparison), or because it is an array, struct, or interface containing such a value.
List equality of a custom class
Implementing Equals
for a list can be done by using the SequenceEquals
method (from System.Linq
namespace), which ensures that each item in one list equals the item at the same index in the other list.
One thing you might consider changing, however is your implementation of GetHashCode
. This method should return the same number if two items are equal (though it's not guaranteed that two items with the same hash code are equal). Using base.GetHashCode()
does not meet this requirement, since the base
is object
in this case; according to the documentation, "hash codes for reference types are computed by calling the Object.GetHashCode method of the base class, which computes a hash code based on an object's reference", so objects only return the same HashCode if they refer to the exact same object.
The HashCode
should be based on the same properties used to determine equality, so in this case we want to use Prop.GetHashCode()
for class A
, and we want to aggregate the hashcode for all the items in Prop
for class B
.
Here's one way the classes could be refactored:
public class A : IEquatable<A>
{
public string Prop { get; }
public A(string val)
{
Prop = val;
}
public bool Equals(A other)
{
if (other == null) return false;
return Prop == other.Prop;
}
public override bool Equals(object obj)
{
return Equals(obj as A);
}
public override int GetHashCode()
{
return Prop.GetHashCode();
}
}
public class B : IEquatable<B>
{
public IReadOnlyList<A> Prop { get; }
public B(IReadOnlyList<A> val)
{
Prop = val;
}
public bool Equals(B other)
{
if (other == null) return false;
if (ReferenceEquals(this, other)) return true;
if (Prop == null) return other.Prop == null;
return other.Prop != null && Prop.SequenceEqual(other.Prop);
}
public override bool Equals(object obj)
{
return Equals(obj as B);
}
public override int GetHashCode()
{
return Prop?.Aggregate(17,
(current, item) => current * 17 + item?.GetHashCode() ?? 0)
?? 0;
}
}
C# - What interfaces + operators must be implemented to get value comparison and equality on custom types?
The first step would be to override
Object.Equals()
andoperator==
with something like:
No, the first step is to override object.Equals()
and GetHashCode()
. You must never override one without overriding the other to correspond, or else your class is buggy if used as a key.
Let's look at your Equals()
public override bool Equals(object obj)
{
MyType t = obj as MyType;
return (this.name == t.name) && (this.count == t.count);
}
There's a bug here because if obj
is null or is not a MyType
this will throw a NullReferenceException
. Let's fix that:
public override bool Equals(object obj)
{
MyType t = obj as MyType;
return t != null && (name == t.name) && (count == t.count);
}
I'd also probably put the count
comparison before the name
as it is likely to give a faster out if it doesn't match, but then I don't know your use-cases so maybe there are a small number of very common count
values in which case that doesn't hold. That's an optimisation matter though, let's fix the bug by giving you a corresponding GetHashCode()
public override int GetHashCode()
{
return (name?.GetHashCode() ?? 0) ^ count;
}
The minimum requirement is that if a.Equals(b)
then it must be true that a.GetHashCode() == b.GetHashCode()
. We also ideally want to spread the bits around as much as possible. We achieve the first (vital) part by basing our hash code on the properties that determine equality. The second part is more complicated, but in this case the relatively good quality of string's GetHashCode()
means just xor-ing with the remaining integer value will probably be reasonably good. Search the site for more details (including why just xoring is often not a good idea in other cases).
Now, you want ==
semantics. It's a requirement that if you define ==
you must define !=
, but we can easily define one in terms of the other.:
public static bool operator !=(MyType x, MyType y)
{
return !(x == y);
}
Now once we've got ==
done !=
will go through that. Of course we've already defined equality so we can start with using that:
public static bool operator ==(MyType x, MyType y)
{
return x.Equals(y);
}
This is buggy though because while it handles y
being null fine it throws if x
is null. We need to consider that too:
public static bool operator ==(MyType x, MyType y)
{
if (x == null)
{
return y == null;
}
return x.Equals(y);
}
Let's consider though that everything must be equal to itself (in fact you will be buggy if that doesn't hold). Since we have to consider the possibility of x == null && y == null
let's think about that as an example of the case of (object)x == (object)y
. This let's us skip the rest of the testing:
public static bool operator ==(MyType x, MyType y)
{
if ((object)x == (object)y)
{
return true;
}
if ((object)x == null)
{
return false;
}
return x.Equals(y);
}
How much of a benefit this is depends on how likely comparing with self is (it can be more common as a side-effect of various things than you might guess) and how expensive the equality method is (in this case not much, but in a case with more fields to compare it could be considerable).
Okay, we've got the Equals
and GetHashCode
sorted and we've added a ==
and !=
as you wanted them. What would be nice to have is IEqutable<MyType>
. This offers a strongly-typed Equals
that will be used when available by comparers within dictionaries, hash-sets, etc. So it's a nice to have. This will force us to implement bool Equals(MyType other)
which will be much like the override we already did, but without the conversion:
public bool Equals(MyType other)
{
return other != null && (name == other.name) && (count == other.count);
}
Bonus: Because of how overloading works our ==
is going to call into this slightly faster method instead of the override that does a cast. We've optimised ==
, and by extension !=
, without even touching them!
Now if we implement this then we have to implement GetHashCode()
which in turn means that we have to implement the object.Equals()
override, but we've already done that. We're duplicating here though, so lets re-write the override to use the strongly typed form:
public override bool Equals(object obj)
{
return Equals(obj as MyType);
}
All done. Putting it together:
public class MyType : IEquatable<MyType>
{
public string name;
public int count;
public MyType(string n, int c)
{
name = n;
count = c;
}
public bool Equals(MyType other)
{
return other != null && (name == other.name) && (count == other.count);
}
public override bool Equals(object obj) => Equals(obj as MyType);
public override int GetHashCode() => (name?.GetHashCode() ?? 0) ^ count;
public static bool operator ==(MyType x, MyType y)
{
if ((object)x == (object)y)
{
return true;
}
if ((object)x == null)
{
return false;
}
return x.Equals(y);
}
public static bool operator !=(MyType x, MyType y) => !(x == y);
}
IComparable<T>
and IComparable
are used if you also want to be able to order your objects; to say one is less than or comes before the other. It isn't needed for equality.
IEqualityComparer<T>
and IEqualityComparer
are used to override all the above and define equality in some completely different way in another class. The classic example here is that sometimes we want "abc" to be equal to "ABC" and sometimes we don't, so we can't just depend on ==
or the Equals()
methods of the types we've described above as they can only apply one rule. They are generally provided by other classes to the actual class being compared.
Let's say we sometimes want to ignore case in comparing MyType
instances. Then we can do:
public class CaseInsensitiveMyTypeEqualityComparer : IEqualityComparer<MyType>
{
public bool Equals(MyType x, MyType y)
{
if ((object)x == (object)y)
{
return true;
}
if ((object)x == null | (object)y == null)
{
return false;
}
return x.count == y.count && string.Equals(x.name, y.name, StringComparison.OrdinalIgnoreCase);
}
public int GetHashCode(MyType obj)
{
if (obj == null)
{
return 0;
}
return StringComparer.OrdinalIgnoreCase.GetHashCode(obj.name) ^ obj.count;
}
}
If you used this with say:
var dictionary = new Dictionar<MyType, int>(new CaseInsensitiveMyTypeEqualityComparer());
Then the dictionary would be case-insensitive for its keys. Note that since we defined equality based on case-insensitive comparison of the names we have to base the hash code on a case-insensitive hashing of it too.
If you don't use an IEqualityComparer<MyType>
then the dictionary uses EqualityComparer<MyType>.Default
which uses your more-efficient IEquatable<MyType>
implementation since it can, and would have used the object.Equals
override if you didn't have that.
You might guess that IEqualityComparer<T>
is relatively less-used than just using the equality defined by a class itself. Also if someone does need it, that person might not be you; one of the great things about it is that we can define them for other people's classes. Still, it's not a concern for your design of your class itself.
Custom Equality check for C# 9 records
Per the C#9 record proposal, the following should compile, even if not very useful without actual implementations..
// No explicit IEquatable<R> - this is synthesized!
public sealed record SimpleVo
{
// Not virtual, as SimpleVo (R) is sealed.
// Accepts SimpleVo? (R?), and not SimpleVo (R), as argument.
public bool Equals(SimpleVo? other) =>
throw new System.NotImplementedException();
// Optional: warning generated if not supplied when Equals(R?) is user-defined.
public int GetHashCode() =>
throw new System.NotImplementedException();
// No other “standard” equality members!
}
There are restrictions on the equality-related members as most of the code is synthesized. The proposal includes examples of the expected synthesized underlying type.
That is, given just a Equals(R?)
the compiler creates a ==
, !=
, and Equals(object)
.
The methods that can be defined can be found by searching for “user-defined” in the proposal.
Attempting to override/define other equality methods or operators is expected to fail:
It is an error if the override is declared explicitly.
The behavior is discussed in ‘Equality members’ and is summarized in the paragraph:
The record type implements
System.IEquatable<R>
and includes a synthesized strongly-typed overload ofbook Equals(R? other)
where R is the record type. The method is public, and the method is virtual unless the record type is sealed. The [Equals(R?)
] method can be declared explicitly. It is an error if the explicit declaration does not match the expected signature or accessibility, or the explicit declaration doesn't allow overriding it in a derived type and the record type is not sealed.
IfEquals(R? other)
is user-defined (not synthesized) butGetHashCode
is not [user-defined], a warning is produced.
ICollectionT.Contains on custom types
Because your type doesn't override Equals, the default implementation of Equals will be used, i.e. reference equality. So Contains will be true if the collection contains that very instance.
To use your own comparison, implement IEqualityComparer<T>
(e.g. to compare the Ids) and pass an instance of your comparer into the Contains method. (This assumes you are able to use LINQ extensions, as the "native" ICollection<T>.Contains
method doesn't have the IEqualityComparer overload.)
What needs to be overridden in a struct to ensure equality operates properly?
An example from msdn
public struct Complex
{
double re, im;
public override bool Equals(Object obj)
{
return obj is Complex c && this == c;
}
public override int GetHashCode()
{
return re.GetHashCode() ^ im.GetHashCode();
}
public static bool operator ==(Complex x, Complex y)
{
return x.re == y.re && x.im == y.im;
}
public static bool operator !=(Complex x, Complex y)
{
return !(x == y);
}
}
Correct way to override Equals() and GetHashCode()
You can override Equals() and GetHashCode() on your class like this:
public override bool Equals(object obj)
{
var item = obj as RecommendationDTO;
if (item == null)
{
return false;
}
return this.RecommendationId.Equals(item.RecommendationId);
}
public override int GetHashCode()
{
return this.RecommendationId.GetHashCode();
}
Related Topics
Passing Values Between Forms (Winforms)
Create a Coroutine to Fade Out Different Types of Object
What's the Equivalent of Vb's Asc() and Chr() Functions in C#
Replace Parameter in Lambda Expression
Find Out Username(Who) Modified File in C#
In Mvvmcross How to Do Custom Bind Properties
Cannot Access Non-Static Field
How to Draw a Rounded Rectangle as the Border for a Rounded Form
Different Like Behaviour Between My Application and the Access Query Wizard
Differencebetween Manualresetevent and Autoresetevent in .Net
Wrapping Stopwatch Timing with a Delegate or Lambda
How Much Faster Is C++ Than C#
How to Write One to Many Query in Dapper.Net
Right Aligning Text in PDFpcell
C# Windows Forms Application - Updating Gui from Another Thread and Class
How to Use Switch-Case on a Type