The "C# 4.0 IN A NUTSHELL" 4th edition book by the Albaharis states on page 249: ". . . calling object.ReferenceEquals guarantees normal referential equality."
So, I decided to test this out.
First I tried value types like this.
int aa1 = 5;
int aa2 = aa1;
MessageBox.Show("object.ReferenceEquals(aa1,aa2) is: " + object.ReferenceEquals(aa1, aa2));
And just as I expected, the result was false: object.ReferenceEquals(aa1, aa2) is: False
Life was good. Then I tried a mutable reference type like this.
System.Text.StringBuilder sblr1 = new System.Text.StringBuilder();
sblr1.Append("aaa");
System.Text.StringBuilder sblr2 = sblr1;
MessageBox.Show("object.ReferenceEquals(sblr1,sblr2) is: " + object.ReferenceEquals(sblr1, sblr2));
And just as I expected, the result was true object.ReferenceEquals(sblr1, sblr2) is: True
Life was still good. Then I figured that since it is a mutable reference type, then if I change one variable to null, then both should be null. So I tried the following.
System.Text.StringBuilder sblr1 = new System.Text.StringBuilder();
sblr1.Append("aaa");
System.Text.StringBuilder sblr2 = sblr1;
sblr1 = null;
MessageBox.Show("object.ReferenceEquals(sblr1,sblr2) is: " + object.ReferenceEquals(sblr1, sblr2));
And I expected them to both be null. But the result I got was False: object.ReferenceEquals(sblr1, sblr2) is: False
Now life was not so good. I thought that if it overrode the memory location of sblr1, then it would be overriding the memory location of sblr2 also.
Then I thought that maybe they were pointing to two different nulls, so I tried this:
System.Text.StringBuilder sblr1 = new System.Text.StringBuilder();
sblr1.Append("aaa");
System.Text.StringBuilder sblr2 = sblr1;
sblr2 = null;
MessageBox.Show("sblr1 == " + sblr1 + " and sblr2 == " + sblr2);
But here, only one was pointing to a null like this. sblr1 == aaa and sblr2 ==
Only one was null.
It was displaying the behavior I'd expect from an immutable reference type like a string object. With a string object, I can do something like this:
string aa1 = "aaX";
string aa2 = "aaX";
MessageBox.Show("object.ReferenceEquals(aa1,aa2) is: " + object.ReferenceEquals(aa1, aa2));
And they will both reference the same thing like this. object.ReferenceEquals(aa1, aa2) is: True because "aaX" only gets written to the assembly once.
But if I do this:
string aa1 = "aaX";
string aa2 = "aaX";
aa1 = null;
MessageBox.Show("After aa1 is null(" + aa1 + "), then aa2 is: " + aa2);
Then they point to different things like this: After aa1 is null (), then aa2 is: aaX
That's because string objects are immutable. The memory location doesn't get overriden. Rather, the variable points to a different location in Heap memory where the new value exists. Changing aa1 to null in the above example means that aa1 will point to a different location on the Heap memory.
Why then is the mutable reference type behaving just the same as the immutable reference type?
Edit 4:03PM and 4:08
I've recently tried this:
System.Text.StringBuilder sblr1 = new System.Text.StringBuilder();
sblr1.Append("aaa");
// sblr1 and sblr2 should now both point to the same location on the Heap that has "aaa".
System.Text.StringBuilder sblr2 = sblr1;
System.Text.StringBuilder sblr3 = new System.Text.StringBuilder();
sblr3.Append("bbb");
sblr1 = sblr3;
MessageBox.Show("sblr1 == " + sblr1 + " and sblr2 == " + sblr2 + " and sblr3 == " + sblr3);
Which gave me: sblr1 == bbb and sblr2 == aaa and sblr3 == bbb
That's more like the result I was expecting. I see now, thanks to the comments, that I abscent mindedly expected null to act like a memory location.
I thought that if it overrode the memory location of sblr1, then it would be overriding the memory location of sblr2 also.
This is your misunderstanding.
When you write this:
System.Text.StringBuilder sblr2 = sblr1;
You're assigning the sblr2
variable to be a reference to the same instance of StringBuilder
as the one pointed to by sblr1
. The two variables now point to the same reference.
You then write:
sblr1 = null;
This changes the sblr1
variable to now be a null reference. You didn't change the instance in memory at all.
This has nothing to do with whether the reference is a mutable type or not. You're changing the variables, not the instance which they are referencing.
As for your string example:
That's because string objects are immutable. The memory location doesn't get overridden
This actually is not true. The fact that you're setting one string variable to null
doesn't really have anything to do with the string being immutable. That's a separate concern.
Why then is the mutable reference type behaving just the same as the immutable reference type?
The behavior you're seeing has nothing to do with mutability. It is the standard behavior for all reference types (whether immutable or mutable). Mutability is a different issue.
The main issue with mutability is this:
Suppose you have a class, like so:
class Foo
{
public int Bar { get; set; }
}
If you write this:
Foo a = new Foo();
a.Bar = 42;
Foo b = a;
b.Bar = 54;
Console.WriteLine(a.Bar); // Will print 54, since you've changed the same mutable object
With immutable types, this can't happen, since you can't change Bar
- instead, if you make an immutable class:
class Baz
{
public Baz(int bar) { this.Bar = bar; }
public int Bar { get; private set; }
}
You would need to write:
Baz a = new Baz(42);
Baz b = a;
// This isn't legal now:
// b.Bar = 54;
// So you'd write:
b = new Baz(54); // Creates a new reference
Alternatively, you could make the class return a new reference on a "change" operation, ie:
class Baz
{
public Baz(int bar) { this.Bar = bar; }
public int Bar { get; private set; }
public Baz Alter(int newValue) { return new Baz(newValue); } // May copy other data from "this"
}
Then when you'd write:
Baz a = new Baz(42);
Baz b = a.Alter(54); // b is now a new instance
This is what happens with string
- all of the methods return a new instance, since a string is immutable, so you can never "change" the existing copy.