The reason why this:
object o1 = 5;
object o2 = 5;
bool b = ReferenceEquals(o1, o2);
does not produce the same result as this:
object o1 = "5";
object o2 = "5";
bool b = ReferenceEquals(o1, o2);
is because in the first case, a pseudo-code-ish version of C# would look like this:
object o1 = box(5);
object o2 = box(5);
bool b = ReferenceEquals(o1, o2);
The two box operations will box the value 5 into two separate objects. One could imagine that in a world where CPU speed was infinite, but memory was not abundant, one might want to scan memory to see if we have an existing object that is a boxed int
5, and thus just point to that instance, and thus o1
and o2
would be the same instance.
However, that does not exist, so in the first case, o1
and o2
will be two references to two separate objects in memory, both containing a boxed int
5.
In the second piece of code, however, some "magic" makes it behave different from the first piece, because the JITter will "intern" strings. Basically, the two strings "5"
used in the code will be just 1 string in memory. That means that in this case, o1
and o2
will in fact contain the same reference.
basically:
object o1 = string.Intern("5");
object o2 = string.Intern("5");
bool b = ReferenceEquals(o1, o2);
Note that this is not the same (hypothetical) case as the above with the infinite CPU. Basically, the JITter will build up an internal data structure of strings it discovers during JITting, and instead of creating an object for two separate (constant) strings that have the same content, it looks it up in the internal data structure, and that's why the two string literals in the code will be a reference to the same single object in memory at runtime.
My naive hypothetical implementation of the intern method could be this:
string original;
if (internDictionary.TryGetValue(newString, out original))
return original;
internDictionary.Add(newString, newString);
return newString;
So to answer your question: ==
on variables of type object
will in turn use ReferenceEquals
, couple that with the above information, and that's why the two pieces of code behaves different.
You can test the following piece of code in LINQPad:
void Main()
{
object o1 = 5;
object o2 = 5;
(o1 == o2).Dump("box(5) == box(5)");
o1.Equals(o2).Dump("box(5).Equals(box(5))");
object o3 = "5";
object o4 = "5";
(o3 == o4).Dump("\"5\" == \"5\"");
o3.Equals(o4).Dump("\"5\".Equals(\"5\")");
object o5 = "5";
object o6 = "15".Substring(1); // "5"
(o5 == o6).Dump("\"5\" == \"15\".Substring(1)");
o5.Equals(o6).Dump("\"5\".Equals(\"15\".Substring(1))");
o6 = string.Intern((string)o6);
(o5 == o6).Dump("\"5\" == \"15\".Substring(1) [interned]");
o5.Equals(o6).Dump("\"5\".Equals(\"15\".Substring(1)) [interned]");
}
As you can see, the act of interning the string returned from substring
returns the same reference that o5
already contains (next to last result).
