Methods with string parameters and ref in .Net

741 Views Asked by At

I have interesting example, could you please explain me it? As i understand string is a reference class, but it can not be modified. So when i try to modify string - framework create new reference to new heap address. Bu i do not understand why this new address from method Bear do not pass outside to the main call stack.

class DoWork
{
    public static void Beer(string s1)  {
        s1 = s1 + "beer";
    }
    public static void Vodka(ref string s2) {
        s2 = s2 + "vodka";
    }
}

string sss = "I Like ";
DoWork.Beer(sss);
DoWork.Vodka(ref sss);

so sss will have value "I Like vodka" but why?

P.S. or this way

DoWork.Vodka(ref sss);
DoWork.Beer(sss);

P.P.S. some IL code from example

DoWork.Beer:
IL_0000:  ldarg.0     
IL_0001:  ldstr       "beer"
IL_0006:  call        System.String.Concat
IL_000B:  starg.s     00 
IL_000D:  ret

DoWork.Vodka:
IL_0000:  ldarg.0     
IL_0001:  ldarg.0     
IL_0002:  ldind.ref   
IL_0003:  ldstr       "vodka"
IL_0008:  call        System.String.Concat
IL_000D:  stind.ref   
IL_000E:  ret   
3

There are 3 best solutions below

4
On

Simply put, ref keyword enables the called method to manipulate the reference at the call site. This is the full purpose of ref.

What happens is that when Vodka returns, sss has been replaced with a new string instance. It does not update the original instance. Without the ref modifier this is not possible.

One thing that I often find that people are confused about when talking about this is when you consider the concepts of passing parameters by value and by reference, when you deal with reference types. When you pass a string (a reference type) by value, what is it really that you pass along? You pass the value of the variable. In this case, the value is a reference to a string. So you pass that value, and the calling method can use that value to access the string. However, since the parameter is passed by value, it is the value as such that is passed (essentially a copy of the reference), not the pointer to the memory location storing the value. This is why the calling method cannot replace the instance at the call site.

When passing a parameter by reference, instead of the value, the called method gets access to the memory location in which the value is stored (keep in mind now that the value is the reference to the string). This means that the called method can now update this value, to have the variable at the call site contain a reference to another string.

Update
Let's dissect the example from your comment (the names have been changed to protect the innocent):

class SomeClass
{
    public int Value { get; set; }
}

class DoWork
{
    public static void DoOne(SomeClass c) { c.Value = c.Value + 1; }
    public static void DoTwo(ref SomeClass c) { c.Value = c.Value + 2; }
}

SomeClass is a reference type. This means that any piece of code to which you pass such an instance (and, just to be clear, we never pass the actual instance of a reference type, we pass a reference to an instance), can manipulate data in that instance*. This means that it can call methods, set property values and so on, and any change in state that this leads to is also visible at the call site. The ref keyword has no effect on that. So in your sample, the ref keyword makes no difference. However, consider this change:

class DoWork
{
    public static void DoOne(SomeClass c) 
    { 
        c = new SomeClass(); 
        c.Value = 1; 
    }
    public static void DoTwo(ref SomeClass c) 
    {
        c = new SomeClass(); 
        c.Value = 2; 
    }
}

Now, lets call it:

var temp = new SomeClass() { Value = 42 };
DoWork.DoOne(temp);
Console.WriteLine(temp.Value); // prints 42

DoWork.DoTwo(ref temp);
Console.WriteLine(temp.Value); // prints 2

What happens now is that both methods create a new SomeClass instance to operate on. Since DoOne gets the reference passed by value, it will update its own private copy of the reference to point to the new instance, and naturally this change is not seen at the call site.

On the other hand, since DoTwo gets the reference passed by reference, when it creates a new instance and assigns it to c, it updates the same memory location as call site is using, so temp is now referencing the new instance created inside DoTwo.

To summarize: for reference types, the only time you need to use ref is when you want to enable the called method to replace the passed instance itself. If you only want to manipulate the state of that instance, reference types will always allow that.

Cody Gray posted a link to Jon Skeet's Parameter passing in C#, if you didn't already, go read it. It does a very good job at explaining these things (and more; it also deals with value types).

*(unless the type is immutable, like string, but that is a completely different story...)

4
On

in Beer the pointer to the string is passed by value, so when you do s1 = s1 + "beer"; the copy of the pointer is changed and it is not visible outside the method.
in Vodka however, the pointer to the string is passed by reference, so when you do s1 = s1 + "vodka"; the same pointer is being changed and it is visible outside the method.

4
On