开发者

Type 'string' as an argument in C# function

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.Write开发者_开发技巧Line(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]);
    }
}


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.


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.


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).


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.


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.


The confusion is that ref type references are passed by value by default, to modify the reference itself (what the object points to) you have to pass the reference by reference - using ref.

In your case you are handling strings - assigning a string to a variable (or appending to, etc.) changes the reference, since strings are immutable there is no way to avoid this either, so you have to use ref.


You're right. Arrays and strings are reference types. But if to be honest and compare similar behavior you should write something like this:

static void Function2(int[] val) // It doesn't need 'ref' for a reference type
{
    val = new[] { 1, 2, 3, 4 };
}

But in your example you perform a write operation in some element of a C# one-dimensional array via reference val.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜