views:

104

answers:

5

From a method, I can pass a struct which contains an array of integers, and change the values in the array. I am not sure I understand fully why I can do this. Can someone please explain why I can change the values stored in the int[]?

    private void DoIt(){

        SearchInfo a = new SearchInfo();
        a.Index = 1;
        a.Map = new int[] { 1 };

        SearchInfo b = new SearchInfo();
        b.Index = 1;
        b.Map = new int[] { 1 };

        ModifyA(a);
        ModifyB(ref b);

        Debug.Assert(a.Index == 1);
        Debug.Assert(a.Map[0] == 1, "why did this change?");

        Debug.Assert(b.Index == 99);
        Debug.Assert(b.Map[0] == 99);

    }
    void ModifyA(SearchInfo a) {
        a.Index = 99;
        a.Map[0] = 99;
    }
    void ModifyB(ref SearchInfo b) {
        b.Index = 99;
        b.Map[0] = 99;
    }
    struct SearchInfo {
        public int[] Map;
        public int Index;
    }
+5  A: 

In C#, references are passed by value. An array is not copied when passed to method or when stored in an instance of another class. - a reference to the array is passed. This means a method which recieves a reference to an array (either directly or as part of another object) can modify the elements of that array.

Unlike languages like C++, you cannot declare "immutable" arrays in C# - you can however uses classes like List which have readonly wrappers available to prevent modification to the collection.

LBushkin
+1 It should further be noted that you while can modify the elements of the array and the calling site will be aware of those changes, if you were to point Map to a different array of integers in the called function, that's when the struct at the call site would be oblivious.
Anthony Pegram
@Anthony Pegram: Very interesting. I added <a|b>Map = new int[] { 99, 98, 97 }; to each method, and <a> keeps the original array. Makes me wonder if this is a memory leak.
AMissico
You can't get memory leaks in the traditional way in C#, as the garbage collector cleans up unreferenced objects for you :)
thecoop
I believe this is still call this "memory leak" in .NET. How does the GC know the memory is unreferenced?
AMissico
A: 

Well, it is passed by reference anyway, like all reference types in C#. Neither C# nor CLR support constness, unfortunately, so the platform doesn't really know if you are allowed to change it or not. So, it has the reference, it may use it to change the value, and there's nothing to stop it from doing so.

You may see it as a language design bug, btw. It is unexpected for the user.

Pavel Radzivilovsky
It's actually passed by value, it's just that the passed value is a reference for ref-types like arrays.
LukeH
+1  A: 

From MSDN:

Do not return an internal instance of an array. This allows calling code to change the array. The following example demonstrates how the array badChars can be changed by any code that accesses the Path property even though the property does not implement the set accessor.

using System;
using System.Collections;

public class ExampleClass
{
   public sealed class Path
   {
      private Path(){}
      private static char[] badChars = {'\"', '<', '>'};
      public static char[] GetInvalidPathChars()
      {
         return badChars;
      }
   }
   public static void Main()
   {
      // The following code displays the elements of the 
      // array as expected.
      foreach(char c in Path.GetInvalidPathChars())
      {
         Console.Write(c);
      }
      Console.WriteLine();

      // The following code sets all the values to A.
      Path.GetInvalidPathChars()[0] = 'A';
      Path.GetInvalidPathChars()[1] = 'A';
      Path.GetInvalidPathChars()[2] = 'A';

      // The following code displays the elements of the array to the
      // console. Note that the values have changed.
      foreach(char c in Path.GetInvalidPathChars())
      {
         Console.Write(c);
      }
   }
}

You cannot correct the problem in the preceding example by making the badChars array readonly (ReadOnly in Visual Basic). You can clone the badChars array and return the copy, but this has significant performance implications.

Robert Harvey
While generally good advice, this does not answer the question.
Adam Robinson
+1  A: 

Although your SearchInfo struct is a value type, the .Map field is holding a reference, because Array is a reference type. Think of this reference as the address pointing to the memory location where the array resides.

When you pass an instance of SearchInfo to a method, as you know, the SearchInfo gets copied. And the copy naturally contains the very same address pointing to the very same array.

In other words, copying the struct doesn't make a copy of the array, it just makes a copy of the pointer.

Charles
+2  A: 

From a method, I can pass a struct which contains an array of integers, and change the values in the array. I am not sure I understand fully why I can do this.

An array is defined as a collection of variables.

Variables, by definition, can be changed. That is why we call them "variables".

Therefore when you pass an array, you can change the contents; the contents of an array are variables.

Why can I change a struct’s int[] property without specifying “ref”?

Remember, as we discussed before in a different question, you use ref to make an alias to a variable. That is what "ref" is for -- making aliases to variables. (It is unfortunate that the keyword is the confusing "ref" -- it probably would have been more clear to make it "alias".)

Eric Lippert
My problem is I forgot that an array is a reference-type, and the structure just holds the pointer to the array. When I grab the pointer to the array in the method, I am changing the array values.
AMissico
I am not sure I agree with your use of "alias". It seems you are creating a new word for a common concept.
AMissico
@AMissico: An "alias" is another name for a thing. When you have void M(ref int x) and you say M(ref y), then x becomes an alias for y -- x and y are two names for the same variable. I did not invent this nomenclature; this is an industry-standard way of referring to variable references. See for example http://en.wikipedia.org/wiki/Pointer_alias
Eric Lippert
@Eric Lippert: Ah, that's right. I been doing the non-C/C++ thing for so long now, I forgot the term. Okay, "alias" it is. :O)
AMissico
@Eric: While your explanation is technically correct as usual, it seems to not be useful this time around. Unless I miss my guess, the real question is why the changes are visible to the caller. Public fields of a struct are also variables, and can also be set, but of course the changes are made in an independent copy so won't be visible to the caller. But the objects referred to by those fields are not independent.
Ben Voigt
@Ben: Because you aren't changing the content of the struct, you're changing the content of an array referenced by the struct. The behavior would be no different if the struct had a member of another reference type. If you were to change the content of the array variable *itself* (i.e. doing something like `a.Map = new int[] { 3 }`), then those changes would NOT be reflected in the calling method.
Adam Robinson