方法将变量更改到其范围之外

本文关键字:范围 变量 方法 | 更新日期: 2023-09-27 18:20:19

在这段小代码中,更改方法内部的二维数组会导致更改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.

上一期:感谢大家的澄清,希望这对以后的其他人有用。

方法将变量更改到其范围之外

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

不幸的是,在C#中没有办法使数组只读。但是,您可以创建一个如本文所述的包装类,它提供了一种访问数组中值的机制,而不提供更改数组的方法。然后可以将该包装类传递到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#将传递它们的整个值。对于像数组这样的较大值,复制它们的整个值将是昂贵的,而且容易出错,因此该语言不会尝试自动执行此操作——必须显式执行。

我只需要使用Copy()来获得一个具有相同内容的新实例:

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