views:

105

answers:

2

I got hit by a strange "asymmetry" in C# that I do not really understand. See the following code:

using System;
using System.Diagnostics;
namespace EqualsExperiment
{
    class Program
    {
        static void Main(string[] args)
        {
            object apple = "apple";
            object orange = string.Format("{0}{1}", "ap", "ple");
            Console.WriteLine("1");
            Debug.Assert(apple.Equals(orange));
            Console.WriteLine("2");
            Debug.Assert(apple == orange);
            Console.WriteLine("3");
        }
    }
}

It might be obvious for all you .NET gurus, but the 2nd assert fails.

In Java I have learnt that == is a synonym for something called Object.ReferenceEquals here. In C#, I thought that Object.operator== uses Object.Equals, which is virtual, so it is overriden in the System.String class.

Can someone explain, why does the 2nd assert fail in C#? Which of my assumptions are bad?

+6  A: 

Operators are defined as static methods, so they can't participate in polymorphism. So your second assert uses the definition of == for object (since your variables are declared as object), which only tests reference equality. If the variables were declared as string, the == overload for string would have been used, and the second assert would have succeeded.

Thomas Levesque
Also worth noting, for completeness, that the static `object.Equals(apple,orange)` would return `true` in this case. `object.Equals` first uses `==` to check for ref equality, and if that fails it will use the overloaded `apple.Equals(orange)` (assuming that `apple` and `orange` aren't `null`).
LukeH
I understand that I use `static bool Object.operator==(Object, Object)`. But I still do not understand, why does not that call `Object.Equals(Object)`. Since that is virtual, the correct `String.Equals(Object)` would be called in the end.
wigy
+6  A: 

The == operator is not a synonym, it's an operator that is defined for different types.

The == operator is defined for strings, and then it does actually use the Equals method:

public static bool operator ==(string a, string b) {
  return Equals(a, b);
}

However, in your code you are not using the operator on strings, you are using it on objects, so what you get is the == operator defined for objects, which uses ReferenceEquals to do the comparison.

Which overload of the operator to use is decided at compile time, so it's the type of the variables that decide the overload, not the actual type of the objects that the variables point to.

Guffa
Hmmm. So you say that Object.operator== is defined with ReferenceEquals, whereas String.operator== is defined with Equals. Is not that nice and intuitive?
wigy
@wigy: Most of the time it works very well, but there are of course situations like your example where you might expect the comparison to be determined at runtime. It's certainly more intuitive than for example in C++ and Java, where you can't reliably use the == operator on strings at all. In VB the = operator determines the comparison at runtime, which is more in line with how VB works, but that gives several other situations where it can surprise you. In C# it's possible to determine from the code exactly what comparison will be used, which is more in line with how C# works in general.
Guffa
Thanks Guffa. I know it is better than C, Java or VB. C++ operators might be virtual, so you have different problems there. That is why most C++ coding standards use Yoda-conditions like `if (5 == something) {...}` From your answer I understood, that the framework designers thought ReferenceEquals is more intuitive for some of the coders, but fixed string.operator== on the other hand.
wigy
@Guffa: Wouldn't string-interning make it possible that == would also return `true`? Any thoughts?
Ani
@Ani: Yes, if you compare two interned strings that have the same value, they will point to the same string object. In the OP's example the `orange` string is deliberately created at runtime so that it's not interned.
Guffa