views:

181

answers:

5
+2  Q: 

Value vs Reference

What decision should I take about Equals(), ReferenceEquals(), and == from the following results? What do they produce actually?

#region
int integer = 1;
int integer2 = integer;

bool referenceEquality = (integer == integer2);//true
bool valueEquality = integer.Equals(integer2);//true
bool valueEqualityMore = object.Equals(integer, integer2);//true
bool valueEqualityMoreMore = object.ReferenceEquals(integer, integer2);//false
#endregion

#region
int integer = 1;
int integer2 = 1;

bool referenceEquality = (integer == integer2);//true
bool valueEquality = integer.Equals(integer2);//true
bool valueEqualityMore = object.Equals(integer, integer2);//true
bool valueEqualityMoreMore = object.ReferenceEquals(integer, integer2);//false
#endregion

#region
MyClass obj = new MyClass(1, "Hello");
MyClass obj2 = obj;

bool referenceEquality = (obj == obj2);//true
bool valueEquality = obj.Equals(obj2);//true
bool valueEqualityMore = object.Equals(obj, obj2);//true
bool valueEqualityMoreMore = object.ReferenceEquals(obj, obj2);//true            
#endregion

#region
MyClass obj = new MyClass(1, "Hello");
MyClass obj2 = new MyClass(1, "Hello");

bool referenceEquality = (obj == obj2);//false
bool valueEquality = obj.Equals(obj2);//false
bool valueEqualityMore = object.Equals(obj, obj2);//false
bool valueEqualityMoreMore = object.ReferenceEquals(obj, obj2);//false
#endregion

Hell! I understand nothing.

To me referenceEquals() of the 1st block should return true. == in the second block should return false (as the references are different). And, both of the Equals() in the 4th block should return true (as their values are same).

+8  A: 

The first point of confusion you seem to be having is that with value types, i.e. int, float, DateTime, the == operator is value equality. With reference types, == is (by default, see below) reference equality. This would explain the disparity of answers in your first two integer cases.

Secondly, the default implementation of Equals() tests reference equality, not value equality. So since it appears that MyClass doesn't override Equals(), that explains the disparity of answers between your reference cases.

Additionally, many reference types, such as String, override the == operator to provide value-equality semantics. So your best bet, until you memorize which types are which, is to look up the documentation.

In short:

  • Value types
    • == is value-equality (for framework types)
    • Not a reference type, so reference equality is meaningless
  • Reference types
    • ReferenceEquals() is always reference equality
    • == is reference equality by default, but can be (and often is for framework types) overridden to provide value equality
    • Equals() is reference equality by default, but can be (and often is for framework types) overridden to provide value equality
Lee
A nice update to all this can be found here: http://stackoverflow.com/questions/384294/where-is-the-implementation-of-internalequalsobject-obja-object-objb, which shows the C++ code of what goes on under the hood. Basically, it explicitly shows that value types are treated specially by `Equals`.
Abel
+1  A: 

ReferenceEquals: two objects are the same if they point to the same place in memory. This is never the case for two distinct value types.

Equals: two objects are equals if the Equals override deems they're equal. This usually means if ReferenceEqual would return true, and if the properties return true. Equals behaves often differently per object or struct. Note: each built-in value type (int, double, IntPtr) has overridden Equals, which is why it behaves different for value types (compares content) then with ReferenceEquals (compares addresses).

==: returns true if two objects have equal references, or if two value types have equal content. Boxed value types are unboxed before they are compared, unltess they are explicitly cast to an object first.

Note that your new MyClass(...) returned false with Equals. That is because the implementation of MyClass did not override the Equals method. Result: it behaves the same as the ReferenceEquals.

Update: added note on Equals for value types

Abel
And what happened to the integral ReferenceEquals()?
JMSA
And, why, int integer = 1;int integer2 = 1;bool referenceEquality = (integer == integer2);//true
JMSA
`==` behaves different then calling `ReferenceEquals` directly. For value types it behaves the same as calling `Equals`.
Abel
+1  A: 

ReferenceEquals checks if two object references reference the same object. That is, they point to the same location in memory.

Equals is a virtual method, so it may in practice be overridden to do anything. The purpose of the method however is to compare instances in a way that makes sense for the type, whatever way that may be. If Equals is not overridden, the implementation of object.Equals is used, which is equivalent to ReferenceEquals.

== is the equality operator, which by default compares reference equality for instances of reference types (i.e. classes, boxed value types, interfaces), and value equality (same values for fields) for instances of value types (i.e. structs). == can be overloaded to provide custom behaviour, but it's not virtual like Equals.

Using an int as an object, for example by passing it to a method like ReferenceEquals that has object parameters, boxes the int, creating an object on the heap with the integer inside. object.ReferenceEquals(integer1, integer2) basically means object.ReferenceEquals((object)integer1, (object)integer2).

Comparing different boxed instances of the same integer by reference equality (ReferenceEquals or ==) will give false, while value equality (Equals) will give true. For unboxed identical integers, which are value types, == and Equals both compare value equality and will thus give true.

Joren
+1  A: 

By default, operator== behaves as follows when used as a==b. Note that operator== can be overridden to perform anything.

  • If a is a value type, then this compiles as a.Equals(b).
  • If a is a reference type, then this compiles as object.Equals(a,b).

object.Equals(a, b) operates only on reference types and performs the following:

  • If a is null, then return (b==null)
  • Otherwise call a.Equals(b)

object.ReferenceEquals(a, b) operates only on reference types. An object reference in C#/.NET is internally held as a pointer. This method returns true if the references a and b are the same, that is, if they point internally to the same object.

Value types exist in two forms: unboxed and boxed. For the following cases discussing value types, I'll use the following variables:

int unboxed = 3;
// boxing occurs automatically when a value type is cast to object or
// to an interface. This allocates memory on the heap to store the value
// and places a reference (internally a pointer) to this memory in boxed.
object boxed = unboxed;

At this point, boxed behaves as a reference type. Two boxed values may not be at the same location in memory, as you see in your call to object.ReferenceEquals(integer1, integer2) (auto-boxing occurs because of the cast of each parameter to object). When you call object.Equals(integer1, integer2), the values are boxed, but since the boxed form of integer1 is not null, the call behaves like integer1.Equals((object)integer2), which returns true because the boxed values are both 1. (see note below on why I have a manual cast here).

Note on the manual cast above: System.Int32, along with most other value types, has a method Int32.Equals(Int32 other), so calling integer1.Equals(integer2) would not box the value of integer2. This has big (positive) impact on performance for lightweight value types.

280Z28
+1  A: 

I made corrections on what the actual comparisons done are. I added the same comparisons for all cases, as you were only doing .Equals(int) on the integer (which uses the == operator), not .Equals(object) as for the other types. I also added explicit castings for the parameters to show which implicit castings are caused by using them as parameters:

int integer = 1;
int integer2 = 1; // exactly the same result as copying integer

bool valueEquality = (integer == integer2); //true
bool valueEquality2 = integer.Equals(integer2); //true
bool typeAndValueEquality = integer.Equals((object)integer2); //true
bool typeAndValueEquality2 = object.Equals((object)integer, (object)integer2); //true
bool referenceEquality = object.ReferenceEquals((object)integer, (object)integer2); //false

//

MyClass obj = new MyClass(1, "Hello");
MyClass obj2 = obj;

bool referenceEquality = (obj == obj2); //true
bool referenceEquality2 = obj.Equals((object)obj2); //true
bool typeAndReferenceEquality = object.Equals((object)obj, (object)obj2); //true
bool referenceEquality3 = object.ReferenceEquals((object)obj, (object)obj2); //true

//

MyClass obj = new MyClass(1, "Hello");
MyClass obj2 = new MyClass(1, "Hello");

bool referenceEquality = (obj == obj2); //false
bool referenceEquality2 = obj.Equals((object)obj2); //false
bool typeAndReferenceEquality = object.Equals((object)obj, (object)obj2); //false
bool referenceEquality = object.ReferenceEquals((object)obj, (object)obj2); //false
Guffa
@Guffa, Very good. I have had accepted your answer if you posted this before.
JMSA