16

Given the following method:

static void ChangeArray(params string[] array) {

    for (int i = 0; i < array.Length; i++) 
        array[i] = array[i] + "s";
}

This works if I call it passing a array of strings:

string[] array = {"Michael", "Jordan"} // will become {"Michaels", "Jordans"}
ChangeArray(array);

But will not work if I call it using string arguments:

string Michael = "Michael";
string Jordan = "Jordan";
ChangeArray(Michael, Jordan); // This will NOT change the values of the variables

I understand that the compiler will wrap Michael and Jordan on an array, so shouldn't the results be the same on both cases?

Bruno Santos
  • 724
  • 7
  • 19

3 Answers3

23

Your second example is essentially:

string Michael = "Michael";
string Jordan = "Jordan";
{
    var tmp = new string[] {Michael, Jordan};
    ChangeArray(tmp);
}

so; actually, the values inside tmp were changed... but tmp was discarded afterwards, so you don't see anything. params does not emulate ref - it won't do a position-wise update back into the original variables. Or in code, it is not the following:

string Michael = "Michael";
string Jordan = "Jordan";
{
    var tmp = new string[] {Michael, Jordan};
    ChangeArray(tmp);
    Michael = tmp[0];
    Jordan = tmp[1];
}

If you need it to behave like that, then code it like that - or use instead an overload that takes ref parameters.

Marc Gravell
  • 1,026,079
  • 266
  • 2,566
  • 2,900
5

The reason is that string is an immutable type - you pass in string instances that get wrapped in an array. The array now contains two new strings (with the same values that the original ones but different instances). When you change the array, these copies are throw away and the array slot will hold a new string. When your function returns, the temporary array is thrown away. Thus your original input strings are never modified (they couldn't be anyway, since string is - again - immutable).

Edit I made an edit to this answer following Lee's argument in the comments (I'll leave the answer as is simply to keep the following discussion complete). The immutable part is indeed irrelevant to the problem. The main underlying issue is that changes are made to the temporary array that's thrown away.

xxbbcc
  • 16,930
  • 5
  • 50
  • 83
  • 1
    This isn't to do with mutability, and the created array does not contain 'new' strings, it contains a copy of the references to the same strings. – Lee Sep 21 '12 at 07:36
  • 1
    @Lee After `ChangeArray` returns, the created array does contain new strings. –  Sep 21 '12 at 07:36
  • 2
    This has nothing to do with strings being immutable. – Daniel Hilgarth Sep 21 '12 at 07:37
  • @Lee well, when the method changes them the array contains different references to new strings. Immutability is *sort of, tangentially* related here, but the **main** issue is simply : the temporary array being thrown away, which this answer does cover. – Marc Gravell Sep 21 '12 at 07:37
  • @hvd - Yes, but the question is why the second example is different. It's different because the temporary array is not visible, not because strings are immutable. String objects are immutable, string references are not. – Lee Sep 21 '12 at 07:38
  • @Lee I think the question assumed it worked like `class X { public int field; } void f(params X[] array) { foreach (var x in array) { x.field++; } }`, and the answer for that is it doesn't, and cannot because strings are immutable. But we may have subtly interpreted the question differently. –  Sep 21 '12 at 07:40
  • @Lee I have a hard time deciding if I should remove this answer. I kind of see Lee's point but then new `string` objects are created during the `+"s"` part of the code inside the function. I guess Marc's answer is clearer and more useful. – xxbbcc Sep 21 '12 at 07:41
  • @hvd - Ok, I suppose that's possible, although the array element is being re-assigned in the first example, so it would work the same with a mutable class. – Lee Sep 21 '12 at 07:43
  • "immutable" simply means the object has no methods that changes its contents. But no methods are being called on the strings at all in the question! So it is irrelevant. If the same exact thing is done with mutable objects, it would be the same. – newacct Sep 21 '12 at 08:50
  • @hvd: your thing about `int` fields doesn't demonstrate what you think. Primitives are equivalent to references to immutable objects -- the only way to change them is by assigning to them. – newacct Sep 21 '12 at 08:52
  • @newacct Indeed, but `X`, in my example, is mutable, and that's what I meant to show. –  Sep 21 '12 at 09:02
2

This is weird (I didn't know this), but as specified.

A parameter array permits arguments to be specified in one of two ways in a method invocation:

The argument given for a parameter array can be a single expression of a type that is implicitly convertible (Section 6.1) to the parameter array type. In this case, the parameter array acts precisely like a value parameter.

Alternatively, the invocation can specify zero or more arguments for the parameter array, where each argument is an expression of a type that is implicitly convertible (Section 6.1) to the element type of the parameter array. In this case, the invocation creates an instance of the parameter array type with a length corresponding to the number of arguments, initializes the elements of the array instance with the given argument values, and uses the newly created array instance as the actual argument.

Community
  • 1
  • 1
Rawling
  • 49,248
  • 7
  • 89
  • 127
  • This shouldn't be hugely surprising to anyone who use used a `params` parameter - for example, in `string.Format` or `Console.WriteLine` – Marc Gravell Sep 21 '12 at 07:40
  • When I say I didn't know this, I meant I didn't know passing an acutal _array_ would allow the method to alter the array - but I don't pass arrays to params very often, so it's never come up. – Rawling Sep 21 '12 at 07:41
  • Arrays are reference types. Any time you pass a reference to a method, that method is free to operate on the object so referenced (within the limitations of that class) – Damien_The_Unbeliever Sep 21 '12 at 12:15
  • Yes. I just assumed that since the called method can't tell whether it has a temporary array or a leaky one (can it?), the compiler might make it not matter e.g. by cloning the array. – Rawling Sep 21 '12 at 13:05