117

Why are the following expressions different?

[1]  (object)0 == (object)0 //false
[2]  ((object)0).Equals((object)0) // true

Actually, I can totally understand [1] because probably the .NET runtime will box the integer and start comparing the references instead. But why is [2] different?

Andre Pena
  • 56,650
  • 48
  • 196
  • 243
  • 36
    OK, now that you understand the answer to this question, check your understanding by predicting the result of: `short myShort = 0; int myInt = 0; Console.WriteLine("{0}{1}{2}", myShort.Equals(myInt), myInt.Equals(myShort), myInt == myShort);` Now check it against reality. Was your prediction correct? If not, can you explain the discrepancy? – Eric Lippert Dec 17 '13 at 19:17
  • 1
    @Star, for recommended reading see http://msdn.microsoft.com/en-us/library/vstudio/system.int16.equals%28v=vs.100%29.aspx for the available overloads on the `int16` aka `short` Equals method, then look at http://msdn.microsoft.com/en-us/library/ms173105.aspx. I don't want to spoil Eric Lippert's puzzle, but it should be pretty easy to figure out once you read those pages. – Sam Skuce Dec 17 '13 at 21:41
  • 2
    I thought this is a java question; at least before seeing the 'E' in Equals. – seteropere Dec 17 '13 at 22:46
  • 4
    @seteropere Java is actually different: the autoboxing in Java caches objects, so ``((Integer)0)==((Integer)0)`` evaluates to true. – Jules Dec 18 '13 at 04:36
  • @Jules: Correct. More generally it is guaranteed to work for every number in [-128,127] and *may* work for values outside that range (it doesn't in OpenJDK). – musiKk Dec 18 '13 at 08:03
  • 1
    You can also try `IFormattable x = 0; bool test = (object)x == (object)x;`. No new boxing is performed when the struct is already in a box. – Jeppe Stig Nielsen Dec 18 '13 at 10:16
  • @EricLippert, because of `IEquatable` :p – bas Jan 05 '14 at 14:41

7 Answers7

151

The reason the calls behave different is they bind to very different methods.

The == case will bind to the static reference equality operator. There are 2 independent boxed int values created hence they are not the same reference.

In the second case you bind to the instance method Object.Equals. This is a virtual method which will filter down to Int32.Equals and this checks for a boxed integer. Both integer values are 0 hence they are equal

JaredPar
  • 733,204
  • 149
  • 1,241
  • 1,454
  • The `==` case does not call `Object.ReferenceEquals`. It simply produces the `ceq` IL instruction to perform a reference comparison. – Sam Harwell Dec 18 '13 at 05:09
  • 8
    @280Z28 is that not just because the compiler inlines it? – markmnl Dec 18 '13 at 06:03
  • @280Z28 So? A similar case is that their Boolean.ToString method apparently contains hardcoded strings inside its function, rather than returning the publicly exposed Boolean.TrueString and Boolean.FalseString. It's irrelevant; The point is, `==` does the same thing as `ReferenceEquals` (on Object, anyway). It's all just internal optimizing on MS' side to avoid unnecessary internal function calls on oft-used functions. – Nyerguds Dec 18 '13 at 08:03
  • 6
    The C# Language Specification, paragraph 7.10.6, says: _The predefined reference type equality operators are: `bool operator ==(object x, object y);` `bool operator !=(object x, object y);` The operators return the result of comparing the two references for equality or non-equality._ It is not a requirement that the method `System.Object.ReferenceEquals` is used for determining the result. To @markmnl: No, the C# compiler does not inline, that is something that the jitter sometimes does (but not in this case). So 280Z28 is right the `ReferenceEquals` method is not actually used. – Jeppe Stig Nielsen Dec 18 '13 at 10:39
  • @JaredPar: It's interesting that the spec says that, since that's not how the language actually behaves. Given operators defined as above, and variables `Cat Whiskers; Dog Fido; IDog Fred;` (for non-related interfaces `ICat` and `IDog` and non-related classes `Cat:ICat` and `Dog:IDog`), the comparisons `Whiskers==Fido` and `Whiskers==34` would be legal (the first could only be true if Whiskers and Fido were both null; the second could never be true). In fact, a C# compiler will reject both. `Whiskers==Fred;` will be forbidden if `Cat` is sealed, but allowed if it isn't. – supercat Dec 18 '13 at 17:36
26

When you cast the int value 0 (or any other value type) to object, the value is boxed. Each cast to object produces a different box (i.e. a different object instance). The == operator for the object type performs a reference comparison, so it returns false since the left-hand side and right-hand side are not the same instance.

On the other hand, when you use Equals, which is a virtual method, it uses the implementation of the actual boxed type, i.e. Int32.Equals, which returns true since both objects have the same value.

Thomas Levesque
  • 286,951
  • 70
  • 623
  • 758
18

The == operator, being static, is not virtual. It will run the exact code that the object class defines (`object being the compile time type of the operands), which will do a reference comparison, regardless of the runtime type of either object.

The Equals method is a virtual instance method. It will be running the code defined in the actual run-time type of the (first) object, not the code in the object class. In this case, the object is an int, so it will perform a value comparison, as that is what the int type defines for its Equals method.

Servy
  • 202,030
  • 26
  • 332
  • 449
  • The `==` token actually represents two operators, one of which is overloadable and one of which isn't. The behavior of the second operator is very different from that of an overload on (object,object). – supercat Dec 17 '13 at 20:30
13

The Equals() method is virtual.
Therefore, it always calls the concrete implementation, even when the callsite is casted to object. int overrides Equals() to compare by value, so you get value comparison.

SLaks
  • 868,454
  • 176
  • 1,908
  • 1,964
10

== Use: Object.ReferenceEquals

Object.Equals compares the value.

The object.ReferenceEquals method compares references. When you allocate an object, you receive a reference containing a value indicating its memory location in addition to the object's data on the memory heap.

The object.Equals method compares the contents of objects. It first checks whether the references are equal, as does object.ReferenceEquals. But then it calls into derived Equals methods to test equality further. See this:

   System.Object a = new System.Object();
System.Object b = a;
System.Object.ReferenceEquals(a, b);  //returns true
  • Although `Object.ReferenceEquals` behaves like a method which uses the C# `==` operator on its operands, the C# reference-equality operator operator (which is represented by using `==` on operand types for which no overload is defined) uses a special instruction rather than calling `ReferenceEquals`. Further, `Object.ReferenceEquals` will accept operands which could only match if both happen to be null, and will accept operands which need to be type-coerced to `Object` and thus couldn't possibly match anything, while the reference-equality version of `==` would refuse to compile such usage. – supercat Dec 18 '13 at 17:28
9

The C# operator uses the token == to represent two different operators: a statically- overloadable comparison operator and a non-overloadable reference-comparison operator. When it encounters the == token, it first checks to see if there exists any equality-test overload which is applicable to the operand types. If so, it will invoke that overload. Otherwise, it will check whether the types are applicable to the reference-comparison operator. If so, it will use that operator. If neither operator is applicable to the operand types, compilation will fail.

The code (Object)0 doesn't merely upcast an Int32 to Object: Int32, like all value types, actually represents two types, one of which describes values and storage locations (such as the literal zero), but doesn't derive from anything, and one of which describes heap objects and derives from Object; because only the latter type may be upcast to Object, the compiler must create a new heap object of that latter type. Each invocation of (Object)0 creates a new heap object, so the two operands to == are different objects, each of which, independently, encapsulates the Int32 value 0.

The class Object does not have any usable overloads defined for the equals operator. Consequently, the compiler will not be able to use the overloaded equality-test operator, and will fall back to using the reference-equality test. Because the two operands to == refer to distinct objects, it will report false. The second comparison succeeds because it asks one heap-object instance of Int32 whether it's equal to the other. Because that instance knows what it means to be equal to another distinct instance, it can answer true.

supercat
  • 77,689
  • 9
  • 166
  • 211
  • Additionally, every time you write a literal `0` in your code I assume it makes an int object in the heap for that. It's not a unique reference to one global static zero value (like how they made String.Empty to avoid making new empty string objects just for initializing new strings) So I'm fairly sure that even doing a `0.ReferenceEquals(0)` will return false, since both 0s are newly created `Int32` objects. – Nyerguds Dec 18 '13 at 08:11
  • 1
    @Nyerguds, I am pretty sure everything you said is incorrect, about the ints, heap, history, global statics, etc. `0.ReferenceEquals(0)` will fail because you are trying to call a method on a compile time constant. there is no object to hang it off. An unboxed int is a struct, stored on the stack. Even `int i = 0; i.ReferenceEquals(...)` will not work. Because `System.Int32` does NOT inherit from `Object`. – Andrew Dec 18 '13 at 08:53
  • @AndrewBacker, `System.Int32` is a `struct`, a `struct` is `System.ValueType`, which itself inherits `System.Object`. Thatswhy there is a `ToString()` method and an `Equals` method for `System.Int32` – Sebastian Dec 18 '13 at 09:05
  • 1
    Nevertheless, Nyerguds is wrong to state that an Int32 will be created on the heap, which is not the case. – Sebastian Dec 18 '13 at 09:06
  • @SebastianGodelet, I am kind of ignoring the internals in that. System.Int32 itself implements those methods. GetType() is extern on `Object`, and thats where I stopped worrying about it a long time ago. It was never necessary to go farther. AFAIK the CLR handles the two types differently, and specially. It's not just inheritance. It `is` one of two types of data though. I just didn't want anyone to read that comment and get so far off track, including that strangeness about empty strings which ignores string interning. – Andrew Dec 18 '13 at 09:20
  • Clearer explanation than I can give. I get it, but I can't communicate e for snot: http://stackoverflow.com/questions/12921777/how-do-value-types-in-net-actually-work – Andrew Dec 18 '13 at 09:26
  • @SebastianGodelet @AndrewBacker Oh, I see, `ReferenceEquals` is not a function ON an Object, it's a static function OF 'Object'. In the right syntax, `Object.ReferenceEquals(0, 0)` indeed returns false, though that might just be because int is a struct, meaning it gets copied for parameters anyway, so it makes a new object every single time. – Nyerguds Dec 18 '13 at 09:46
  • @SebastianGodelet yeah, I messed that up. Didn't know that was a specific difference between structs and classes. I assumed that since in .NET even primitive types are really objects, they'd all be heaped. – Nyerguds Dec 18 '13 at 09:52
  • A really comprehensive answer from Erric Lippert is here: http://stackoverflow.com/questions/2559271/are-these-objectss-references-on-the-stack-or-on-the-heap/2561622#2561622 – Sebastian Dec 18 '13 at 10:18
  • @SebastianGodelet: There exists a heap-object type `System.Int32` which inherits from `System.Object`. *A storage location of type `System.Int32`, however, doesn't hold one of those*. I consider it unfortunate that .NET languages try to pretend that the thing held in a `System.Int32` storage location derives from `System.Object`, rather than recognizing that it isn't an object but can be converted to one. The CLI spec which states how the managed world actually works is "reality"; a language spec which describes things inconsistently from the CLI spec is wrong. – supercat Dec 18 '13 at 17:24
3

Both checks are different. The first one checks for identity, the second one for equality. In general two terms are identical, if they are refering to the same object. This implies that they are equal. Two terms are equal, if their values is the same.

In terms of programming identity is usually messured by reference equality. If the pointer to both terms are equal (!), the object they are pointing at is exactly the same one. However, if the pointers are different, the value of the objects they are pointing at can still be equal. In C# identity can be checked using the static Object.ReferenceEquals member, whilst equality is checked using the non-static Object.Equals member. Since you are casting two integers to objects (which is called "boxing", btw), the operatior == of object performs the first check, which is by default mapped to Object.ReferenceEquals and checks for identity. If you explicity call the non-static Equals-member, dynamic dispatch results in a call to Int32.Equals, which checks for equality.

Both concepts are similar, but not the same. They may seem confusing for first, but the small difference is very important! Imagine two persons, namely "Alice" and "Bob". They both are living in a yellow house. Based on the assumption, that Alice and Bob are living in a district, where houses are only differ in their color, they could both live in different yellow houses. If you compare both homes you will recognize, that they are absolutely the same, because they are both yellow! However, they are not sharing the same home and thus their houses are equal, but not identical. Identity would imply that they are living in the same house.

Note: some languages are defining the === operator to check for identity.

Carsten
  • 11,287
  • 7
  • 39
  • 62