【问题标题】:Should the argument be passed by reference in this .net example?在这个 .net 示例中,参数是否应该通过引用传递?
【发布时间】:2010-05-20 18:30:02
【问题描述】:

我使用过 Java、C++、.Net。 (以该顺序)。当被问及面试中的按价值与按参考时,我在这个问题上总是做得很好……也许是因为没有人深入研究它。现在我知道我没有看到全貌。

我在看别人写的这段代码:

XmlDocument doc = new XmlDocument();
AppendX(doc); // Real name of the function is different
AppendY(doc); // ditto

当我看到这段代码时,我想:等一下,我不应该在doc 变量前面使用ref 吗(并相应地修改AppendX/Y?它按写的那样工作,但让我质疑我是否真正理解C#中的ref关键字。

当我更多地思考这个问题时,我想起了早期的 Java 时代(大学介绍语言)。我的一个朋友看了我写的一些代码,他有一个心理障碍——他一直问我哪些东西是通过引用传递的,什么时候通过值传递的。我无知的反应是这样的:老兄,Java 中只有一种 arg 传递,我忘记了它是哪一种 :)。冷静,不要想太多,直接写代码。

Java 仍然没有ref 是吗?然而,Java 黑客似乎很有生产力。无论如何,用 C++ 编码让我接触到了这整个通过引用的业务,现在我很困惑。

上面的例子中应该使用ref吗?

我猜当ref 应用于值类型时:原语、枚举、结构(此列表中还有其他内容吗?)它会产生很大的不同。而且......当应用于对象时,它不会因为它都是引用。如果事情这么简单,那么编译器为什么不将ref 关键字的使用限制为类型的子集。

当涉及到对象时,ref 是否用作评论之类的?好吧,我确实记得null 可能会出现问题,ref 对于在一个方法中初始化多个元素也很有用(因为你不能像在 Python 中那样简单地返回多个东西)。

谢谢。

【问题讨论】:

  • ref 参数添加到方法是您应该仅在需要时使用的东西。您的默认想法应该是避免使用ref 参数。如果它按书面方式工作,请不要添加ref。顺便说一句,人们通常可以避免使用 ref,因为以下两个 sn-ps 通常会给出相同的结果(给定 foo 的合理实现):foo(ref x);x = foo(x);。就个人而言,当有选择时,我将以第二种方式实现 foo 。因此,Java 程序员可以在没有它的情况下生存也就不足为奇了。
  • @Brian,当您拥有x = foo(x); 时,您的x 也可能是不可变的(如string)。现在......当一种方法需要“返回”几个变量并且最好一起计算它们,因为它们是相关的......那么你会想要使用 ref,还是不使用?答案是否会根据参数是否不可变而改变?我想会的。
  • 如果需要返回多个变量,可以使用ref来实现。另一种方法是返回一个包含您希望返回的所有内容的类的实例。这通常但并不总是更好。请注意,您也可以使用out 参数代替ref。如果您希望返回多个变量但实际上不需要传递它们,out 更好。例如,foo(out x); 类似于 foo(ref x);,但应与未初始化的 x 一起使用。

标签: c# pass-by-reference


【解决方案1】:

你并不孤单。

默认情况下,C# 中的所有内容都是按值传递的 - 但在引用类型(例如 XmlDocument)的情况下,该值是引用。

ref 关键字用于指示参数是“通过引用传递”,并且它也必须在调用站点指定。 Java 没有任何等价物 - Java 中的所有内容都是按值传递的。

请查看我的article on parameter passing 了解更多详情。

【讨论】:

  • 我知道您喜欢反馈,所以我认为您的文章不错,但它对于 .Net(我猜是 Java)世界是“沙盒”。另一篇文章提到了 C++,打开了一罐蠕虫,这就是我在这个特定问题上所寻求的。在认为这是你的错之前,请考虑一下:理查德·费曼(Richard Feynman),一个讲过很多课并热衷于教学和解释的聪明人承认,没有单一的教学方法,因为学生们思考和吸收的方式是如此多样化.对他儿子有用的东西对他女儿没用。太糟糕了,我找不到链接。
  • @Hamish:是的,我的文章特意专门针对 .NET。正如你所说,有很多其他语言的蠕虫罐头。从试图教别人 C# 行为的角度来看,我发现最好把这些罐子关起来:)
【解决方案2】:

对象类型总是通过引用传递(实际上引用是通过值传递的)。整数类型可以通过引用或值传递。

【讨论】:

  • 括号里的那位是正确的——但前面的那位不是。
  • @Jon Skeet:是一回事,对吧?如果您传递对对象的引用,那么从逻辑上讲,您是通过引用传递对象。 ref 通过引用传递一个变量,而不是变量引用的对象。 (作为记录,我对使用 ref 的作用以及不使用 ref 时的传递语义有一个准确的理解。我只是想知道你的术语。)
  • @Joren:不,通过引用传递与传递引用不同。 “通过引用传递”语义意味着对参数本身所做的更改是对参数进行的。现在在这种情况下,谈论传递的对象本身是不准确的,因为在 Java 和 C# 中,没有表达式的类型 实际上 是一个对象 - 它只是 only一个参考。我发现完全不使用传递实际 object 的想法来解释这个主题要容易得多。
  • @Jon:我们根本不讨论变量,而是讨论任意表达式。如果我用 S 类型的某个表达式调用一个方法,其中 S 是一个结构,那么该表达式的值是“按值传递”。由于 S 是值类型,因此对象(定义为“S 的实例”的“对象”)按值传递。如果我用 C 类型的某个表达式调用该方法,而 C 是一个类,那么表达式的值仍然是按值传递的。但是由于 C 是一个引用类型,所以这种情况下的值是对一个对象的引用。然后使用引用来处理该对象,我称之为“通过引用传递”。
  • 我想我们都同意这与使用ref 完全不同。 (ref 所做的讨论也远没有那么普遍,因为ref 只能用于变量,创建一种完全不同类型的表达式)这就是为什么我会说ref 被称为' ref',因为它创建了对 变量 的引用,而不是说任何关于值、对象等的内容。我特别喜欢 Eric Lippert 对行为的解释,并在心理上假装“ref”这个词是“别名”的同义词。
【解决方案3】:

接受ref 传递的引用类型变量的方法可能会改变变量指向的东西——不仅仅是修改它。

【讨论】:

    【解决方案4】:

    http://www.albahari.com/valuevsreftypes.aspx 很好地解释了 .Net 中引用类型和值类型之间的区别。

    【讨论】:

      【解决方案5】:

      不,不要使用 ref 关键字。您想使用文档的内容,而不是更改文档本身。请参阅此帖子以获得进一步的解释:ref keyword

      【讨论】:

        【解决方案6】:

        这取决于您希望 AppendX 做什么。如果是修改对象的内容,则不需要通过 ref 传递。如果您希望 AppendX 能够更改变量“doc”指向的对象,您需要这样做。 “doc”已经是一个“引用类型”,相当于它是一个指向 c++ 中对象的指针。

        【讨论】:

          【解决方案7】:

          我和一些同事争论过这个问题,最终输了;以下是我如何学会按照他们的方式做事。

          refout 关键字的意思是“我可能/将用新的引用替换当前的引用”。调用该方法后,变量可能会引用一个完全不同的对象。另一方面,众所周知,对于引用类型,该类型的属性可能会发生变化,如果它们很重要,您应该缓存它们。

          我并不完全同意,但有一些东西来表示“参考将改变”绝对是有意义的。

          【讨论】:

            【解决方案8】:

            你并不是唯一一个在 c++ 和 c# 中感到困惑和编码的人,当你在参数上使用 & 传递时,我可以理解为什么(不是批评而是评论,因为这也是我生活的世界)您基本上是在说这是一个别名,我将在方法内部使用该别名来传递传递给该方法的参数。 您对该参数所做的任何事情都将产生与您使用它自己的变量相同的效果。 所以在代码中你可以这样做: void Foo(MyClass& arg) { arg = new MyClass(1); }

            int x = new MyClass(0);
            Foo(x);
            

            int x = new MyClass(0);
            void Foo()
            {
               x = new MyClass(1);
            }
            

            在任何一种情况下,x 现在都等于 MyClass(1)(并且您有泄漏,因为您无法获得原始数据,但这不是我的意思)。我猜你的问题你已经知道了,但无论如何它都会有目的的:)

            如果你在标准中传递了一个引用,那就是这个引用是按值传递的。它不再是别名,您对被引用的对象所做的一切都会影响该对象,但如果您对引用该对象的变量做任何事情(例如分配一个新对象),那么这只会影响引用的副本。让我们再写一些代码

            c#

            MyClass x = MyClass(0);
            void Foo(MyClass arg) //reference being passed by val
            {
              arg = new MyClass(1);
            }
            Foo(x);
            

            x 仍然等于 MyClass(0)

            MyClass x = MyClass(0);
            void Foo(ref MyClass arg) //passing by ref
            {
              arg = new MyClass(1);
            }
            Foo(ref x);
            

            x 等于 MyClass(1)

            因此,在 C# 中传递的标准参数与在 C++ 中传递引用不同,但使用 ref 关键字(不鼓励这样做)可以让您接近与 C++ 中的 & 相同的机制 由于优化/缺乏复制,通常鼓励 C++ 中的 &,因为您只复制 C# 中的引用,这不是一个问题,并且 ref 仅应在您确实需要传递给方法的变量的别名时使用,也就是当您可能必须为变量分配一个新的对象实例,而不是使用/更改对象的状态

            【讨论】:

              【解决方案9】:

              如果您之前用 C++ 编写过代码,那么您必须熟悉指针。 .NET 代码和 Java 中的对象引用是引擎盖下的指针。它只是没有明确地用一种语法明确地写出来,这使得它很明显是一个指针,你应该记住它。规则并不难,任何引用类、数组、字符串或 System.Object 对象的变量都是引用类型,变量是指针。其他任何东西都是值类型,变量包含实际值。

              当您将这样的变量传递给方法时,您传递的是指针值。然后,该方法可以按照它认为合适的方式修改指向的对象。通过引用传递指针没有任何区别,它仍然是同一个指针,指向同一个对象。

              当您传递值类型的值时,这完全不同。如果您希望调用方法修改该值,则必须生成指向该值的指针。您可以在方法声明中使用“ref”关键字。

              异常情况是您希望调用方法返回一个新对象。也就是说,修改指针值。 然后你必须使用 ref 关键字,它创建一个指向指针的指针。您通常会通过让方法将对象作为方法返回值返回来避免这种情况。

              【讨论】:

              • 谢谢。 .Net 框架是否处理适合 64 位的值类型和不适合的值类型?如果是这样,那到底有什么区别?
              • 只有 JIT 编译器关心。是否在 CPU 寄存器中填充 64 位值取决于处理器类型。
              【解决方案10】:

              很多人对此感到困惑。这就是我的想法。我根本不认为“参考”的意思是“通过参考”。我认为“ref”的意思是“别名”。也就是说,当你说

              void M(ref int x) { x = 123; }
              void N(int z) { ... }
              ...
              int y = 456;
              M(ref y);
              N(y);
              

              “ref y”的意思是“请使相应的形式参数 x 成为变量 y 的 别名”。也就是说,x 和 y 现在都是相同存储位置的变量。当您写信给 x 时,您正在写信给 y,因为 x 是 y 的另一个名字。

              当你在没有 ref 的情况下通过时,如在 N(y) 中,你是在说“y 和 z 是两个不同的变量,因此 z 以与 y 相同的内容开始其生命周期”。

              一旦您开始这样思考,您就不必担心 pass-by-ref 与 pass-by-value,等等等等,这一切都非常令人困惑。主要区别在于普通传递创建一个新变量并使用参数初始化它,而ref 为现有变量创建别名

              我希望我们使用“alias”而不是“ref”;它会更清楚。

              【讨论】:

              • 我更喜欢参考。它更符合我现有的知识(即 ref 表示您通过 by reference 而不是 by value 传递您的参考)。
              猜你喜欢
              • 2013-11-08
              • 1970-01-01
              • 2014-03-08
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2016-12-02
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多