views:

153

answers:

9

I have the below piece of code which Prefixs a string to the start of each member of a string array. ie. ["a","b","c"] prefixed with "z" becomes ["za","zb","zc"].

private string[] Prefix(string[] a, string b) {
   for(int i = 0;i < a.Length;i++) {
     a[i] = b + a[i];
   }
  return a;
}

The function works fine (although if theres a better way to do this, I'm happy to hear it), but I'm having issues when passing parameters.

string[] s1 = new string[] {"a","b"};
string[] s2 = Prefix(s1,"z");

Now as far as I can tell, I'm passing s1 by Value. But when the Prefix function has finished, s2 and s1 have the same value of ["za,"zb"], or s1 has been passed by reference. I was certain you had to explicitly declare this behaviour in c#, and am very confused.

+3  A: 

C#, like Java before it, passes everything by value by default.

However, for reference types, that value is a reference. Note that both string and array are reference types.

R. Bemrose
+1  A: 

You are passing the reference to s1 by value. In other words, your a parameter (when in the Prefix function scope), and your s1 variable, are references to the same array.

driis
+1  A: 

strings are immutable

this means that when you append a string to a string you get out a totally new string - its for performance reasons. its cheaper to make a new string that to reallocate the existing array.

hence why it feels like you are working with strings by value

in c# all reference types are passed by reference by default - ie classes creaped on the heap rather than values.

John Nicholas
The "reference" of said object is passed by value (n references can refer to the same object). Using the ref/out keywords introduces pass-by-reference semantics
pst
+1  A: 

The value of an object is passed. For "reference" objects this is the value of the reference. A clone/copy/duplicate of the underlying data is not made.

To fix the issue observed, simply don't mutate the input array -- instead, create a new output array/object and fill it in appropriately. (If you must use arrays, I would likely use this approach as it's so boringly C-like anyway.)

Alternately, you can clone the input array first (which also creates a new object). Using a clone (which is a shallow copy) in this case is okay because the inner members (strings) are themselves immutable -- even though they are reference types the value of the underlying object can't be changed once created. For nested mutable types, more care may need to be taken.

Here are two methods which can be used to create a shallow copy:

string[] B = (string[])A.Clone();

string[] B = (new List<string>(A)).ToArray();

It's not inclusive.

Personally though, I would use LINQ. Here is a teaser:

return a.Select(x => b + x).ToArray();
pst
+1  A: 

A reference to the string array is passed by value. Consequently, the original array reference in the calling method cannot be changed, meaning that a = new string[10] within the Prefix method would have no impact on the s1 reference in the calling method. But the array itself is mutable, and a duplicate reference to the same array is capable of making changes to it in a way that would be visible to any other reference to the same array.

Ani
whilst technically correct this is confusing beacase c# doesn't contain the concept of pointer and reference as they are in c. In c# you are passing by reference in effect- nobody cares that it is the value of the memory location that is being referenced. Alkthough it seems most people think this way after all. Wierd as its more complicated.
John Nicholas
@John Nicholas: C# *most certainly* has the concept of a reference. It also has the concept of pointers in unsafe code, but thats not relevant here.
Ani
the key words were 'as they are in c'.
John Nicholas
@John Nicholas: I don't really understand your comments and how they relate to my answer. All mentions of 'pointer','memory location' and comparisons with C are your own. The fact that a *reference* is passed by *value* is exactly how the behaviour is meant to be understood as per the C# spec.
Ani
My point is that precisley because the value of the reference is passed it is equivalent to saying that a reference to the object is passed. You cannot operate upon the value of the reference only the object that is referenced. Granted you are just repeating the spec, buts its a really confusing way to explain something to someone because you would have to explain pointers which a lot of c# programmers in my experience have no clue about. I am not disputing your veracity.
John Nicholas
Emphasizing the pass-by-value semantics is precisely what will prevent confusion when contrasting the behaviour between reference and value-type arguments.
Ani
@John, *passing by reference* is fundamentally different than *passing a reference by value*. You're equating the two, but they are simply not equal and never will be. The simplest demonstration is `void Bar(Foo foo)` vs. `void Bar(ref Foo foo)`. If within the method body, you have `foo = new Foo();`, which method results in a change at the call site?
Anthony Pegram
hmm, interesting example. I have never actually re-instantiated a parameter in a method and expected its scope to extend beyond that method nor have i ever had to. You are right, I am being misleading in what i am saying. I had not considered this side effect.
John Nicholas
@John, and in the interest of completeness, it doesn't have to be setting it to a *new* instance, all it needs to be is setting it equal to *something* other than whatever it already is. `foo = null;` or `foo = someOtherFoo;` will demonstrate the same difference between the `ref` parameter and the standard.
Anthony Pegram
+1  A: 

Actually, a reference to s1 was passed by value to Prefix(). While you now have two different references, both s1 and s2 still refer to the same string array, as arrays are reference types in C#.

BoltClock
+1  A: 

Passing by value doesn't mean that you can't change things. Objects are passed by value, but the value is (effectively) a pointer to the object. Arrays are objects and a pointer to the array gets passed in. If you change the contents of the array in a method, the array will reflect the changes. This doesn't happen with strings only because strings are immutable - once constructed, their contents can't change.

plinth
+4  A: 

As others have said, the reference is passed by value. That means your s1 reference is copied to a, but they both still refer to the same object in memory. What I would to do fix your code is write it like this:

private IEnumerable<string> Prefix(IEnumerable<string> a, string b) {
   return a.Select(s => b + s);
}

.

string[] s1 = new string[] {"a","b"};
string[] s2 = Prefix(s1,"z").ToArray();

This not only fixes your problem, but also allows you to work with Lists and other string collections in addition to simple arrays.

Joel Coehoorn
+2  A: 

Here's a better way to do it:

private string[] Prefix(string[] a, string b) {
  return a.Select(s => b + s).ToArray();
}

or even:

private IEnumerable<string> Prefix(IEnumerable<string> a, string b) {
  return a.Select(s => b + s);
}
Gabe