【问题标题】:C# passing parameters by referenceC#通过引用传递参数
【发布时间】:2010-10-11 17:07:05
【问题描述】:

我有下面的一段代码,它在字符串数组的每个成员的开头添加了一个字符串前缀。 IE。以“z”为前缀的 ["a","b","c"] 变为 ["za","zb","zc"]。

private string[] Prefix(string[] a, string b) {
   for(int i = 0;i < a.Length;i++) {
     a[i] = b + a[i];
   }
  return a;
}

该函数工作正常(尽管如果有更好的方法来做到这一点,我很高兴听到它),但我在传递参数时遇到了问题。

string[] s1 = new string[] {"a","b"};
string[] s2 = Prefix(s1,"z");

现在据我所知,我正在通过值传递 s1。但是当 Prefix 函数完成时,s2 和 s1 具有相同的值 ["za,"zb"],或者 s1 是通过引用传递的。我确定你必须在 c# 中显式声明这种行为,我很困惑.

【问题讨论】:

    标签: c# parameter-passing


    【解决方案1】:

    正如其他人所说,引用是按值传递的。这意味着您的s1 引用被复制到a,但它们仍然引用内存中的同一个对象。我要修复你的代码是这样写的:

    private IEnumerable<string> Prefix(IEnumerable<string> a, string b) {
       return a.Select(s => b + s);
    }
    

    .

    string[] s1 = new string[] {"a","b"};
    string[] s2 = Prefix(s1,"z").ToArray();
    

    这不仅解决了您的问题,还允许您使用列表和其他字符串集合,除了简单的数组。

    【讨论】:

      【解决方案2】:

      C# 和之前的 Java 一样,默认按值传递所有内容。

      但是,对于引用类型,该值一个引用。请注意,stringarray 都是引用类型。

      【讨论】:

        【解决方案3】:

        这里有一个更好的方法:

        private string[] Prefix(string[] a, string b) {
          return a.Select(s => b + s).ToArray();
        }
        

        甚至:

        private IEnumerable<string> Prefix(IEnumerable<string> a, string b) {
          return a.Select(s => b + s);
        }
        

        【讨论】:

          【解决方案4】:

          您正在通过值传递对s1 的引用。换句话说,您的 a 参数(在 Prefix 函数范围内)和您的 s1 变量是对同一个数组的引用。

          【讨论】:

            【解决方案5】:

            字符串是不可变的

            这意味着当您将一个字符串附加到一个字符串时,您会得到一个全新的字符串 - 出于性能原因。制作一个重新分配现有数组的新字符串会更便宜。

            因此感觉就像是按值处理字符串

            在 c# 中,所有引用类型默认通过引用传递 - 即在堆上创建的类而不是值。

            【讨论】:

            • 所述对象的“引用”是按值传递的(n个引用可以引用同一个对象)。使用 ref/out 关键字引入了传递引用语义
            【解决方案6】:

            实际上,对s1 的引用按值传递给Prefix()。虽然您现在有两个不同的引用,但 s1s2 仍然引用同一个字符串数组,因为数组是 C# 中的引用类型。

            【讨论】:

              【解决方案7】:

              按值传递并不意味着你不能改变事情。 对象是按值传递的,但值(实际上)是指向对象的指针。 数组是对象,传入数组的指针。如果在方法中更改数组的内容,数组将反映更改。 字符串不会发生这种情况,因为字符串是不可变的——一旦构造,它们的内容就不能改变。

              【讨论】:

                【解决方案8】:

                string 数组的引用 按值传递。 因此,调用方法中的原始数组引用无法更改,这意味着Prefix 方法中的a = new string[10] 不会影响调用方法中的s1 引用。但是 array 本身是可变的,并且对 same 数组的重复引用能够以对相同的任何其他引用可见的方式对其进行更改数组。

                【讨论】:

                • 虽然在技术上是正确的,但这是令人困惑的,因为 c# 不包含 c 中的指针和引用的概念。在 c# 中,您实际上是通过引用传递 - 没有人关心它是被引用的内存位置的值。尽管似乎大多数人毕竟是这样想的。奇怪的是它更复杂。
                • @John Nicholas:C#肯定有引用的概念。它也有不安全代码中指针的概念,但这里不相关。
                • 关键词是'as they are in c'。
                • @John,按引用传递按值传递引用根本不同。您将两者等同起来,但它们根本不相等,而且永远不会相等。最简单的演示是void Bar(Foo foo)void Bar(ref Foo foo)。如果在方法体内有foo = new Foo();,哪个方法会导致调用站点发生变化?
                • @John,为了完整起见,不必将其设置为 new 实例,只需将其设置为 某种东西,而不是它已经存在的东西。 foo = null;foo = someOtherFoo; 将演示 ref 参数和标准之间的相同差异。
                【解决方案9】:

                传递一个对象的值。对于“引用”对象,这是引用的值。 不会对基础数据进行克隆/复制/复制。

                要解决观察到的问题,只需不要改变输入数组 - 相反,创建一个新的输出数组/对象并适当地填充它。 (如果你必须使用数组,我可能会使用这种方法,因为无论如何它都非常无聊。)

                或者,您可以先克隆输入数组(这也会创建一个新对象)。在这种情况下使用克隆(这是一个浅拷贝)是可以的,因为内部成员(字符串)本身是不可变的——即使它们是引用类型,底层对象的值一旦创建就不能更改。对于嵌套的可变类型,可能需要更加小心。

                这里有两种方法可以用来创建浅拷贝:

                string[] B = (string[])A.Clone();
                
                string[] B = (new List<string>(A)).ToArray();
                

                不包括在内。

                不过,我个人会使用 LINQ。这是一个预告片:

                return a.Select(x => b + x).ToArray();
                

                【讨论】:

                  猜你喜欢
                  • 1970-01-01
                  • 2012-01-04
                  • 2010-10-07
                  • 2011-10-07
                  • 2016-05-13
                  • 1970-01-01
                  • 2011-08-21
                  • 2017-09-25
                  • 1970-01-01
                  相关资源
                  最近更新 更多