How does one override .Equals() for a Discriminated Union?
Clash Royale CLAN TAG#URR8PPP
How does one override .Equals() for a Discriminated Union?
I have a discriminated union type and want to override .Equals()
.
.Equals()
In this simple example I could have used the .Equals function for int
to solve the problem, but in my code otherStuff does not support structural comparison.
int
The following code was my best try:
[<CustomEquality>]
type ModelArg = Name: string; OtherStuff: int
with override this.Equals (o: obj) = this.Name = (o :?> ModelArg).Name
I then got a red squiggly line and the following message:
"The struct, record or union type 'ModelArg' has an explicit implementation of 'ObjectEquals'. Consider implementing a matching override for 'Object.GetHashCode'."
I would like to avoid doing that because I really only care for the field Name
and also, for performance reasons.
Name
Of course I could write an equals
function but I would not be able to use it with List
functions like List.contains
and I need to do that.
equals
List
List.contains
Any suggestions?
1 Answer
1
The error is telling you that, since you're overriding the Equals
method, it's a very good idea to override GetHashCode
as well.
Equals
GetHashCode
The reason for this is that in .NET in general (not just in F#), hash codes are often used as an approximation of equality. For example, if you were to put your objects in a hash table, the hash table would distribute them between buckets based on GetHashCode
, and would look them up in the buckets that way too. Then, if Equals
is implemented differently than GetHashCode
, the hash table's behavior will be unpredictable - it might fail to look up an object that was just inserted or something similar.
GetHashCode
Equals
GetHashCode
Further, the error message does not suggest that you include the int in the definition of equality. All it says is that you need to implement GetHashCode
, and do it in the same sense as your Equals
implementation. There is also no performance penalty for doing this, as long as you never actually call GetHashCode
. And if you do - see above.
GetHashCode
Equals
GetHashCode
Since all your Equals
implementation does is compare the Name
field, it would probably make sense to delegate GetHashCode
to the same field as well:
Equals
Name
GetHashCode
[<CustomEquality>]
type ModelArg = Name: string; OtherStuff: int
with
override this.Equals (o: obj) = this.Name = (o :?> ModelArg).Name
override this.GetHashCode() = this.Name.GetHashCode()
Finally, your implementation of Equals
would crash when called with a null
or with an object of another type. I would suggest that you handle this case if you want your code to be robust:
Equals
null
override this.Equals (o: obj) =
match o with
| :? ModelArg as ma -> this.Name = ma.Name
| _ -> false
CustomEquality
NoComparison
CustomComparison
By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.
Thanks. I tried this but got a message: "The
CustomEquality
attribute must be used in conjunction with theNoComparison
orCustomComparison
attributes. I added the former and everything worked fine. I also modified your suggestion for handling the case in which the argument is of another type: I generate an exception in that case.– Soldalma
Aug 6 at 3:10