Type 'string' as an argument in C# function
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 int
s 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
.
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, 2022Comments
-
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 theref
modifier. However, I need to use theref
modifier for modifying the inputstring
. 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 almost 13 yearsThe 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 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 astring
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 almost 13 yearsBut 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 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 over 8 yearsThe 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 over 8 yearsThis problem is completely unrelated to immutability. This answer is misleading.
-
pickypg over 8 yearsIts been years, but again, I put more weight into the question where he was confused about
string
s. 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, thenoperator=
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.