方法将变量更改为其范围

时间:2015-11-02 17:47:47

标签: c# arrays multidimensional-array

在这一小段代码中,更改方法内部的二维数组会导致更改main方法中的变量。

这是什么原因以及如何保护main方法中的变量保持不变?

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]);
        }
    } 
} 

最后这是程序输出:

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.

编辑1:我想到的一个问题是:有哪些其他常见的编程语言没有这类问题?

编辑2:为了进行比较,我用字符串变量而不是数组编写了这个相同的代码,输出正如预期的那样正常:

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);
        }
    } 
}   

,此代码的输出为:

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.

最后编辑:感谢大家的澄清,希望将来对其他人有用。

2 个答案:

答案 0 :(得分:5)

将数组传递给方法(或任何引用类型)时,您将对该数组所在的内存传递引用。因此,对数组进行更改的行为与在最初实例化数组的方法中进行更改的行为完全相同。

不幸的是,没有办法在C#中将数组设为只读。但是,您可以创建一个包装类as described here,它提供了一种访问数组中值的机制,而无需提供更改数组的方法。然后你可以将该包装类传递给Function而不是传递数组本身。

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];}
    }
}

然后:

    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]);
    }

或者,您可以确保只提供原始数组的Function 副本。但这显然有一些性能影响。

对编辑的响应

您给出的用于显示字符串变量如何工作的示例并不等同于原始字符串变量。在第二个示例中,您将在本地更改变量的值,但该值只是一个内存地址 - 您实际上并未更改字符串本身。你可以用这样的数组做同样的事情:

    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]);
    }

通过执行此操作,您将更改method_var 变量的值,而不是其指向的数组中的值。

Eric Lippert在本文下面的评论非常清楚地解释了C / C ++如何在数组上为您提供只读行为,但是不允许您在本地更改数组的值而不在调用方法的数组中更改它们正在引用。他正确地指出这不是C#的限制:它是内存分配如何工作的基本原则。从一个方法传递到另一个方法的值可以通过复制其所有内容或仅复制对其内容的引用来传递。对于较小的值,您可以使用值类型,C#将传递其整个值。对于像数组这样的较大值,复制它们的整个值会很昂贵且容易出错,因此语言不会自动尝试这样做 - 你必须明确地这样做。

答案 1 :(得分:0)

我只是使用Copy()来获取具有相同内容的新实例:

string[,] newArray = new string[1, 1];
Array.Copy(string_variable , 0, newArray, 0, string_variable.Lenght);
Function(newArray);