【问题标题】:Why are objects automatically passed by reference?为什么对象会自动通过引用传递?
【发布时间】:2014-05-27 07:17:03
【问题描述】:

在 C# 的传递引用和传递值概念的上下文中,我有一个关于深复制和浅复制的一般性问题:

在 C# 中,需要显式创建接受指针/引用的方法才能将其传递给方法。但是,至少作为参数传递给方法/构造函数的对象的行为与其他对象不同。如果没有按照此处所述进行额外克隆,它们似乎总是通过引用传递:http://zetcode.com/lang/csharp/oopii/

为什么对象会自动通过引用传递? 在这些情况下,强制克隆过程而不是像 int、double、boolean 等那样处理对象有什么特别的好处吗?

这是说明我的意思的代码示例:

using System;

public class Entry
{

    public class MyColor
    {
        public int r = 0;
        public int g = 0;
        public int b = 0;
        public double a = 1;

        public MyColor (int r, int g, int b, double a)
        {
            this.r = r;
            this.g = g;
            this.b = b;
            this.a = a;
        }
    }

    public class A
    {
        public int id;
        public MyColor color;
        public MyColor hiddenColor;

        public A (int id, MyColor color)
        {
            this.id = id;
            this.color = color;
        }
    }

    static void Main(string[] args)
    {
        int id = 0;
        MyColor col = new MyColor(1, 2, 3, 1.0);

        A a1 = new A(id, col);
        A a2 = new A(id, col);

        a1.hiddenColor = col;
        a2.hiddenColor = col;

        a1.id = -999;
        id = 1;
        col.a = 0;

        Console.WriteLine(a1.id);
        Console.WriteLine(a2.id);

        Console.WriteLine(a1.color.a);
        Console.WriteLine(a2.color.a);

        Console.WriteLine(a1.hiddenColor.a);
        Console.WriteLine(a2.hiddenColor.a);
    }
}

这会导致:

-999
0
0
0
0

MyCol 的实例始终按引用传递,而其他参数按值传递。我必须在 MyColorA 类中实现 ICloneable。另一方面,'ref'-statement 出现在 C# 中,它应该用于显式允许和执行 pass-by-reference。

欢迎提出建议!

【问题讨论】:

  • 并非所有实例都自动通过引用传递。结构实例是按值传递的——看起来你的类应该是一个结构,因为它是一组颜色值。
  • @BoltClock,实际上所有类型都是按值传递的(包括引用类型),除非另有说明。对于引用类型,它只是意味着传递的值是一个引用,但它仍然是按值传递的:为参数分配一个新的引用不会影响调用者。
  • @BoltClock,我同意它一开始令人困惑,但区别很重要,所以我认为最好准确地解释它;)
  • @ThomasLevesque 我同意这种区别很重要,但是关于它的迂腐主义有点愚蠢。对象不是按值传递的。相反,按值传递的是对对象的引用。说“一个对象是按值传递的,但值是对象以外的东西”是没有意义的。只要传递给函数的是对象的引用,声称“对象不是通过引用传递”是愚蠢的。
  • 要真正了解发生了什么,您需要了解指针。对象引用只不过是一个指针。通过引用传递一个值会传递一个指向该值的指针。 C 语言是一门好学的语言,它迫使您考虑指针,并且几乎没有将机器抽象出来。这就是为什么它是一种快速的语言。还有一个危险的。

标签: c# clone pass-by-reference pass-by-value shallow-copy


【解决方案1】:

为什么对象会自动通过引用传递?

他们不是。

在这些情况下,强制克隆过程而不是处理更像 int、double、boolean 等的对象有什么特别的好处吗?

引用类型没有“克隆过程”,只有值类型。

我认为您混淆了不同的概念:

  • 值类型与引用类型

    对于值类型(例如原始数值类型、枚举和DateTime 等结构),变量的值就是对象本身。将变量分配给另一个变量(或通过值将其作为参数传递)会创建对象的副本。

    对于引用类型(如objectstring、类(非结构)等),变量的值是对对象的引用。将变量分配给另一个变量(或通过值将其作为参数传递)会创建引用的副本,因此它仍然引用相同的对象实例。

  • 按值传递参数与按引用传递参数

    按值传递参数意味着您传递值的副本。根据它是值类型还是引用类型,这意味着对象本身的副本或引用的副本。如果被调用者修改了作为参数传递的值类型的成员,则调用者将看不到更改,因为被调用者正在处理副本。另一方面,如果被调用者修改了作为参数传递的引用类型的成员,调用者将看到更改,因为被调用者和调用者都引用了同一个对象实例。

    通过引用传递参数意味着你将引用传递给一个变量(可能是值类型或引用类型的变量)。该值不被复制:它在调用者和被调用者之间共享。因此,被调用者所做的任何更改(包括为参数分配新值)都会被调用者看到。

    除非另有说明(使用refout 关键字),所有参数都按值传递,包括引用类型。只是对于引用类型来说,传递的值是引用,但还是按值传递。

我建议你阅读 Jon Skeet 的文章 Parameter passing in C# 以获得更好的解释。

【讨论】:

  • 很好解释。这个问题已经被问过很多次了,但人们仍然混淆了这些概念。我希望有一些其他术语,因为“引用类型”与“通过引用”相混淆。
  • 所以,对我来说真正令人困惑的部分是对传递的值(实际值或引用)的不同解释,具体取决于参数/变量类型。如果我进一步使用refout 传递对对象的引用,这会完全过时吗?我尝试通过引用(ref)将引用值MyColor-instance 传递给A-instance 之一,结果与传递引用值相同。
  • @FlorianR.Klein,只要被调用者没有为参数分配任何东西,引用类型就没有区别。但是如果它分配了一个不同的对象,那么调用者的变量也会指向那个对象。这是通过引用传递引用类型有用的唯一情况。
  • 再怎么强调 Skeet 的文章都不够,尤其是“对象引用默认按值传递”这一行,一旦你明白在自编程语言中查看这种行为的起源会很有帮助: en.wikipedia.org/wiki/Self_(programming_language)
【解决方案2】:

所有方法参数都按值传递,除非您使用refout 关键字明确指定它们应通过引用传递。这意味着如果您将变量传递给方法参数,那么变量的内容将被复制并传递给方法。

如果变量是值类型,这基本上意味着struct,那么变量包含一个对象,因此该对象被复制。如果变量是引用类型,这基本上意味着class,那么变量包含对对象的引用,因此引用被复制。

如果您将参数声明为refout,则会创建对该变量的引用并将其传递给方法。如果变量包含一个对象,则创建对该对象的引用,如果变量包含引用,则创建对该引用的引用。

【讨论】:

  • 如果使用 ref 关键字将引用类型的对象传递给方法,您能否告知会发生什么?
  • 我的意思是我写的,我写的也是我的意思。如果你读过我写的,那么你就知道我的意思了。如果没有区别,那么ref 关键字将不存在。
【解决方案3】:

我会改写你的问题:为什么我们需要课程?我们不能只有结构吗?

并非所有对象都可以安全复制。例如,您不能在逻辑上复制 FileStreamButton。这些对象具有标识,您希望所有代码都引用唯一的对象。

【讨论】:

    【解决方案4】:

    类或接口类型(统称为“引用类型”)的变量、参数或字段不包含类对象;它拥有一个对象标识符。同样,引用类型的数组也不包含对象;它包含对象标识符。

    尽管 .NET 中的对象没有任何与人类可读的标识符相关联,但对它们进行推理可能会有所帮助:如果在课程中至少创建了一些(例如 592)个对象在程序执行中,恰好一个对象将是第 592 个创建的对象;一旦创建了第 592 个对象,就不会再有其他对象成为第 592 个对象。没有办法找出哪个对象是第 592 个对象,但是如果一个持有对第 592 个对象的引用的变量作为非 ref 参数传递给某个方法,那么当该方法时,它将继续持有对第 592 个对象的引用返回。如果对象#592 是对红色的Car 实例的引用,局部变量myCar 持有“对象ID #592”,并且调用方法PaintCar(myCar);,则该方法将接收Object #592" .如果该方法将汽车涂成蓝色,那么当它返回时,myCar 将持有“Object #592”,它将识别一辆蓝色汽车。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2015-06-18
      • 1970-01-01
      • 1970-01-01
      • 2016-12-09
      • 2011-09-04
      • 2017-09-11
      • 2012-03-18
      相关资源
      最近更新 更多