0

In this small piece of code, changing a 2 dimensional array inside a method leads to changes the variable in main method.

What is the cause of this and how can I protect the variable in main method to remain unchanged?

using System;

namespace example
{
    class Program
    {
        static void Main(string[] args)
        {
            string[,] string_variable = new string[1, 1];
            string_variable[0, 0] = "unchanged";
            Console.Write("Before calling the method string variable is {0}.\n", string_variable[0,0]);
            Function(string_variable);
            Console.Write("Why after calling the method string variable is {0}? I want it remain unchanged.\n", string_variable[0, 0]);
            Console.ReadKey();
        } 
        private static void Function(string [,] method_var)
        {
            method_var[0, 0] ="changed";
            Console.Write("Inside the method string variable is {0}.\n", method_var[0, 0]);
        }
    } 
} 

At the end this is the program output:

Before calling the method string variable is unchanged.
Inside the method string variable is changed.
Why after calling the method string variable is changed? I want it remain unchanged.

EDIT 1: A question come in my mind is : What are other common programming languages that doesn't have this sort of problem?

EDIT 2: For sack of comparison, I write this somehow identical code with string variable instead of array and the output is as expected and is just fine:

using System;
namespace example
{
    class Program
    { 
    static void Main(string[] args)
        {
            string string_variable;
            string_variable= "unchanged";
            Console.Write("Before calling the method string variable is {0}.\n", string_variable);
            Function(string_variable);
            Console.Write("after calling the method string variable is {0} as expected.\n", string_variable);
            Console.ReadKey();
        } 
        private static void Function(string method_var)
        {
            method_var ="changed";
            Console.Write("Inside the method string variable is {0}.\n", method_var);
        }
    } 
}   

and the output of this code is :

Before calling the method string variable is unchanged.
Inside the method string variable is changed.
after calling the method string variable is unchanged as expected.

Last EDIT : Thanks everybody for clarification, Hope this will become useful for others in future.

Sadegh
  • 865
  • 1
  • 23
  • 47
  • You could use an ImmutableArray https://msdn.microsoft.com/en-us/library/system.collections.immutable.immutablearray%28v=vs.111%29.aspx?f=255&MSPPError=-2147217396 and the immutable collections library in general. – MatthewMartin Nov 02 '15 at 17:56
  • @MatthewMartin: That would only work for a one-dimensional array, wouldn't it? – StriplingWarrior Nov 02 '15 at 17:59
  • Don't know, I don't have much experience with the immutable library. Immutable patterns are how you get by value logic from something that would otherwise be by reference logic. If you implement the immutability pattern your self or use a library, or some specific class, that's up to you. – MatthewMartin Nov 02 '15 at 18:01
  • @MatthewMartin I need my code for multidimensional arrays so I suppose this method wont work. – Sadegh Nov 02 '15 at 18:39
  • @ChrisO that question is in contrary of what I asked.But it is somehow relevant. For instance seems like using keyword ref is interesting. Is there a keyword opposite of ref? – Sadegh Nov 02 '15 at 18:46
  • 1
    @MyUserName: Without a `ref` or `out` keyword you can never change the *value* of the parameter that's passed in (e.g. `method_var = null` won't change `string_variable`), but in .NET the value of reference typed objects which gets passed as a parameter is its reference. There's no keyword to prevent you from invoking an indexer or calling a method on the object that might change its state. – StriplingWarrior Nov 02 '15 at 19:05
  • Deleted my comment, since arrays are always passed by ref, i.e. you really cannot turn that off, or make them readonly. The wrapper class is your best bet, as mentioned by @StriplingWarrior. – Chris O Nov 02 '15 at 21:25
  • 1
    Your fundamental issue is **an array is a collection of variables**. Variables can change; that is why they are called variables. If you don't like that arrays are collections of variables then *don't use arrays*. There are many other data structures that are *not* collections of variables. – Eric Lippert Nov 02 '15 at 23:24
  • A question come in my mind is : What are other common programming languages that doesn't have this sort of problem? – Sadegh Nov 03 '15 at 12:17
  • 1
    As for your update: **a string is not a collection of variables**. A string is a collection of character *values*, not a collection of character *variables*. Again, an array is a collection of *variables*, and variables can *change*. Again, if you don't want something to change, *do not use a collection of variables*. They change! – Eric Lippert Nov 03 '15 at 15:33
  • Also, your comparison is an apples-to-oranges comparison. In your first program, change the method to `method_var = new string[1,1];` and you will see that `string_variable` does not change. In both the string and array cases **the value of the formal parameter is a copy of the reference**. The difference is what the reference *refers to*. In your first example it refers to a collection of *string variables* which can change; in the second it refers to a collection of *character values*, which cannot. – Eric Lippert Nov 03 '15 at 15:36
  • 1
    I note also that your statement "changing a 2 dimensional array inside a method leads to changes the variable in main method." is simply wrong. **The value of `string_variable` has not changed**. It is a *reference*. It refers to a *collection of variables*. That reference never changes. **The variables the reference refers to change**. – Eric Lippert Nov 03 '15 at 15:37
  • 1
    Think about it this way. You write on a piece of paper "MyUserName's sock drawer". You make a photocopy of that piece of paper and hand it to a friend. The friend looks at their piece of paper, goes to the drawer named, and puts socks in the drawer. Did your piece of paper change? No. Did the sock drawer that the piece of paper refers to change? Yes. **That is how it is supposed to work**. – Eric Lippert Nov 03 '15 at 15:39

2 Answers2

5

When you pass an array to a method (or any reference type), you're passing a reference to the memory where that array exists. So making a change to the array will behave exactly the same as if you'd made that change in the method in which the array was originally instantiated.

Unfortunately, there's no way to make an array read-only in C#. However, you could create a wrapper class as described here that provides a mechanism for accessing the values in the array, without providing a way to change the array. Then you could pass that wrapper class into Function instead of passing the array itself.

public class ReadOnlyTwoDimensionalArray<T>
{
    private T[,] _arr;
    public ReadOnlyTwoDimensionalArray(T[,] arr)
    {
        _arr = arr;
    }
    public T this[int index1, int index2]
    {
        get {return _arr[index1, index2];}
    }
}

Then:

    static void Main(string[] args)
    {
        string[,] string_variable = new string[1, 1];
        string_variable[0, 0] = "unchanged";
        Console.Write("Before calling the method string variable is {0}.\n", string_variable[0,0]);
        Function(new ReadOnlyTwoDimensionalArray<string>(string_variable));
        Console.Write("Can't touch this: {0}.\n", string_variable[0, 0]);
        Console.ReadKey();
    } 
    private static void Function(ReadOnlyTwoDimensionalArray<string> method_var)
    {
        // the compiler will complain if you try to do this:
        //method_var[0, 0] ="changed"; 
        // but this works just fine
        Console.Write("Inside the method string variable is {0}.\n", method_var[0, 0]);
    }

Alternatively, you could make sure that you only give Function a copy of the original array. But that obviously has some performance implications.

Response to Edits

The example you give to show how string variables work isn't really equivalent to the original. In that second example, you are changing the value of the variable locally, but that value is just a memory address--you're not actually changing the string itself. You could do the same thing with an array like this:

    private static void Function(string [,] method_var)
    {
        method_var = new string[1, 1] {{"changed"}};
        Console.Write("Inside the method string variable is {0}.\n", method_var[0, 0]);
    }

By doing this, you are changing the value of the method_var variable, not the values in the array that it is pointing to.

Eric Lippert's comments below this post explain very clearly how C/C++ can give you read-only behavior on an array, but won't allow you to change the array's values locally without also changing them in the array that the calling method is referencing. He rightly points out that this is not a limitation of C#: it is a fundamental principle of how memory allocation works. Values passed from one method to another can either be passed by copying all their contents or by just copying a reference to their contents. For small values, you can have a value type, and C# will pass their entire value. For larger values like arrays, it would be expensive and error-prone to copy their entire value, so the language will not attempt to do this automatically--you must do it explicitly.

Community
  • 1
  • 1
StriplingWarrior
  • 151,543
  • 27
  • 246
  • 315
  • Thanks for answer.I suppose using a wrapper class will also decrease the performance,isn't it ? I am shocked with limitation of C# ! – Sadegh Nov 02 '15 at 18:15
  • @MyUserName: A wrapper class will have a tiny impact on performance: not enough to worry about in the vast majority of cases. C# does have `ImmutableArray`s, but they only work on a single dimension. What language and language feature are you accustomed to using for this sort of thing? – StriplingWarrior Nov 02 '15 at 18:58
  • I read the info in link but I am little confused.I need a simple example to understand it.Can be nice if anyone can show this idea on my example (or any other simple example). – Sadegh Nov 02 '15 at 22:29
  • Didn't get your question : "What language and language feature are you accustomed to using for this sort of thing?" – Sadegh Nov 02 '15 at 22:37
  • @MyUserName: See my updated answer for an example. I was just just curious, because you were so "shocked" at this language limitation, to know what language you use that doesn't have this limitation? – StriplingWarrior Nov 02 '15 at 23:14
  • I used C and C++ in past and as far as I remember, there was no limitation like this. – Sadegh Nov 03 '15 at 09:57
  • Well, seems like the problem didn't solve as it is not possible to change the variable inside the method. So if I want to use the variable inside the method, I need to make a copy of it.isn't it? If I didn't want to change the variable inside the method, then my own code also works fine(if you change method_var[0, 0] ="changed"; to comment, then it is the same. – Sadegh Nov 03 '15 at 14:20
  • I understand the fact that variable is read-only in your code and it is protected, but it can be much better if instead of having a read-only, it become read-only only in its scope and it is possible to modify it inside the method without affecting the main method. – Sadegh Nov 03 '15 at 14:21
  • 1
    @MyUserName: I assure you that C and C++ have the same feature; if you pass an array to a method as a pointer, then the pointer can be dereferenced to produce a variable, and the variable can be modified. Now, C and C++ do have a `const` feature which allows *the called function* to say "I promise not to modify these variables", but then you cannot write the code you want to write, because *you modify the variable*. It seems you want to have a variable that can be modified without changing; that's obviously a contradiction. – Eric Lippert Nov 03 '15 at 14:25
  • @eric-lippert I want my variable to remain unchanged when I call it in method, so the scope of variable will be method and it wont affect the main method. Try to read the question again so it may become more clear – Sadegh Nov 03 '15 at 14:30
  • 2
    @MyUserName: But arrays are *how you pass around a variable* in C#. You are talking like this is a bad thing, but everything is working as it was specifically designed to work. If you want to make a collection that cannot be changed by the callee then pass an **immutable** collection. If you want to pass a collection that can be changed, but is a *different* set of variables than those in the caller, pass a **copy**. If you want the caller to be able to change the variables but the callee can only read them, pass a **ReadOnlyCollection**. – Eric Lippert Nov 03 '15 at 14:48
  • @eric-lippert Arrays doesn't work exactly like variables in C# . Simply changing 1x1 dimension string array to string variable will lead to a type of code that I expect.I update my question to illustrate my idea in more clear way. – Sadegh Nov 03 '15 at 15:18
  • @MyUserName: I've updated my post to address your edits. If you change the value of a parameter, you're really just changing the value of the reference that was copied from the calling method. If you change the value of an array, you're changing the memory that the calling method was referencing. If you want to be able to modify the state of an object or array in one method, but want the original to remain untouched, there *has to* be a copy. This is not a limitation of C#: it's just how computers work. – StriplingWarrior Nov 03 '15 at 16:13
0

I'd just simply use Copy() to get a new instance with the same content:

string[,] newArray = new string[1, 1];
Array.Copy(string_variable , 0, newArray, 0, string_variable.Lenght);
Function(newArray);
W0lfw00ds
  • 2,018
  • 14
  • 23
  • This modification leads to this error: `Argument 1: cannot convert from 'string[*,*]' to 'string'` – Sadegh Nov 02 '15 at 22:05
  • also as @StriplingWarrior said, copying will decrease the performance. specially considering the fact that I need my code to compare performance of algorithms in term of space and time. – Sadegh Nov 02 '15 at 22:17
  • @MyUserName: If you are comparing performance of algorithms then you must *not* measure the costs associated with setting up the test, which includes creating the data structures that will be consumed by the tested algorithm. – Eric Lippert Nov 03 '15 at 00:22
  • @eric-lippert I am not completely agree with you. Your comment is Ok for this code, but in my code, I need to call a method inside another method.In this case it will get very complicated to calculate the cost, isn't it? – Sadegh Nov 03 '15 at 09:17