Type 'string' as an argument in C# function

15,460

Solution 1

The reason you need to ref the string parameter is that even though you pass in a reference to a string object, assigning something else to the parameter will just replace the reference currently stored in the parameter variable. In other words, you have changed what the parameter refers to, but the original object is unchanged.

When you ref the parameter, you have told the function that the parameter is actually an alias for the passed-in variable, so assigning to it will have the desired effect.

EDIT: Note that while string is an immutable reference type, that's not too relevant here. Since you're just trying to assign a new object (in this case the string object "modified"), your approach wouldn't work with any reference type. For example, consider this slight modification to your code:

using System;

class TestIt 
{
    static void Function(ref string input)
    {
        input = "modified";
    }

    static void Function2(int[] val) // don't need ref for reference type
    {
        val = new int[10];  // Change: create and assign a new array to the parameter variable
        val[0] = 100;
    }
    static void Main()
    {
        string input = "original";
        Console.WriteLine(input);
        Function(ref input);      // need ref to modify the input
        Console.WriteLine(input);

        int[] val = new int[10];
        val[0] = 1;
        Function2(val);
        Console.WriteLine(val[0]); // This line still prints 1, not 100!
    }
}

Now, the array test "fails", because you're assigning a new object to the non-ref parameter variable.

Solution 2

It helps to compare string to a type that is like string but is mutable. Let's see a short example with StringBuilder:

public void Caller1()
{
    var builder = new StringBuilder("input");
    Console.WriteLine("Before: {0}", builder.ToString());
    ChangeBuilder(builder);
    Console.WriteLine("After: {0}", builder.ToString());
}

public void ChangeBuilder(StringBuilder builder)
{
    builder.Clear();
    builder.Append("output");
}

This produces:

Before: input
After: output

So we see that for a mutable type, i.e. a type that can have its value modified, it is possible to pass a reference to that type to a method like ChangeBuilder and not use ref or out and still have the value changed after we called it.

And notice that at no time did we actually set builder to a different value in ChangeBuilder.

By contrast, if we do the same thing with string:

public void Caller2()
{
    var s = "input";
    Console.WriteLine("Before: {0}", s);
    TryToChangeString(s);
    Console.WriteLine("After: {0}", s);
}

public void TryToChangeString(string s)
{
    s = "output";
}

This produces:

Before: input
After: input

Why? Because in TryToChangeString we are not actually changing the contents of the string referenced by the variable s, we are replacing s with an entirely new string. Furthermore, s is a local variable to TryToChangeString and so replacing the value of s inside the function has no effect on the variable that was passed in to the function call.

Because a string is immutable, there is no way, without using ref or out, to affect the callers string.

Finally, the last example does what we want with string:

public void Caller3()
{
    var s = "input";
    Console.WriteLine("Before: {0}", s);
    ChangeString(ref s);
    Console.WriteLine("After: {0}", s);
}

public void ChangeString(ref string s)
{
    s = "output";
}

This produces:

Before: input
After: output

The ref parameter actually makes the two s variables aliases for each other. It's as though they were the same variable.

Solution 3

Strings are immutable - you are not modifying the string but replacing the object the reference points to with another one.

Compare that with e.g., a List: To add Items, you don't need ref. To replace the entire list with a different object, you need ref (or out).

Solution 4

This is the case for all immutable types. string happens to be immutable.

In order to change the immutable type outside of the method, you must change the reference. Therefore either ref or out is required to have an effect outside of the method.

Note: It's worth noting that in your example, you are calling out a particular case that does not match the other example: you are actually pointing to a different reference rather than simply changing the existing reference. As noted by dlev (and the Skeet himself in my comments), if you did the same for all other types (e.g., val = new int[1]), including mutable ones, then you will "lose" your changes once the method returns because they did not happen to the same object in memory, unless you use ref or out like you did with string above.

To hopefully clarify:

You are passing in a pointer that points to your object in memory. Without ref or out, a new pointer is made that points to the exact same location, and all changes happen using the copied pointer. Using them, the same pointer is used and all changes made to the pointer are reflected outside the method.

If your object is mutable, then that means that it can be changed without creating a new instance of the object. If you create a new instance, then you must point to somewhere else in memory, which means you must change your pointer.

Now, if your object is immutable, then that means that it cannot be changed without creating a new instance.

In your example, you created a new instance of a string (equal to "modified") and then changed the pointer (input) to point to that new instance. For the int array, you changed one of the 10 values effectively pointed to by val, which does not require messing with val's pointer--it simply goes to where you want (the first element of the array), and then modifies that first value, in-place.

A more similar example would be (stolen from dlev, but this is how to make them truly comparable):

static void Function(ref string input)
{
    input = "modified";
}

static void Function2(int[] val)
{
    val = new int[1];
    val[0] = 100;
}

Both functions change their parameter's pointer. Only because you used ref does input "remember" its changes, because when it changes the pointer, it is changing the pointer that was passed in and not just a copy of it.

val will still be an array of 10 ints outside of the function, and val[0] will still be 1 because the "val" within Function2 is a different pointer that originally points to the same location as Main's val, but it points somewhere else after the new array is created (the different pointer points to the new array, and the original pointer continues to point to the same location).

If I used ref with the int array, then it to would have changed. And it would have changed in size too.

Solution 5

A better example for newbies:

string a = "one";
string b = a;
string b = "two";

Console.WriteLine(a);

... will output "one".

Why? Because you are assigning a whole new string into pointer b.

Share:
15,460
prosseek
Author by

prosseek

A software engineer/programmer/researcher/professor who loves everything about software building. Programming Language: C/C++, D, Java/Groovy/Scala, C#, Objective-C, Python, Ruby, Lisp, Prolog, SQL, Smalltalk, Haskell, F#, OCaml, Erlang/Elixir, Forth, Rebol/Red Programming Tools and environments: Emacs, Eclipse, TextMate, JVM, .NET Programming Methodology: Refactoring, Design Patterns, Agile, eXtreme Computer Science: Algorithm, Compiler, Artificial Intelligence

Updated on July 27, 2022

Comments

  • prosseek
    prosseek almost 2 years

    The string type in C# is a reference type, and passing a reference type argument by value copies the reference so that I don't need to use the ref modifier. However, I need to use the ref modifier for modifying the input string. Why is this?

    using System;
    
    class TestIt
    {
        static void Function(ref string input)
        {
            input = "modified";
        }
    
        static void Function2(int[] val) // Don't need ref for reference type
        {
            val[0] = 100;
        }
    
        static void Main()
        {
            string input = "original";
            Console.WriteLine(input);
            Function(ref input);      // Need ref to modify the input
            Console.WriteLine(input);
    
            int[] val = new int[10];
            val[0] = 1;
            Function2(val);
            Console.WriteLine(val[0]);
        }
    }
    
  • Jon Skeet
    Jon Skeet almost 13 years
    The immutability is a bit of a red herring here. The important point is to note the difference between changing the value of the parameter, and changing the contents of the object that the parameter's value refers to.
  • pickypg
    pickypg almost 13 years
    @Jon I actually disagree that it's a red herring. Every use of string would require setting the value (e.g., input = input.Replace(...);) to have any hope of changing it within the method, or out of it. I think that the question shows that the poster understands acting on the object changed other reference types, such as his array example, and he simply did not understand why doing so with a string did not have the same result (and used a poor example). However, I do believe you and dlev both make valid points that prosseek probably did not consider when forming his question.
  • Jon Skeet
    Jon Skeet almost 13 years
    But your answer suggests that mutable types behave differently to immutable types - and they don't. If you assign a new value to a StringBuilder parameter (rather than modifying the object) then that won't be seen either. That's the important bit. With immutable types there's no other kind of change that can be made, but the way that assignment behaves does not depend on immutability.
  • pickypg
    pickypg almost 13 years
    @Jon That's a fair point, and I guess I can see that with regard to the example, but not to the question; I just put much more faith into the question rather than the example. Since he did post the example, I added a large note clarifying and probably repeating dlev. I guess this is why you write the books and note me :). Hopefully he understands pointers.
  • Johannes Overmann
    Johannes Overmann over 8 years
    The fact that strings are immutable is not relevant here. An int[] is mutable but suffers from the same problem. The same problem exists for all reference types. However, your other explanations are correct and helpful.
  • Johannes Overmann
    Johannes Overmann over 8 years
    This problem is completely unrelated to immutability. This answer is misleading.
  • pickypg
    pickypg over 8 years
    Its been years, but again, I put more weight into the question where he was confused about strings. Both the array and string are references, but the array "remembers" changes to its elements because it's mutable. Assignment is really just a special case of modification and if string were mutable, then operator= would most likely work as expected here. Back to Jon's point though, if you're focused exclusively on assignment, then immutability is really irrelevant. However, I feel like that's missing the devil in the details.