【问题标题】:Why Can I Change Struct's int[] Property from Method Without Specifying "ref"?为什么我可以在不指定“ref”的情况下从 Method 更改 Struct 的 int[] 属性?
【发布时间】:2011-02-06 08:19:25
【问题描述】:

通过一个方法,我可以传递一个包含整数数组的结构,并更改数组中的值。我不确定我是否完全理解为什么我能做到这一点。有人可以解释一下为什么我可以更改存储在 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;
    }

【问题讨论】:

    标签: c# struct pass-by-reference


    【解决方案1】:

    在 C# 中,引用是按值传递的。 数组在传递给方法或存储在另一个类的实例中时不会被复制。 - 传递了对数组的引用。这意味着接收对数组的引用(直接或作为另一个对象的一部分)的方法可以修改该数组的元素。

    与 C++ 等语言不同,您不能在 C# 中声明“不可变”数组 - 但是您可以使用 List 等具有只读包装器的类来防止修改集合。

    【讨论】:

    【解决方案2】:

    通过一个方法,我可以传递一个包含整数数组的结构,并更改数组中的值。我不确定我是否完全理解为什么我可以这样做。

    数组被定义为变量的集合

    根据定义,变量可以更改。这就是为什么我们称它们为“变量”。

    因此当你传递一个数组时,你可以改变内容;数组的内容是变量

    为什么我可以在不指定“ref”的情况下更改结构的 int[] 属性?

    请记住,正如我们之前在另一个问题中讨论的那样,您使用 ref 来为变量创建别名。这就是“ref”的用途——为变量制作别名。 (不幸的是,关键字是令人困惑的“ref”——将其设为“别名”可能会更清楚。)

    【讨论】:

    • 我的问题是我忘记了数组是引用类型,而结构只是保存指向数组的指针。当我在方法中抓取指向数组的指针时,我正在更改数组值。
    • 我不确定我是否同意您使用“别名”。看来你是在为一个共同的概念创造一个新词。
    • @AMissico:“别名”是事物的另一个名称。当你有 void M(ref int x) 并说 M(ref y) 时,x 成为 y 的别名——x 和 y 是同一个变量的两个名称。这个命名法不是我发明的;这是引用变量引用的行业标准方式。参见例如en.wikipedia.org/wiki/Pointer_alias
    • @Eric:虽然您的解释在技术上和往常一样正确,但这次似乎没有用。除非我错过了我的猜测,否则真正的问题是为什么调用者可以看到这些更改。结构的公共字段也是变量,也可以设置,但当然更改是在独立副本中进行的,因此调用者看不到。但是这些字段引用的对象并不是独立的。
    • @Ben:因为你没有改变结构的内容,你改变的是结构引用的数组的内容。如果结构具有另一个引用类型的成员,则行为将没有什么不同。如果您要更改数组变量 本身 的内容(即执行类似a.Map = new int[] { 3 } 的操作),那么这些更改将不会反映在调用方法中。
    【解决方案3】:

    来自MSDN

    不要返回数组的内部实例。这允许调用代码来更改数组。以下示例演示了访问 Path 属性的任何代码如何更改数组 badChars,即使该属性未实现 set 访问器。

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

    您无法通过将 badChars 数组设为只读(在 Visual Basic 中为只读)来更正前面示例中的问题。您可以克隆 badChars 数组并返回副本,但这会对性能产生重大影响。

    【讨论】:

    • 虽然通常是好的建议,但这并不能回答问题。
    【解决方案4】:

    虽然您的 SearchInfo 结构是值类型,但 .Map 字段包含引用,因为 Array 是引用类型。将此引用视为指向数组所在内存位置的地址。

    如您所知,当您将SearchInfo 的实例传递给方法时,SearchInfo 会被复制。并且副本自然包含指向相同数组的相同地址。

    换句话说,复制结构并不会复制数组,它只是复制指针。

    【讨论】:

      【解决方案5】:

      好吧,无论如何它都是通过引用传递的,就像 C# 中的所有引用类型一样。 不幸的是,C# 和 CLR 都不支持 constness,因此平台并不真正知道您是否可以更改它。所以,它有引用,它可以用它来改变值,没有什么可以阻止它这样做。

      顺便说一句,您可能会将其视为语言设计错误。这对用户来说是意想不到的。

      【讨论】:

      • 其实是按值传递的,只是传递的值是对数组等ref-types的引用。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-11-29
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多