何时复制C#值/对象,何时复制其引用?

86

我一遍又一遍地遇到相同的问题,即我想引用的对象被复制,或者我想复制的对象被引用。这发生在我使用=运算符时。

例如,如果我要将该对象发送到另一个窗体:

SomeForm myForm = new SomeForm();
SomeObject myObject = new SomeObject();
myForm.formObject = myObject;

当我修改表单中的对象时,原始对象并没有被修改。就好像对象被复制而不是引用一样。然而,当我这样做时:

SomeObject myObject = new SomeObject();
SomeObject anotherObject = new SomeObject();
anotherObject = myObject;

如果修改 anotherObjectmyObject 也会被修改。

最令人恼火的情况是我尝试克隆我定义的对象之一:

public class SomeObject
{
    double value1, value2;

    //default constructor here

    public SomeObject(val1, val2)
    {
        value1 = val1;
        value2 = val2;
    }

    public void Clone(SomeObject thingToCopy)
    {
        this.value1 = thingToCopy.value1;
        this.value2 = thingToCopy.value2;
    }
}

当我执行这个操作时...

SomeObject obj1 = new SomeObject(1, 2);
SomeObject obj2 = new SomeObject();
obj2.Clone(obj1);

...obj1被引用,而对obj2所做的任何修改都会更改obj1

int、double、string等系统对象似乎总是被复制,除了上面的克隆方法的情况。

我的问题是,在不考虑函数中使用ref关键字的情况下,每种情况下对象何时被复制,何时被引用(例如,传递给函数时,设置为其他对象时(如前两个示例),复制成员变量时等)?


1
我希望能看到更多对这个伟大问题的回答。 - Roblogic
3个回答

64

如果不花费很多时间仔细挑选用词,回答这种问题是很困难的。

我在几篇文章中已经进行了这样的工作,你可能会发现它们很有用:

当然,并不是说这些文章完美无缺,但我尽可能地清晰明了。

我认为一个重要的事情是把这两个概念(参数传递和引用与值类型)分开来看。

看看你具体的例子:

SomeForm myForm = new SomeForm();
SomeObject myObject = new SomeObject();
myForm.formObject = myObject;
这意味着myForm.formObjectmyObject引用同一个SomeObject实例——就像两个人有着不同的纸张,每个纸张上都写着相同的地址。如果你去了其中一张纸上的地址然后将房子涂成红色,然后再去另一张纸上的地址,你会看到一个红色的房子。
“然后修改表单中的对象”并不清楚,因为您提供的类型是不可变的。没有办法直接修改对象本身。您可以更改myForm.formObject以引用不同的SomeObject实例,但这就像在一张纸上涂鸦并将其替换为不同的地址。这不会更改另一张纸上的内容。
如果您能提供一个简短但完整的程序,其行为令您困惑(最好是控制台应用程序,只需使事情更短,更简单),那么讨论具体问题会更容易些。

1
我现在正在工作,稍后再写程序。谢谢你的帮助。我也会阅读这些文章。 - Mike Webb
3
将地址写在不同纸张上的比喻有助于理解引用类型的概念。 - Martin
Jon,请问您能否解释一下低级别的“按值”复制是如何完成的吗?CLR是否使用某种反射并逐个字段复制?还是仅仅复制内存区域? - Johnny_D
1
@Johnny_D:针对哪种类型?对于引用类型,它只是复制引用 - 实际上是一个指针。对于值类型,我认为涉及到更多的细微差别,但基本上仍然是复制一块内存。它不需要使用反射 - 它知道类型的大小以及值在内存中的位置。 - Jon Skeet
@JonSkeet 谢谢您的回答。您是否记得有关此主题的任何低级详细信息文章?我想多读一些。 - Johnny_D
显示剩余6条评论

11

嗨,Mike。 所有从ValueType派生的对象,如结构体或其他原始类型都是值类型。这意味着每当您将它们分配给变量或作为方法参数传递时,它们都会被复制。其他类型是引用类型,这意味着当您将引用类型分配给变量时,不是其值,而是其在内存空间中的地址被分配给该变量。 此外,您应该注意可以使用ref关键字将值类型作为引用传递。以下是语法。

public void MyMethod(ref int a) { a = 25 }
int i = 20;
MyMethod(ref i); //Now i get's updated to 25.
希望能帮到你 :)

抱歉挑剔一下。对象不会派生,类型才会。"对象"通常用来描述一个类型的实例。 - Brian Rasmussen
你可以使用 ref 通过引用传递参数,但这并不等同于将其作为引用传递。我认为区分这两者是值得的。 - Jon Skeet
@JonSkeet:我知道这是一个老问题,但您能否澄清或分享一些关于您最后一条评论的信息?我的意思是传递_by_引用与传递_as_引用有什么区别?提前感谢:)我之所以问是因为我一直认为两者最终都会产生相同的结果,即如果在外部方法中传递,则会影响传递的对象值。 - Mohammed Swillam
@MohammedElSayed:最好还是阅读http://jonskeet.uk/csharp/parameters.html。 - Jon Skeet
@JonSkeet:感谢您的快速回复,下一杯咖啡我请您;) - Mohammed Swillam

1

关于克隆对象,如果您从一个对象复制到另一个对象的值是引用类型,则对原始对象中这些值的任何修改都会影响复制对象中的值(因为它们只是指向同一对象的引用)。

如果您需要克隆具有引用类型属性的对象,则需要使这些类型可克隆或根据需要手动复制它们的新实例。

考虑使用IClonable接口,但在我看来这不是最好的解决方案。


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