C# string reference type?

C#StringReferenceTypes

C# Problem Overview


I know that "string" in C# is a reference type. This is on MSDN. However, this code doesn't work as it should then:

class Test
{
    public static void Main()
    {
        string test = "before passing";
        Console.WriteLine(test);
        TestI(test);
        Console.WriteLine(test);
    }

    public static void TestI(string test)
    {
        test = "after passing";
    }
}

The output should be "before passing" "after passing" since I'm passing the string as a parameter and it being a reference type, the second output statement should recognize that the text changed in the TestI method. However, I get "before passing" "before passing" making it seem that it is passed by value not by ref. I understand that strings are immutable, but I don't see how that would explain what is going on here. What am I missing? Thanks.

C# Solutions


Solution 1 - C#

The reference to the string is passed by value. There's a big difference between passing a reference by value and passing an object by reference. It's unfortunate that the word "reference" is used in both cases.

If you do pass the string reference by reference, it will work as you expect:

using System;

class Test
{
    public static void Main()
    {
        string test = "before passing";
        Console.WriteLine(test);
        TestI(ref test);
        Console.WriteLine(test);
    }

    public static void TestI(ref string test)
    {
        test = "after passing";
    }
}

Now you need to distinguish between making changes to the object which a reference refers to, and making a change to a variable (such as a parameter) to let it refer to a different object. We can't make changes to a string because strings are immutable, but we can demonstrate it with a StringBuilder instead:

using System;
using System.Text;

class Test
{
    public static void Main()
    {
        StringBuilder test = new StringBuilder();
        Console.WriteLine(test);
        TestI(test);
        Console.WriteLine(test);
    }

    public static void TestI(StringBuilder test)
    {
        // Note that we're not changing the value
        // of the "test" parameter - we're changing
        // the data in the object it's referring to
        test.Append("changing");
    }
}

See my article on parameter passing for more details.

Solution 2 - C#

If we have to answer the question: String is a reference type and it behaves as a reference. We pass a parameter that holds a reference to, not the actual string. The problem is in the function:

public static void TestI(string test)
{
test = "after passing";
}
The parameter test holds a reference to the string but it is a copy. We have two variables pointing to the string. And because any operations with strings actually create a new object, we make our local copy to point to the new string. But the original test variable is not changed.

The suggested solutions to put ref in the function declaration and in the invocation work because we will not pass the value of the test variable but will pass just a reference to it. Thus any changes inside the function will reflect the original variable.

I want to repeat at the end: String is a reference type but since its immutable the line test = "after passing"; actually creates a new object and our copy of the variable test is changed to point to the new string.

Solution 3 - C#

As others have stated, the String type in .NET is immutable and it's reference is passed by value.

In the original code, as soon as this line executes:

test = "after passing";

then test is no longer referring to the original object. We've created a new String object and assigned test to reference that object on the managed heap.

I feel that many people get tripped up here since there's no visible formal constructor to remind them. In this case, it's happening behind the scenes since the String type has language support in how it is constructed.

Hence, this is why the change to test is not visible outside the scope of the TestI(string) method - we've passed the reference by value and now that value has changed! But if the String reference were passed by reference, then when the reference changed we will see it outside the scope of the TestI(string) method.

Either the ref or out keyword are needed in this case. I feel the out keyword might be slightly better suited for this particular situation.

class Program
{
    static void Main(string[] args)
    {
        string test = "before passing";
        Console.WriteLine(test);
        TestI(out test);
        Console.WriteLine(test);
        Console.ReadLine();
    }

    public static void TestI(out string test)
    {
        test = "after passing";
    }
}

Solution 4 - C#

"A picture is worth a thousand words".

I have a simple example here, it's similar to your case.

string s1 = "abc";
string s2 = s1;
s1 = "def";
Console.WriteLine(s2);
// Output: abc

This is what happened:

enter image description here

  • Line 1 and 2: s1 and s2 variables reference to the same "abc" string object.

  • Line 3: Because strings are immutable, so the "abc" string object does not modify itself (to "def"), but a new "def" string object is created instead, and then s1 references to it.

  • Line 4: s2 still references to "abc" string object, so that's the output.

Solution 5 - C#

Actually it would have been the same for any object for that matter i.e. being a reference type and passing by reference are 2 different things in c#.

This would work, but that applies regardless of the type:

public static void TestI(ref string test)

Also about string being a reference type, its also a special one. Its designed to be immutable, so all of its methods won't modify the instance (they return a new one). It also has some extra things in it for performance.

Solution 6 - C#

Here's a good way to think about the difference between value-types, passing-by-value, reference-types, and passing-by-reference:

A variable is a container.

A value-type variable contains an instance. A reference-type variable contains a pointer to an instance stored elsewhere.

Modifying a value-type variable mutates the instance that it contains. Modifying a reference-type variable mutates the instance that it points to.

Separate reference-type variables can point to the same instance. Therefore, the same instance can be mutated via any variable that points to it.

A passed-by-value argument is a new container with a new copy of the content. A passed-by-reference argument is the original container with its original content.

When a value-type argument is passed-by-value: Reassigning the argument's content has no effect outside scope, because the container is unique. Modifying the argument has no effect outside scope, because the instance is an independent copy.

When a reference-type argument is passed-by-value: Reassigning the argument's content has no effect outside scope, because the container is unique. Modifying the argument's content affects the external scope, because the copied pointer points to a shared instance.

When any argument is passed-by-reference: Reassigning the argument's content affects the external scope, because the container is shared. Modifying the argument's content affects the external scope, because the content is shared.

In conclusion:

A string variable is a reference-type variable. Therefore, it contains a pointer to an instance stored elsewhere. When passed-by-value, its pointer is copied, so modifying a string argument should affect the shared instance. However, a string instance has no mutable properties, so a string argument cannot be modified anyway. When passed-by-reference, the pointer's container is shared, so reassignment will still affect the external scope.

Solution 7 - C#

Above answers are helpful, I'd just like to add an example that I think is demonstrating clearly what happens when we pass parameter without the ref keyword, even when that parameter is a reference type:

MyClass c = new MyClass(); c.MyProperty = "foo";

CNull(c); // only a copy of the reference is sent 
Console.WriteLine(c.MyProperty); // still foo, we only made the copy null
CPropertyChange(c); 
Console.WriteLine(c.MyProperty); // bar


private void CNull(MyClass c2)
        {          
            c2 = null;
        }
private void CPropertyChange(MyClass c2) 
        {
            c2.MyProperty = "bar"; // c2 is a copy, but it refers to the same object that c does (on heap) and modified property would appear on c.MyProperty as well.
        }

Solution 8 - C#

For curious minds and to complete the conversation: Yes, String is a reference type:

unsafe
{
     string a = "Test";
     string b = a;
     fixed (char* p = a)
     {
          p[0] = 'B';
     }
     Console.WriteLine(a); // output: "Best"
     Console.WriteLine(b); // output: "Best"
}

But note that this change only works in an unsafe block! because Strings are immutable (From MSDN):

> The contents of a string object cannot be changed after the object is > created, although the syntax makes it appear as if you can do this. > For example, when you write this code, the compiler actually creates a > new string object to hold the new sequence of characters, and that new > object is assigned to b. The string "h" is then eligible for garbage > collection.

string b = "h";  
b += "ello";  

And keep in mind that:

> Although the string is a reference type, the equality operators (== and > !=) are defined to compare the values of string objects, not > references.

Solution 9 - C#

Try:


public static void TestI(ref string test)
{
test = "after passing";
}

Solution 10 - C#

I believe your code is analogous to the following, and you should not have expected the value to have changed for the same reason it wouldn't here:

 public static void Main()
 {
     StringWrapper testVariable = new StringWrapper("before passing");
     Console.WriteLine(testVariable);
     TestI(testVariable);
     Console.WriteLine(testVariable);
 }

 public static void TestI(StringWrapper testParameter)
 {
     testParameter = new StringWrapper("after passing");

     // this will change the object that testParameter is pointing/referring
     // to but it doesn't change testVariable unless you use a reference
     // parameter as indicated in other answers
 }

Solution 11 - C#

Another way to bypass the string behavior. Use string array of ONE element only and manipulate this element.

class Test
{
    public static void Main()
    {
        string[] test = new string[1] {"before passing"};
        Console.WriteLine(ref test);
        TestI(test);
        Console.WriteLine(ref test);
    }

    public static void TestI(ref string[] test)
    {
        test[0] = "after passing";
    }
}

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionCriosRView Question on Stackoverflow
Solution 1 - C#Jon SkeetView Answer on Stackoverflow
Solution 2 - C#Martin DimitrovView Answer on Stackoverflow
Solution 3 - C#Derek WView Answer on Stackoverflow
Solution 4 - C#PhucView Answer on Stackoverflow
Solution 5 - C#eglasiusView Answer on Stackoverflow
Solution 6 - C#BryanView Answer on Stackoverflow
Solution 7 - C#BornToCodeView Answer on Stackoverflow
Solution 8 - C#n.yView Answer on Stackoverflow
Solution 9 - C#Marius KjeldahlView Answer on Stackoverflow
Solution 10 - C#Dave CousineauView Answer on Stackoverflow
Solution 11 - C#Joe FrankView Answer on Stackoverflow