views:

173

answers:

7

I was wondering if any one could explain this code to me...

public void SomeMethod()
{
    StringBuilder sb = new StringBuilder();
    AppendFoo(sb);
    String foo = sb.ToString(); // foo is "foo"

    String s = String.Empty;
    AppendBar(s);
    String bar = s; // bar is empty 
}

public void AppendFoo(StringBuilder x)
{
    x.Append("Foo");
}

public void AppendBar(String x)
{
    x = x + "Bar";
}

If both StringBuilder and String are reference types, why is the string object not altered when passing it through the AppendBar method, whereas the StringBuilder object is altered when passing it into the AppendFoo method, as the both parameters to the methods are taking reference types as parameters?

+2  A: 

Strings are immutable - once created they cannot be changed.

What happens in appendBar is:

x= x+"Bar"

Creates a new string (with the new value) and sets the reference x to it. (Actual implementation is compiler dependent)

But the reference s in the calling code still points to the original string object.

chris
I get that string is immutable - the fact that it behaves differently is where am confused. Is this something that was decided during the design phase?
Chalkey
What do you mean when you say it behaves differently? Behaves differently from what?
Jason
+1  A: 

In the first call, AppendFoo, you alter the argument, by calling a method member of it.

In the 2nd call, AppendBar, you assign new value to the argument. It does not reflect the original argument (unless you call it by ref)

For example, if the first call would have been:

public void AppendFoo(StringBuilder x)
{
    x = new StringBuilder();
    x.Append("Foo");
}

You would have gotten the same result.

It is tempting to think it's related to the fact strings are immutable but it has nothing to do with it. It's just an assignment of new value to argument and therefore it does not change the original object sent to the method.

Elisha
+1  A: 

Strings are reference types, but in C# they are immutable, meaning once assigned, they never change their contents. All you are doing when you say

x = x + "Bar";

is 'create a new string containing x+"Bar" and assign it to the local reference of x. Because you didn't pass the string is as a 'ref' parameter, assigning to the local reference doesn't affect the reference outside the function.

Jim Lynn
I get that string is immutable, but why is it a reference type, why not a value like int, float etc..?
Chalkey
String is reference type because, some string are "big", the can have a tousands of characters. So for every pass of string parameter should copy whole string. It would by very expensive for system resources in some scenarios (web applications for example - because they handle a lot of string for HTML pages).
TcKs
It's not a value type because if it were there would be serious performance issues. For example, if it were a value type, the content of a `string` would have to be copied every time it were passed to a method or returned from a method. So calling a function accepting a `string` parameter would be `O(n)` where `n` is the length of the `string`; ditto for returning a `string` from a method.
Jason
+1  A: 

http://msdn.microsoft.com/en-us/library/system.string.aspx

A String object is called immutable (read-only) because its value cannot be modified once it has been created. Methods that appear to modify a String object actually return a new String object that contains the modification. If it is necessary to modify the actual contents of a string-like object, use the System.Text.StringBuilder class.

Zim
+9  A: 

Ignore the fact that strings are immutable for the moment - it's a bit of a red herring. The important point is the difference between:

x.Append(...);

and

x = x + ...;

Look at them closely: the first one is acting on the object that x refers to, changing the contents of the StringBuilder. The second is changing the value of x to refer to a different object (a new string). It's not changing the contents of the existing object. (In fact it couldn't because strings are immutable, but the same logic would apply anyway.)

Changing the value of x within a method doesn't change the value of the argument used to initialize x.

The crucial point is to differentiate between changing the value of a variable and changing the contents of the object it refers to. Once you've got that difference, the rest should fall into place.

Read more about this and parameters in my article on parameter passing, and more about reference types vs value types in another article.

Jon Skeet
Cheers, the most coherent answer. So basically if string had a method called append then it would work as the StringBuilder? Also, how is the immutablilty of the string class enforced? Is this something hardcoded into the .NET framework, or is it an attribute or something?
Chalkey
@Chalkey: The immutability comes out of it not having any methods which mutate its state. (In fact, it *is* mutable if your code is running in mscorlib - StringBuilder actually mutates a string internally!) But yes, if String had any methods which *did* mutate its state, it would work the same way as StringBuilder.
Jon Skeet
@JonSkeet: nice one for the answer. People tend to answer this with 'strings are immutable!' then leave it there - your articles put things into perspective!
Chalkey
A: 

It doesn't matter that string are immutable at all, you are missing a ref parameter in the AppendBar function.

public void AppendBar(ref String x)
{
    x = x + "Bar";
}

The logic is the same, if your AppendFoo was

public void AppendFoo(StringBuilder x)
{
    x = new StringBuilder();
    x.Append("Foo");
}

it would not work either... if you want to change the value of the reference, you need to declare the x var as ref.

Jorge Córdoba
The confusion was with the reference types being passed by value - not reference types being passed by reference!
Chalkey
A: 

.NET string is actually an immutable data type, which means that once you initialize a string object, that string object can never change. An operators that looks like modifying the contents of a string, actually creates new string, then updates the address stored in the variable to that of the newly created string. The old string becomes unreferenced.

So in the case of passing a string to a function, your AppendBar(string x) for example, what happens is that when you do x = x + "Bar", the .NET runtime allocates enough memory to store the combined text. The original text which is empty in your sample and "Bar" are copied into the new string instance. x is updated to point to the address of the new string instance. However, since s is passed to the AppendBar(string x) as a copy, the value of s in your caller method is not updated to point to the new string instance. It's still storing the address of the old one, the empty string one. Therefore, after calling the function, your referencing of s still yields the empty string.

You can change the signature of the function to AppendBar(ref string x) to make the string passed to function altered. After the statement AppendBar(ref s);, s "is" then "Bar".

mqbt