System.String: It doesn't seem to act like a reference type.

  • Thread starter Thread starter Chris Simmons
  • Start date Start date
C

Chris Simmons

I know that a String is immutable, but I don't understand why this
piece of code fails in nUnit:

// BEGIN CODE

using System;

class Test
{
public static void Main( String[] args )
{

String testField;
testField = "Old Value";

changeString( testField );

Console.WriteLine( testField );

}


private static void changeString( String field )
{

field = "New Value";

}


}
// END CODE

produces the output:
Old Value

As I understand String, I though the output should have been:
New Value

I was under the understanding that Strings, although immutable, were
still reference objects and would be passed by reference to a method.
Can someone tell me how I'm thinking incorrectly?
 
Chris Simmons said:
I know that a String is immutable, but I don't understand why this
piece of code fails in nUnit:

// BEGIN CODE

using System;

class Test
{
public static void Main( String[] args )
{

String testField;
testField = "Old Value";

changeString( testField );

Console.WriteLine( testField );

}


private static void changeString( String field )
{

field = "New Value";

}


}
// END CODE

produces the output:
Old Value

As I understand String, I though the output should have been:
New Value

I was under the understanding that Strings, although immutable, were
still reference objects and would be passed by reference to a method.
Can someone tell me how I'm thinking incorrectly?

because reference types are NOT passed in by reference. in .NET, pass
something by reference means you declare your parameter with the ref keyword
(or out). in your example, a reference is passed in (rather than a copy),
but you can't change where that reference points to, which is what you are
trying to do with assignment (because strings are immutable).
 
Chris Simmons said:
I know that a String is immutable, but I don't understand why this
piece of code fails in nUnit:

// BEGIN CODE

using System;

class Test
{
public static void Main( String[] args )
{
String testField;
testField = "Old Value";

changeString( testField );

Console.WriteLine( testField );
}


private static void changeString( String field )
{
field = "New Value";
}


}
// END CODE

produces the output:
Old Value

As I understand String, I though the output should have been:
New Value

I was under the understanding that Strings, although immutable, were
still reference objects and would be passed by reference to a method.
Can someone tell me how I'm thinking incorrectly?


The argument to changeString was passed by reference.
It locally changed the reference to refer to another String.
That is why the change did not take. If you instead write:
private static void changeString( ref String field )
{
field = "New Value";
}
and change the call to read:
changeString( ref testField );
then you will see what you expected. The difference is that
you are passing a reference to the object named "testField",
allowing it to be changed. Your previous code had no effect
on that object.
 
Larry Brasfield said:
The argument to changeString was passed by reference.

No it wasn't. It was passed by value. The argument was a reference in
itself, but it was passed by value.
It locally changed the reference to refer to another String.
That is why the change did not take. If you instead write:
private static void changeString( ref String field )
{
field = "New Value";
}
and change the call to read:
changeString( ref testField );
then you will see what you expected. The difference is that
you are passing a reference to the object named "testField",
allowing it to be changed. Your previous code had no effect
on that object.

No, the difference is that you are passing the reference *by*
reference. The object itself has no name, and its data cannot be
changed.

See http://www.pobox.com/~skeet/csharp/parameters.html
 
I was under the understanding that Strings, although immutable, were
still reference objects and would be passed by reference to a method.
Can someone tell me how I'm thinking incorrectly?

It's unfortunate that people talk about things being passed "by
reference" when they don't really mean it - they mean references being
passed by value.

Hopefully you'll find the following page clears things up:
http://www.pobox.com/~skeet/csharp/parameters.html
 
A couple of problems here.

First, because Strings are immutable, changing the value of a string
doesn't really change the string _in situ_ in memory. Instead, it
creates a whole new string and points the String reference to it.

When you say:

String a = "Old Value";
a = "New Value";

you haven't really changed what is at the memory location where the
string "Old Value" lives. It's still there, if only for a short time
before the garbage collector comes around to sweep it up. What has
happened is that a whole new object in memory, a String instance, has
been created with the value "New Value". The variable a, which contains
a pointer (or a _reference_) to a string, is then changed to refer to
the new String instance (with the value "New Value") instead of the old
one (with the value "Old Value").

Now let's look at what happens when two String variables are involved:

String a = "Old Value";
String b = a;
a = "New Value";

After the first line executes, there is a new String instance in the
heap with the value "Old Value", and the variable "a" contains a
reference to the memory location on the heap where that String instance
is stored.

The second line just copies the reference to the String instance with
the value "Old Value" from "a" to "b". Now both String variables have
the same value, but be careful what I mean by "value": their value is
_not_ a String "Old Value". Their value is a _reference_ or a pointer
to a memory location at which is found a String with the value "Old
Value".

The third line instantiates a new String instance in the heap and gives
it the value "New Value". It then changes the String variable "a" to
contain a reference to this new String instance. Notice that nothing
has happened to the variable "b" in the third line of code: "b" is
still a reference to the first instance of String, which has the value
"Old Value". "a" and "b" now point to different memory locations.

This is _exactly_ what happens with reference types:

ArrayList x = new ArrayList();
ArrayList y = x;
x = new ArrayList();
x.Add("Hello world");

The first line of code creates and empty array list and points "x" to
it. The second line of code points "x" and "y" to the same array list.
The third line of code points "x" to a new array list that is distinct
from the first one. The last line modifies the second array list to
contain one item: a reference to a string with the value "Hello world".
The array list pointed to by "y" will still be the original, empty one,
so x.Count will be 1, but y.Count will be 0.

The different with the String type is that you don't see that "new"
operation taking place. It happens behind the scenes, which can be
confusing.

On top of this, there is one _true_ difference between Strings and most
other reference types that _does_ make Strings look like value types:
comparing two Strings with the "==" operator compares their _values_,
not their addresses (references), whereas comparing two object
references with "==" usually compares addresses, not values.

The second bit of confusion in your question relates to the fact that
objects are not, in fact, _passed by reference_ to methods. Instead,
object references are passed by value, which is exactly what happens
with String. To understand the difference, read Jon Skeet's excellent
page on parameter passing in C#:

http://www.yoda.arachsys.com/csharp/parameters.html

Suffice to say that your question would have been more interesting if
in your method changeString() you had said:

field.Replace("l", "i");

and asked why the resulting output was not "Oid Vaiue" instead of "Old
Value". The problem with your original example is that setting "field"
to refer to a new object would _never_ change testField back in Main,
regardless of what kind of object "field" was declared to be. The only
way to make that happen is to do this:

changeString(ref testField);
....
private static void changeString(ref String field) ...

You will then see that yes, in fact testField changes its value in Main
after changeString completes.
 
Many thanks to all of you. I see the error in my understanding (or
lack thereof) now.
 
Back
Top