2

Explanation:

  • Equals() compares the values of two objects.
  • ReferenceEquals() compares their references.

For reference types operator== by default compares references, while for value types it performs (AFAIK) the equivalent of Equals() using reflection.

So. I have a situation where I need to compare two reference types by their values. I can explicitly call Equals() or I can overload operator== to perform the desired comparison.

However, overloading operator== for value comparison kinda-sorta violates the principle of least astonishment. On the other hand explicitly calling two-object Equals looks like overkill.

What is standard practice here?


I know how to override Equals(). The question was whether it is commonly acceptable to override operator== to test for value equality on reference types or whether it is commonly accepted practice to explicitly call Equals/ReferenceEquals to explicitly specify which comparison you want.

Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
SigTerm
  • 26,089
  • 6
  • 66
  • 115
  • See: [Guidelines for Overriding Equals() and Operator == (C# Programming Guide)](https://msdn.microsoft.com/en-US/library/ms173147(v=vs.90).aspx) – Habib Jul 06 '15 at 13:45
  • 1
    Not a duplicate, since I'm asking about commonly accepted standard and not about fastest/whatever method. Different question. – SigTerm Jul 06 '15 at 13:45
  • For reference types, unless you override `Equals`, it will also do reference equality. – juharr Jul 06 '15 at 13:55

4 Answers4

3

What is standard practice?

The "standard practice" is, if you want to check two elements for equality which isn't reference equality, you need to implement IEquatable<T>, which introduces a Equals(T other) method, and override GetHashCode. That way, you control the way these two objects are compared. Usually, this includes overriding the == and != operators as well.

while for value types it performs (AFAIK) equivalent of Equals() using reflection.

Only if your value type has a member which is a reference type. If it's all value types, it will do a bit comparison of these two objects:

// if there are no GC references in this object we can avoid reflection 
// and do a fast memcmp
if (CanCompareBits(this))
    return FastEqualsCheck(thisObj, obj);

The question was whether it is commonly acceptable to override operator== to test for value equality on reference types

It really depends on what you're doing. It is encouraged to override the == operator once you override Equals because you want a consistent behavior value equality semantics. This depends on your definition of equality between two objects.

Although, If an object is mutable, then doing value comparison might result in odd scenarios where two objects are considered equal but later, one is mutated. This should definitely be analyzed on per case basis. Usually, overriding Equals should suffice.

Yuval Itzchakov
  • 146,575
  • 32
  • 257
  • 321
  • "You wouldn't want the former to perform a reference equality while the latter performs value equality" Why, though? That would be consistent with default behavior. I mean, if by default operator+ adds objects, then overload should do same thing. Shouldn't be same principle used on operator==? Or is it common practice to treat reference types as if they were value types in C#? – SigTerm Jul 06 '15 at 13:53
  • @SigTerm It is never common practice to treat reference types as value types. I've edited my answer to better reflect what I ment. If you define two objects as equal if all their members are identical, then `Equals` and `==` should return the same. Else, you can let `==` do the reference equality only. – Yuval Itzchakov Jul 06 '15 at 13:54
  • 1
    `Usually, it is common practice to first check reference equality for both types, if they don't match, then check for value equality (if needed).` This is just an optimization, and not important for the actual semantics. Something would need to be sorely wrong for removing that check to actually change the result of the method. It would mean that the value check would need to fail when an object was compared with itself. And note that if the actual value based check is very fast (if there's only one member to compare and it can be quickly checked) this may not even improve performance. – Servy Jul 06 '15 at 13:59
  • @Servy It may be important for semantics, if two immutable objects are different instances but their values are actually semantically identical. This would mean `==` would return false. This isn't only done for performance sake. – Yuval Itzchakov Jul 06 '15 at 14:01
  • @YuvalItzchakov `==` would return whatever it was overridden to return. If it was overridden to perform value semantics, then that would be what it would return. That said, that point is irrelevant to the section I quoted. If you do a reference check, find the objects unequal, and therefore have to do a value comparison then you haven't changed the semantics of the method from just doing a value comparison to being with. The reference comparison has not changed the result of the method. – Servy Jul 06 '15 at 14:05
  • You're right, that is simply to optimize the case they're actually equal. Now I see what you meant. – Yuval Itzchakov Jul 06 '15 at 14:06
  • "If you define two objects as equal if all their members are identical, then Equals and == should return the same" That... frankly, doesn't make much sense to me. By default operator== compares VALUES of two different references to objects. Unless "this" is an explicit member of the object, defining equality as having members as equal sounds like incorrect behavior, because it pretty much skips "reference" part and goes straight for field-by-field comparison which is suitable for "value-type". Yet you say that this is not treating them as if they were value types. :-\ – SigTerm Jul 06 '15 at 14:08
  • The same can be said of that whole second section on checking the bits of the object. The semantic behavior is that it checks the quality of all of the fields. As an optimization, if they know all of the fields are types that will perform a check of the bits, it will perform an optimization *that it knows will have identical semantics* of just checking each of the bits, so the OP's statement is really fine, as far as the semantic behavior of the method is concerned. – Servy Jul 06 '15 at 14:08
  • @Servy The OP's statement is that the comprassion done via reflection. I was simply adding that there is also a different code path of execution. No body said the semantic behavior was wrong. – Yuval Itzchakov Jul 06 '15 at 14:10
  • Basically, what confuses me is that by default `operator==` tests for two objects being the *same*. Overriding it to test for them being *identical* breaks default behavior, because *being identical* is not the same as *being the same object*. Although, overriding it for immutable types makes sense because the emulate value types. – SigTerm Jul 06 '15 at 14:11
  • 1
    @SigTerm Both `object.Equals` and `operator ==` have *identical semantics* by default. They both compare references of reference types, and they both compare the values of all fields on value types. Both can be overridden to have whatever behavior you want them to have. If you think it's wrong for any reference type to ever override either to have a value comparison, rather than just a reference comparison, look at something like `string`. People very rarely care if two strings point to the same object; they care if the two strings represent the same sequence of characters. – Servy Jul 06 '15 at 14:11
  • 1
    @SigTerm *operator== tests for two objects being the same.* What does "being the same" mean? The entire point of implementing `IEquatable` is that you say what "same" really means. You bring extra meaning into it other then the good ol' reference equality or value equality semantic by introducing your own equality semantic to the type. – Yuval Itzchakov Jul 06 '15 at 14:14
  • @SigTerm Immutable types do not "emulate value types". They're simply immutable. Whether something is immutable is entirely orthogonal to whether it has reference or value semantics. If you feel that, for a particular type, it makes more sense for its instances to be compared based on their references, rather than their values, then *that's fine*. Don't override equality for that type. If you have a type that *does* make semantic sense to compare based on its values, rather than the reference to it, then do that. You can give your objects whatever equality semantics you want. – Servy Jul 06 '15 at 14:14
  • @YuvalItzchakov: My primary language is C++. Being identical means that all fields of objects A are identical to object B. Being same means their references are equal and A and B both point to the same object. Same: ReferenceEquals() returns true. Identical: ReferenceEquals() returns false. – SigTerm Jul 06 '15 at 14:17
  • @SigTerm `ReferenceEquals` defines equality by reference. This is the default behavior for reference types. Value types, on the other had, are defined by value equality (identical, in your world). If you have a reference type, where A being equal to B is determined by the fact that both their fields are the same, then do by all means override the default behavior, including `==`. – Yuval Itzchakov Jul 06 '15 at 14:18
  • 1
    @SigTerm Your assertion that `==` should always and forever provide reference semantics simply isn't true. If it were, then there'd be no reason to allow it to be overridden in the first place. If you personally want to never override it, then that's fine. Nobody's forcing you to do that. – Servy Jul 06 '15 at 14:20
  • @Servy: I also checked documentation and you're correct about operator== and Equals doing the same thing. Although that's not explicitly mentioned anywhere and documentation for Equals is 20+ times longer. – SigTerm Jul 06 '15 at 14:20
  • 1
    @Servy: As I said, C# is not my primary. Which is why I asked what people usually do in this kind of situation. – SigTerm Jul 06 '15 at 14:21
2

Equals() performs value comparison of two objects.

This is not true. The default behavior of object.Equals on value types is to compare each of the fields using their definition of equality, the default behavior of reference types is to compare their references. It can be overridden to do whatever you want it to do. It is exactly the same as the == operator in this regard.

The only difference between the == operator and Equals is that Equals will perform a virtual dispatch on the first (but not the second) operand, finding the implementation of the method based on the runtime type of that object. The == operator is entirely statically bound; it considers only the compile time type of both operands. Other than this difference in binding both have the same default behaviors, and both can be overridden to provide whatever implementation you want.

The standard practice is to always ensure that the behavior of Equals and operator == is the same for your type. If you override the Equals method to change the equality semantics, then you should also overload the == operator to provide *identical semantics`, and vice versa.

Servy
  • 202,030
  • 26
  • 332
  • 449
1

The question was whether it is commonly acceptable to override operator== to test for value equality

It depends on the object, if the object is immutable then you can override == operator, otherwise not. (Remember they are just guidelines).

See: Guidelines for Overriding Equals() and Operator == (C# Programming Guide)

By default, the operator == tests for reference equality by determining whether two references indicate the same object. Therefore, reference types do not have to implement operator == in order to gain this functionality. When a type is immutable, that is, the data that is contained in the instance cannot be changed, overloading operator == to compare value equality instead of reference equality can be useful because, as immutable objects, they can be considered the same as long as they have the same value. It is not a good idea to override operator == in non-immutable types.

Habib
  • 219,104
  • 29
  • 407
  • 436
0

It's a good practice to give a semantic meaning to your code. So, if reference comparison is really something you should care about, use default behaviour for your classes; otherwise your application context dependent logic should be used for comparison. (with consistent behaviour of all the equality members like Equals, GetHashCode and operators)

Uladzislaŭ
  • 1,680
  • 10
  • 13