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

10

关于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 接口。另一方面,在 C# 中存在 ´ref´ 语句,应该使用它来明确允许并进行引用传递。

欢迎提出建议!


并非所有实例都自动传递引用。结构体实例是按值传递的 - 看起来您的类应该是一个结构体,因为它是一组颜色值。 - BoltClock
5
@BoltClock,实际上,所有类型都是按值传递的(包括引用类型),除非另有规定。对于引用类型,这只意味着传递的值是一个引用,但它仍然是按值传递的:将新引用分配给参数不会影响调用者。 - Thomas Levesque
1
@BoltClock,我同意一开始会感到困惑,但是区分非常重要,所以我认为最好准确地解释一下 ;) - Thomas Levesque
1
@ThomasLevesque 我同意这个区别很重要,但是对此过于追求严谨有点儿可笑。对象并不是按值传递的,相反,传递的是一个指向对象的引用。说“对象按值传递,但值不是对象”是没有意义的。只要传递给函数的是对象的引用,就没有必要声称“对象不是按引用传递的”,这样做是可笑的。 - jalf
1
要真正理解正在发生的事情,您需要了解指针。对象引用只是一个指针。通过引用传递值会传递一个指向该值的指针。C语言是学习的好语言,它迫使您思考指针,并且很少将机器抽象化。这就是为什么它是一种快速语言,也是一种危险的语言。 - Hans Passant
显示剩余7条评论
4个回答

45
为什么对象会自动通过引用传递?
并不是这样的。
对于引用类型,没有“克隆过程”,只针对值类型才有。
你可能混淆了不同的概念:
- 值类型与引用类型
对于值类型(如原始数字类型、枚举和类似 DateTime 的结构),变量的值就是对象本身。将变量分配给另一个变量(或按值传递作为参数)会创建对象的副本。
对于引用类型(如 objectstring、类(而非结构体)等),变量的值是对对象的引用。将变量分配给另一个变量(或按值传递作为参数)会创建对引用的副本,因此它仍然指向同一对象实例。
- 按值传递参数与按引用传递参数
按值传递参数意味着传递值的副本。这取决于它是值类型还是引用类型,这意味着对象本身的副本或引用的副本。如果被调用方修改了作为参数传递的值类型成员,则调用方将不会看到更改,因为调用方正在使用的是副本。另一方面,如果被调用方修改了作为参数传递的引用类型成员,则调用方将看到更改,因为被调用方和调用方都有对同一对象实例的引用。
按引用传递参数意味着传递对变量的引用(它可以是值类型或引用类型的变量)。该值不会被复制:它在调用方和被调用方之间共享。因此,被调用方进行的任何更改(包括将新值分配给参数)将被调用方看到。

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

我建议您阅读Jon Skeet的文章《C#中的参数传递》以获得更好的解释。


2
讲解得非常清楚。这个问题已经被问过和回答过很多次,但人们仍然会混淆概念。我希望有其他的术语,因为“引用类型”与“按引用传递”被混淆了。 - Jeppe Stig Nielsen
所以,实际让我困惑的是对传递值(实际值或引用)的不同解释取决于参数/变量类型。如果我进一步使用refout传递对象的引用,这是否完全过时?我试图通过引用(ref)将引用值MyColor实例传递给其中一个A实例,并且结果与传递引用值相同。 - Florian R. Klein
只要被调用者不对参数进行赋值,对于引用类型来说就没有任何影响。但是如果它分配了不同的对象,那么调用者的变量也将指向该对象。这是传递引用类型的唯一有用情况。 - Thomas Levesque
无法强调Skeet文章的重要性,特别是这句话“默认情况下,对象引用按值传递”,一旦你理解了这一点,就有助于看到这种行为在Self编程语言中的起源:https://en.wikipedia.org/wiki/Self_(programming_language) - missaghi

3
除非您明确指定使用“ref”或“out”关键字将其作为引用传递,否则所有方法参数都是按值传递的。这意味着,如果您将变量传递给一个方法参数,则变量的内容被复制并传递给该方法。
如果变量是值类型,基本上是一个“struct”,则变量包含一个对象,因此该对象被复制。如果变量是引用类型,基本上是一个“class”,则变量包含对对象的引用,因此该引用被复制。
如果您将参数声明为“ref”或“out”,则会创建对变量的引用,并将其传递给方法。如果变量包含一个对象,则会创建对该对象的引用;如果变量包含一个引用,则会创建对该引用的引用。

请问当使用ref关键字将引用类型的对象传递到方法中时会发生什么? - variable
我的意思是我所写的就是我所想的。如果你读了我写的,那么你就知道我的意思了。如果没有区别的话,那么 ref 关键字也不会存在。 - jmcilhinney

2

我来改写你的问题:为什么我们需要类?难道我们只能使用结构体吗?

并不是所有对象都可以安全地复制。例如,你无法逻辑上复制FileStreamButton。这些对象具有身份,你希望所有代码都引用同一个对象。


0

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

尽管.NET中的对象没有与之关联的可读标识符,但将它们视为具有这些标识符可能是有帮助的:如果在程序执行过程中创建了至少一些数量(例如592)的对象,则恰好一个对象将是第592个创建的对象;一旦创建了第592个对象,就不会再有其他对象成为第592个对象。无法找出哪个对象是第592个,但如果将保存对第592个对象引用的变量作为非ref参数传递给某个方法,则当该方法返回时,它将继续保存对第592个对象的引用。如果对象#592是指向红色汽车实例的引用,则本地变量myCar保存“Object ID#592”,并调用PaintCar(myCar);方法,则该方法将接收到Object #592"。如果该方法将汽车涂成蓝色,则返回时,myCar将保存“Object #592”,这将标识一辆蓝色汽车。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接