【问题标题】:methods change variable out of its scope方法更改变量超出其范围
【发布时间】:2015-11-02 17:47:46
【问题描述】:

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

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

【问题讨论】:

  • @MyUserName: 如果没有refout 关键字,您将永远无法更改传入参数的(例如method_var = null 不会更改@ 987654328@),但在 .NET 中,作为参数传递的引用类型对象的值是它的引用。没有关键字可以阻止您调用索引器或调用对象上可能更改其状态的方法。
  • 您的基本问题是数组是变量的集合。变量可以改变;这就是为什么它们被称为变量。如果你不喜欢数组是变量的集合,那么不要使用数组。还有许多其他数据结构不是变量的集合。
  • 至于您的更新:字符串不是变量的集合。字符串是字符的集合,而不是字符变量的集合。同样,数组是变量的集合,变量可以改变。同样,如果您不想更改某些内容,不要使用变量集合。他们改变了!
  • 我还注意到您的声明“更改方法内的二维数组会导致更改 main 方法中的变量。”完全是错误的。 string_variable 的值没有改变。它是一个参考。它指的是一个变量的集合。该参考永远不会改变。 引用所指的变量发生变化
  • 这样想。您在一张纸上写下“MyUserName 的袜子抽屉”。你复印那张纸,然后交给朋友。朋友看了看他们的纸,走到指定的抽屉前,把袜子放在抽屉里。你的纸条变了吗?不。这张纸所指的袜子抽屉有变化吗?是的。 它应该是这样工作的

标签: c# arrays multidimensional-array


【解决方案1】:

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

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

【讨论】:

  • 感谢您的回答。我想使用包装类也会降低性能,不是吗?我对 C# 的局限性感到震惊!
  • @MyUserName:包装类对性能的影响很小:在绝大多数情况下都不足以担心。 C# 确实有ImmutableArrays,但它们只适用于单一维度。你习惯用什么语言和语言特性来做这种事情?
  • 我阅读了链接中的信息,但我有点困惑。我需要一个简单的例子来理解它。如果有人能在我的例子(或任何其他简单的例子)中展示这个想法,那就太好了。
  • @MyUserName:我向你保证,C 和 C++ 具有相同的特性;如果将数组作为指针传递给方法,则可以取消引用指针以生成变量,并且可以修改变量。现在,C 和 C++ 确实有一个const 功能,它允许被调用函数 说“我保证不会修改这些变量”,但是你不能编写你想要编写的代码,因为 你修改变量。似乎您想要一个可以在不更改的情况下进行修改的变量;这显然是一个矛盾。
  • @MyUserName:但是数组是在 C# 中传递变量的方式。你说的好像这是一件坏事,但一切都在工作,因为它是专门为工作而设计的。如果你想创建一个不能被被调用者改变的集合,那么传递一个 immutable 集合。如果您想传递一个可以更改的集合,但它是一组不同的变量集,而不是调用者中的那些,请传递一个副本。如果您希望调用者能够更改变量但被调用者只能读取它们,请传递 ReadOnlyCollection
【解决方案2】:

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

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

【讨论】:

  • 这个修改导致这个错误:Argument 1: cannot convert from 'string[*,*]' to 'string'
  • 也正如@StriplingWarrior 所说,复制会降低性能。特别考虑到我需要我的代码来比较算法在空间和时间方面的性能。
  • @MyUserName:如果您要比较算法的性能,那么您必须衡量与设置测试相关的成本,其中包括创建将由以下人员使用的数据结构测试算法。
  • @eric-lippert 我不完全同意你的看法。您对这段代码的评论是好的,但是在我的代码中,我需要在另一个方法中调用一个方法。在这种情况下,计算成本会变得非常复杂,不是吗?
猜你喜欢
  • 1970-01-01
  • 2012-12-19
  • 2016-04-30
  • 1970-01-01
  • 2011-08-22
  • 2016-03-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多