0

Consider this code:

object str1 = "shahrooz";
object str2 = "shahrooz";
object int1 = 1;
object int2 = 1;

Console.WriteLine("str1 == str2 : " + (str1 == str2));
Console.WriteLine("int1 == int2 : " + (int1 == int2));
Console.ReadLine();

When you run this code you will get this result:

str1 == str2 : True
int1 == int2 : False

Both compare are object but why the first comparison returns true but second comparison returns false?

John Odom
  • 1,189
  • 2
  • 20
  • 35
  • 2
    This is a guess, but strings get interned so likely that's what's happening to those object references compared to the Int32 objects being created for the numbers and you're doing a direct reference comparison. Hopefully someone can confirm this in a concrete answer? – Lloyd Dec 14 '15 at 17:56
  • 1
    You should read about boxing and unboxing. Integers are value-types and making them objects causes them to be wrapped internally to act like reference-types. So integers are compared by reference. – Red Dec 14 '15 at 18:00

6 Answers6

5

The == operator performs a reference equality check if both operands are of type object or no more specific equality operator can be determined based on the compile-time types of the operands.

In your case the compile-time operand types are object, so reference equality is checked. At the time when you assigned 1 to either variable, the runtime "boxed" the value into an object. As a result you get two objects that contain the integer value one, but they are two separate objects, thus reference equality returns false.

To perform value equality comparison, use the Equals() method. It can be overridden in subclasses to perform type-specific value comparison, which I believe is what you want to do. The expression int1.Equals(int2) should return what you want. Since both variables are nullable, you might want to write it as int1 != null ? int1.Equals(int2) : int2 == null to prevent throwing NullReferenceException when int1 is null.

Wormbo
  • 4,978
  • 2
  • 21
  • 41
  • 1
    As a note to your last paragraph, you can use the static method [`Object.Equals(int1, int2)`](https://msdn.microsoft.com/en-us/library/w4hkze5k(v=vs.110).aspx), that static method will do proper unboxing and null checking and gives the same logical result as `int1 != null ? int1.Equals(int2) : int2 == null` (Well actually it does a `Object.RefrenceEquals(int1, int2)` and a `Object.RefrenceEquals(int1, null) || Object.RefrenceEquals(int2, null)` before the `int1.Equals(int2)` so the logic is slightly different) – Scott Chamberlain Dec 14 '15 at 19:19
4

Doing (int1 == int2) compares 2 objects, which are different, due to the CLR's boxing: when you put a ValueType inside a ReferenceType, .NET does boxing for you, so you get 2 different objects, each boxes the 1 value that you assigned.

To make the comparison work, you need to either store the 2 numbers in ints,
or do ((int)int1 == (int)int2)

For info about boxing, see this article:
Boxing and Unboxing (C# Programming Guide)
https://msdn.microsoft.com/en-us/library/yz2be5wk.aspx

spaceman
  • 1,061
  • 1
  • 11
  • 31
  • This code: `((int)int1 == (int)int2)` does not "store" the numbers in ints. It **casts** them as ints, which is entirely different. – Alan McBee Dec 14 '15 at 18:07
  • Hi Alan. I wrote 2 options: The first option: store the 2 numbers in **int**s. And a second option: do `((int)int1 == (int)int2)`. You merged the two options for some reason. – spaceman Dec 14 '15 at 18:08
4

A quick and dirty check of memory addressing helps to shine light on this using this (grabbed from here):

public static IntPtr GetAddress(object o)
{
    unsafe
    {
        TypedReference tr = __makeref(o);
        IntPtr ptr = **(IntPtr**)(&tr);
        return ptr;
    }
}

You can then do this:

Console.WriteLine(GetAddress(str1));
Console.WriteLine(GetAddress(str2)); 
Console.WriteLine(GetAddress(int1)); 
Console.WriteLine(GetAddress(int2));

I get:

35839968
35839968
35840128
35840152

What you should see is that the address of str1 and str2 are identical, and the address of int1 and int2 are different. This is because your strings are interned (because they are string literals). A single copy of the string shahrooz is stored in memory and both str1 and str2 are referring to it. Your int1 and int2 on the other hand are being boxed which is taking the value type int and placing it inside an object. Each int (or any other value type) boxed gets put in its own box with its own address.

Now the default behavior of == when given two reference types (like object) is to just compare the memory address (to see if they refer to the same object), and this is what's happening. In the case of the strings, it's the same object. In the case of the ints, it isn't.

As others have suggested, using Equals will give the desired result because Object.Equals is virtual and can be overridden, hence calling the appropriate int.Equals when given int and string.Equals when given a string.

Community
  • 1
  • 1
Matt Burland
  • 44,552
  • 18
  • 99
  • 171
2

From https://msdn.microsoft.com/en-us/library/53k8ybth.aspx?f=255&MSPPError=-2147217396

For predefined value types, the equality operator (==) returns true if the values of its operands are equal, false otherwise. For reference types other than string, == returns true if its two operands refer to the same object. For the string type, == compares the values of the strings.

First time I misunderstood this quote, what it means actually is that there are 2 different operators in this case, one for object, and one for string.

The object operator compares the instance reference, and string operator compares the value of both strings.

In this case

Console.WriteLine(str1 == str2); // True, becuase of compiler optimizations.
// you can test it with:
Console.WriteLine(object.ReferenceEquals(str1, str2)); // this is true

But if you do

object s1 = "shahrooz";
object s2 = "shah";
s2 += "rooz";

Console.WriteLine(s1 == s2);
//You get false, becuase compiler now can't optimize this.

Said that

int1 == int2 // False, because they reference a different instance
int1 == int1 // True, same instance.
bto.rdz
  • 6,636
  • 4
  • 35
  • 52
  • This is still not right, because he is compareing `==` on object and `==` is not virtual. Try `object str2 = "shah" + Console.ReadLine();` and type in `rooz`, you get false. – Scott Chamberlain Dec 14 '15 at 18:09
  • @ScottChamberlain if this is wrong then why is it documented in msd.microsoft?, I have not tested what you say but this is what microsoft says – bto.rdz Dec 14 '15 at 18:14
  • @ScottChamberlain has the most correct answer. The types being referenced by the object variables are immaterial. Only the actual memory addresses being allocated are being compared. The only reason that the string references are reporting as equal is due to the compiler optimization. – Alan McBee Dec 14 '15 at 18:15
  • Because you are misunderstanding the documentation. The quote you posted only applies if the two side types is of type `string`, if one is of type `object` you get the object `==` operator. – Scott Chamberlain Dec 14 '15 at 18:15
  • @AlanMcBee after some tests you both were right I updated – bto.rdz Dec 14 '15 at 18:42
2

It is because operators can't be virtual, because of that you are calling the == operator on Object.

The == operator for object compares references. For structs (like int) when cast to a object it will "box" the struct in to a class reference. The code

object int1 = 1;
object int2 = 1;

causes two boxes to be made so when you call == on the box it sees the other side is not the same box so it returns false.

Strings are already reference types so they don't get a extra box made when they are put in to a object. However the compiler treats them special, in the following code

object str1 = "shahrooz";
object str2 = "shahrooz";

The compiler only makes one shahrooz string in memory and assigns it to both str1 and str2. This is why it returns true when you compare them.

If you did the code

public static void Main()
{
    object str1 = "shahrooz";
    object str2 = "shah" + Console.ReadLine();

    Console.WriteLine(str1 == str2);
    Console.ReadLine();
}

and typed in rooz you would get false as your result because you now have two different string references even though they have the same string content.

use the method Equals instead of ==, that does check if the Equals method was overloaded on derived classes and will unbox structs to compare them.

Scott Chamberlain
  • 124,994
  • 33
  • 282
  • 431
0

It is because string is immutable and they point to same references from string intern pool and its not true for any other value type or reference types as they create new references every time you initialize a new object.

To prove above we check references of two strings initialized separately:

object str1 = "shahrooz";
object str2 = "shahrooz";

Console.WriteLine(object.ReferenceEquals(str1, str2)); //True
Console.WriteLine(object.ReferenceEquals(int1, int2)); //False

Which proves that same strings hold same reference but other objects don't. Its not true for all the reference types but strings are treated as special classes.

On the other hand for any other reference types:

Test a = new Test { Num=1, Str="shahrooz"};
Test b = new Test { Num=1, Str="shahrooz"};

a.Equals(b); //False  
object.ReferenceEquals(a,b);  //False

But if you do,

Test c = a;
a.Equals(c); //True 
object.ReferenceEquals(a,c) //True.
Marshal
  • 6,551
  • 13
  • 55
  • 91
  • 4
    THIS IS NOT A GOOD ANSWER. Notice that the OP is not comparing integers. It's not clear from reading the OP's question that it's actually comparing "references to integers" -- it looks like it is actually comparing integers that happen to be boxed. The "integer" comparison is actually comparing two objects, which happened to be named "int1" and "int2". – Alan McBee Dec 14 '15 at 17:59
  • Any object which is not string, be it integer or object, they will hold different references. But strings return the references from string intern pool which tend to be exactly same all the time for same string. – Marshal Dec 14 '15 at 18:01
  • It doesn't matter that the references are the same for strings, because they overload the Equals method. – Alan McBee Dec 14 '15 at 18:02
  • @AlanMcBee Actually, that is a compiler optimization. Do `"shah" + Console.ReadLine()` and type in `rooz` and you get false. – Scott Chamberlain Dec 14 '15 at 18:07
  • 2
    @AlanMcBeeL It isn't true. Its false! Anyways I don't get your point. – Marshal Dec 14 '15 at 18:08
  • Okay -- got carried away. You're right. Too late to delete my comment. – Alan McBee Dec 14 '15 at 18:08
  • @AlanMcBee: Check the updated answer. Wait for your reply on that. Don't be too hasty to judge. – Marshal Dec 14 '15 at 18:21
  • @AlanMcBee The compile time type of the expressions is `object`, so no, it won't use the overloaded `==` operator. – Servy Dec 14 '15 at 18:23
  • 1
    Yeah. I replied too quickly. I do know better. – Alan McBee Dec 14 '15 at 18:28