As the title says: do I need to override the ==
operator? how about the .Equals()
method? Anything I'm missing?
-
Also watch out for stackoverflow.com/questions/1972262/… -- if you're not careful then comparison of your struct (a value type) to null will compile just fine but not do what you expect.– yoyoCommented Jan 26, 2015 at 19:35
6 Answers
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);
}
}
-
I wonder if it woldn't be better for performance to use
Complex other = obj as Complex
and then check ifother == null
instead of usingis
and then a cast...– ClémentCommented Nov 9, 2012 at 17:38 -
6@Clement: You can't do that for a struct; the result can't be null. You'd get a compile error. Commented Nov 26, 2012 at 10:04
-
@MatthewWatson: I think one could use
Complex? other = obj as Complex?
, but nullable types are often not amenable to efficiency.– supercatCommented May 30, 2013 at 20:08 -
1@HaraldCoppoolse - Value types in naturally sealed so it's not possible to derive a
MyComplex
as you suggest. Commented Aug 27, 2018 at 23:26 -
1Why not obj is SaveOptions op && this == op; ? Commented Jul 14, 2019 at 14:21
You should also implement IEquatable<T>. Here is an excerpt from Framework Design Guidelines:
DO implement IEquatable on value types. The Object.Equals method on value types causes boxing, and its default implementation is not very effcient because it uses refection. IEquatable.Equals can offer much better performance and can be implemented so that it does not cause boxing.
public struct Int32 : IEquatable<Int32> {
public bool Equals(Int32 other){ ... }
}
DO follow the same guidelines as for overriding Object.Equals when implementing IEquatable.Equals. See section 8.7.1 for detailed guidelines on overriding Object.Equals
-
-
2Because reference types do not need to be boxed when passed as object, ergo, IEquatable<T> would not provide any benefit. Value types are usually copied fully onto the stack (or into the outer types layout), so to get an object reference to it, and correctly handle the lifetime of the object, it needs to be boxed (wrapped with a special type) and copied to the heap; only then the reference to the heap object can be passed to a function like Object.Equals.– gimpfCommented Apr 8, 2013 at 15:22
Unfortunetely I don't have enough reputation to comment other entries. So I'm posting possible enhancement to the top solution here.
Correct me, if i'm wrong, but implementation mentioned above
public struct Complex
{
double re, im;
public override bool Equals(Object obj)
{
return obj is Complex && this == (Complex)obj;
}
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);
}
}
Has major flaw. I'm refering to
public override int GetHashCode()
{
return re.GetHashCode() ^ im.GetHashCode();
}
XORing is symmetrical, so Complex(2,1) and Complex(1,2) would give same hashCode.
We should probably make something more like:
public override int GetHashCode()
{
return re.GetHashCode() * 17 ^ im.GetHashCode();
}
-
9Having hashcode collisions is not necessarily a problem. In fact you will always have a chance of a collision (read up on pigion holes/birthday paradox) In your case Complex(1,4) and Complex(4,1) collide (admittedly there were less collisions) it depends on your data. The hashcode is used to quickly weed out 99.999% of the unwanted objects (e.g., in a dictionary) The equality operators have the final say. Commented Dec 19, 2013 at 1:15
-
That been said the more properties you have on the struct, there is a bigger chance of a collision. This may be a better hash algorithm: stackoverflow.com/a/263416/309634 Commented Dec 19, 2013 at 1:22
-
@DarcyThomas That doesn't mean you should be outright dismissing collision probabilities, especially in data structures that expect redundancy. You could potentially be generating far more 0-hashes than you reasonably should be and for what benefit exactly? At a certain point, intentionally crippling uniqueness defeats the entire purpose of generating the hash.– arkonCommented May 31, 2022 at 15:25
Most of the time you can avoid implementing Equals and GetHashcode in structs - because there is an automatic implementation by the compiler for Value types using bitwise content + reflection for reference members.
Have a look at that post : Which is best for data store Struct/Classes?
So for ease of use you could still implement == and !=.
But most of the time you can avoid implementing Equals and GetHashcode.
A case where you'd have to implement Equals and GetHashCode is for a field that you don't wan't to take into account.
For instance a field that varies as time goes by like Age of a Person or instantSpeed of a car( the identity of the object shouldn't change if you want to find it back in the dictionary at the same place)
Regards, best code
-
Reflection is a lot slower compared to a manual implementation. If you care about performance, DO write them manually. Commented Aug 15, 2019 at 7:37
The basic difference among the two is that the ==
operator is static, i.e. the appropriate method to invoke is determined at compile time, while the Equals
method is invoked dinamically on an instance.
Defining both is probably the best thing to do, even if this matters less in the case of structs, since structs cannot be extended (a struct can't inherit from another).
Just for completness I would also advice to overload Equals
method:
public bool Equals(Complex other)
{
return other.re == re && other.im == im;
}
this is a real spead improvement as there is no boxing occuring of the input argument of Equals(Object obj)
method
Some best practices for using value types:
- make them immutable
- override Equals (the one that takes an object as argument);
- overload Equals to take another instance of the same value type (e.g. * Equals(Complex other));
- overload operators == and !=;
- override GetHashCode
This comes from this post: http://theburningmonk.com/2015/07/beware-of-implicit-boxing-of-value-types/