3

With this code:

 void SomeMethod(string str)
 {
      string tmpStr = ... Read some value from file ... 

      if( !tmpStr.Empty() ) 
           str = tmpStr;

 }

When I call this method with a simple string just created inside the main subroutine:

 string localString = "";
 SomeMethod( localString );

The value that I get back in localString is an empty event when I see in debug that in the method SomeMethod the variable tmpStr is not empty and the variable str in the method is full with chars.

If I change the SomeMethod signature to be:

SomeMethod(ref string str) 

I see the right result.

But string is a reference type, so why do I need in this case to send it with ref? and why I get the result that I expected only when I call:

SomeMethod(string str) 
Nicolás Ozimica
  • 9,481
  • 5
  • 38
  • 51
Yanshof
  • 9,659
  • 21
  • 95
  • 195

4 Answers4

7

It's not just that the string is immutable, but also that while string is a reference type, a reference itself is not. That is, you are passing the string by reference, but the reference itself is passed by value.

Thus, for reference types, you can modify the object that is referred to by the parameter (as long as it's modifiable), but you cannot modify what the argument refers to unless you pass it by reference.

So, when you try to change what the string variable refers to:

str = tmpStr;

It changes what str refers to locally, but does not affect what the original argument localString refers to.

Think of it this way, let's say that the argument localString refers to an object at location 1000:

localString 
+---------------+                      1000
|      1000     |   -----------------> +---------------+
+---------------+                      | Count: 1      |
                                       | Value: ""     |
                                       +---------------+

Then when we pass localString to the method, it creates a copy of the reference (as str) and updates the reference count...

localString 
+---------------+                      1000
|      1000     |   -----------------> +---------------+
+---------------+                      | Count: 2      |
                                       | Value: ""     |
str                                    +---------------+
+---------------+                        ^
|      1000     |   ---------------------+
+---------------+

Then, when you assign str to a new string, it modifies the reference str but not localString:

localString
+---------------+                      1000
|      1000     |   -----------------> +---------------+
+---------------+                      | Count: 1      |
                                       | Value: ""     |
str                                    +---------------+
+---------------+                          2500
|      2500     |   ---------------------> +---------------+
+---------------+                          | Count: 1      |
                                           | Value: ...    |
                                           +---------------+

So your modification of str only changed what str refers to, not the original refernce localString, if you want to change that, then you pass by reference, which means that str is a reference back to the original argument (much like a ptr to a ptr):

localString
+---------------+                      1000
|      2500     |  ------------------> +---------------+
+---------------+                      | Count: 2      |
        ^                              | Value: ""     |
str     |                              +---------------+
+---------------+       
|               |       
+---------------+                                                              

Now, when you change str it changes the refernce localString as well:

localString
+---------------+                      1000
|      1000     |  -----+              +---------------+
+---------------+       |              | Count: 0      |
        ^               |              | Value: ""     |
str     |               |              +---------------+
+---------------+       |                  2500
|               |       +----------------> +---------------+
+---------------+                          | Count: 1      |
                                           | Value: ...    |
                                           +---------------+

And then, of course, the original string (assuming nothing else refers to it as in this example) can be garbage collected...

So, if you really want to modify the string parameter, pass it by ref or out, or you can return the new mutated version, or store in an instance member (though pass-by-instance-member is a higher order of coupling and can cause other issues...).

James Michael Hare
  • 37,767
  • 9
  • 73
  • 83
2

If you don't use ref all the changes, but assign will take place, it is because str inside of the body of the method is copy of the variable that was passed as argument and it is pointing to the same instance in the memory so if you call methods or access fields it is on the same instance, but if you assign another instance to str its only start to point to another instance. But as str is only copy of the variable used as the argument the argument would not be changed. If you use ref then no copy is created and same pointer is used.

Matej
  • 36
  • 4
1

This should do what you want:

string SomeMethod()
{
      string tmpStr = ... Read some value from file ... 

      if( !tmpStr.Empty() ) 
           str = tmpStr;

         return str;
}

string localString = SomeMethod();
tehdoommarine
  • 1,868
  • 3
  • 18
  • 31
tpederson
  • 516
  • 1
  • 5
  • 12
  • Ok , but what about the case of two string as a argument in the 'SomeMethod' ? i don't want to return a class/struct as the return value. – Yanshof Sep 06 '12 at 02:07
1

"you cannot change the value of the reference itself; that is, you cannot use the same reference to allocate memory for a new class and have it persist outside the block."

From this site: http://msdn.microsoft.com/en-us/library/0f66670z(v=vs.71).aspx#vclrfpassingmethodparameters_example4

So you are only changing the reference inside the block. There are a two ways around this. 1. Pass with the ref keyword as you discovered. 2. As Someone else suggested; change the method to return a string and then declair your new string like this:

String localString = SomeMethod();
Joseph Carrigan
  • 359
  • 1
  • 5