When I pass a string
to a function, is a pointer to the string's contents passed, or is the entire string passed to the function on the stack like a struct
would be?
A reference is passed; however, it's not technically passed by reference. This is a subtle, but very important distinction. Consider the following code:
void DoSomething(string strLocal)
{
strLocal = "local";
}
void Main()
{
string strMain = "main";
DoSomething(strMain);
Console.WriteLine(strMain); // What gets printed?
}
There are three things you need to know to understand what happens here:
strMain
isn't passed by reference. It's a reference type, but the reference itself is passed by value. Any time you pass a parameter without the ref
keyword (not counting out
parameters), you've passed something by value.So that must mean you're...passing a reference by value. Since it's a reference type, only the reference was copied onto the stack. But what does that mean?
C# variables are either reference types or value types. C# parameters are either passed by reference or passed by value. Terminology is a problem here; these sound like the same thing, but they're not.
If you pass a parameter of ANY type, and you don't use the ref
keyword, then you've passed it by value. If you've passed it by value, what you really passed was a copy. But if the parameter was a reference type, then the thing you copied was the reference, not whatever it was pointing at.
Here's the first line of the Main
method:
string strMain = "main";
We've created two things on this line: a string with the value main
stored off in memory somewhere, and a reference variable called strMain
pointing to it.
DoSomething(strMain);
Now we pass that reference to DoSomething
. We've passed it by value, so that means we made a copy. It's a reference type, so that means we copied the reference, not the string itself. Now we have two references that each point to the same value in memory.
Here's the top of the DoSomething
method:
void DoSomething(string strLocal)
No ref
keyword, so strLocal
and strMain
are two different references pointing at the same value. If we reassign strLocal
...
strLocal = "local";
...we haven't changed the stored value; we took the reference called strLocal
and aimed it at a brand new string. What happens to strMain
when we do that? Nothing. It's still pointing at the old string.
string strMain = "main"; // Store a string, create a reference to it
DoSomething(strMain); // Reference gets copied, copy gets re-pointed
Console.WriteLine(strMain); // The original string is still "main"
Let's change the scenario for a second. Imagine we aren't working with strings, but some mutable reference type, like a class you've created.
class MutableThing
{
public int ChangeMe { get; set; }
}
If you follow the reference objLocal
to the object it points to, you can change its properties:
void DoSomething(MutableThing objLocal)
{
objLocal.ChangeMe = 0;
}
There's still only one MutableThing
in memory, and both the copied reference and the original reference still point to it. The properties of the MutableThing
itself have changed:
void Main()
{
var objMain = new MutableThing();
objMain.ChangeMe = 5;
Console.WriteLine(objMain.ChangeMe); // it's 5 on objMain
DoSomething(objMain); // now it's 0 on objLocal
Console.WriteLine(objMain.ChangeMe); // it's also 0 on objMain
}
Ah, but strings are immutable! There's no ChangeMe
property to set. You can't do strLocal[3] = 'H'
in C# like you could with a C-style char
array; you have to construct a whole new string instead. The only way to change strLocal
is to point the reference at another string, and that means nothing you do to strLocal
can affect strMain
. The value is immutable, and the reference is a copy.
To prove there's a difference, here's what happens when you pass a reference by reference:
void DoSomethingByReference(ref string strLocal)
{
strLocal = "local";
}
void Main()
{
string strMain = "main";
DoSomethingByReference(ref strMain);
Console.WriteLine(strMain); // Prints "local"
}
This time, the string in Main
really does get changed because you passed the reference without copying it on the stack.
So even though strings are reference types, passing them by value means whatever goes on in the callee won't affect the string in the caller. But since they are reference types, you don't have to copy the entire string in memory when you want to pass it around.