2

Lets imagine we have code:

void Main()
{
    object a = new object();
    a = 5;
    object b = new object();
    b = 5;
    Console.WriteLine (a == b);       // False
    Console.WriteLine(a.Equals(b));   // True

    Console.WriteLine();

    object s1 = new object();
    s1 = "5";
    object s2 = new object();
    s2 = "5";
    Console.WriteLine(s1 == s2);       // True
    Console.WriteLine(s1.Equals(s2));  // True
}

The question is: Why this behaviour is different and relies of type stored in object.

EDIT

To clarify the problem, and why it is not a dupplicated question. The main problem is on == behavior that produces different responses in this cases: See @LesseV.Karlsen comment.

Dariusz
  • 15,573
  • 9
  • 52
  • 68
  • 1
    probably dublication http://stackoverflow.com/questions/112625/vs-object-equalsobject-in-net – Jevgenij Nekrasov Dec 13 '13 at 12:06
  • 2
    http://msdn.microsoft.com/en-us/library/ms173147.aspx – Soner Gönül Dec 13 '13 at 12:07
  • 1
    In first case object.Equals() method is called for boxed int. In second case overriden string.Equals() method is called, which compares string contents. – Yuriy Dec 13 '13 at 12:15
  • 1
    The difference here, why `==` "works" in one case and not in the other is because by assigning `s1` and `s2` both to the constant string `"5"`, they're assigned the same reference, to the string interned by the JITter. This means that in `s1` and `s2` there is the same reference, and `==` for `object` variables uses `ReferenceEquals`. Boxing the number 5 twice, however, will create two distinct objects containing the boxed number 5, thus `ReferenceEquals` will return false in that case. – Lasse V. Karlsen Dec 13 '13 at 12:35
  • To test this, you can do this: `s2 = "15".Substring(1);`. Then both strings will still contain `"5"`, but the compiler or JITter has not analyzed the code in such a detail that it infers that the result of calling `Substring` on `"15"` will in fact return `"5"`, and thus you get two different strings, both containing `"5"`. – Lasse V. Karlsen Dec 13 '13 at 12:36
  • So basically, `==` does not depend on type contained in the `object` variable at all, it always uses `ReferenceEquals`, but due to the difference between using an interned string and boxing, the result is different. – Lasse V. Karlsen Dec 13 '13 at 12:37
  • Personally I don't think the question is a duplicate, at least not of the one chosen when it was closed. The question is not really about the difference between `==` and `Equals`, but why `==` seemingly behaves different in the two cases. – Lasse V. Karlsen Dec 13 '13 at 12:39
  • Thanks @LasseV.Karlsen for clarification and understanding my question well. IMHO this question is not duplicate. Yes, its all about object comparioson methods but the problem is more internal, so dear admins don't be so quick. – Dariusz Dec 13 '13 at 12:56

2 Answers2

2

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).

result of LINQPad script

Lasse V. Karlsen
  • 380,855
  • 102
  • 628
  • 825
  • Pointing to second piece of code. Correct me if i'm wrong. So if now, I change value of object `o1 = "100"`: new space in memory is allocated, because there is no the same strings which can be mapped to the same? But if there is another object `oX` that contains `"100"`, new alocation will not me made instead of "mapping" between `o1` and `oX`? – Dariusz Dec 13 '13 at 13:59
  • That is correct, that is what "string interning" does. Constant string literals in the source code will be merged if they are identical, so that all those literals will end up referencing the same single string object in memory. This string interning happens during JITting, and only affects string literals. If you later on construct a string by piecing together smaller strings, or extracting portions of larger ones, and want to intern it by referencing an existing string, you can call `string.Intern`, but be aware that this will cache that string until you tear down that app domain. – Lasse V. Karlsen Dec 13 '13 at 17:07
1

According to MSDN:

"The type of comparison between the current instance and the obj parameter depends on whether the current instance is a reference type or a value type. If the current instance is a reference type, the Equals(Object) method tests for reference equality, and a call to the Equals(Object) method is equivalent to a call to the ReferenceEquals method. Reference equality means that the object variables that are compared refer to the same object."

"If the current instance is a value type, the Equals(Object) method tests for value equality."

For value types it checks if the two objects are of the same type and if the values of the public and private fields of the two objects are equal.

When you compare the two strings above, they both use the same memory location, whereas the a and b objects are reference type, so they will be different objects when you compare the objects, but will have same value when you compare their value, because of how Equals works.

lex87
  • 1,276
  • 1
  • 15
  • 33