什么时候复制C#值/对象,什么时候复制其引用?


69

我一遍又一遍地不断遇到相同的问题,在该位置我要引用的对象被复制或在我要复制的对象被引用。当我使用=运算符时,会发生这种情况。

例如,如果我将对象发送到另一种形式,即:

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

...然后修改表单中的对象,原始对象不会被修改。好像对象已被复制但未被引用。但是,当我这样做时:

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

...然后修改anotherObject,也myObject将被修改。

最糟糕的情况是当我尝试克隆我定义的对象之一时:

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在函数中使用关键字,何时复制对象以及何时在每种情况下都引用对象(即,传递给函数时,设置为其他对象时(例如上面的前两个示例),何时复制成员变量(如第三个示例等)?

Answers:


53

如果不花大量时间仔细挑选您的单词,就很难准确地回答此类问题。

我已经在几篇文章中这样做了,您可能会发现它们很有用:

当然,这并不是说这些文章是完美的-远非如此-但是我已经尽我所能地做到了清晰。

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

要查看您的特定示例:

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

这意味着myForm.formObjectmyObject引用相同的实例SomeObject-就像两个人有分开的纸,每个人都写有相同的地址。如果转到一张纸上的地址并将房子涂成红色,然后转到第二张纸上的地址,则会看到一个红色的房子。

不清楚“然后修改表单中的对象”是什么意思,因为提供的类型是不可变的。无法修改对象本身。您可以更改myForm.formObject以引用的其他实例SomeObject,但这就像在一张纸上写出地址并在其上写一个不同的地址一样。那不会改变另一张纸上写的内容。

如果您可以提供一个简短但完整的程序,但您不了解其行为(理想情况是一个控制台应用程序,只是为了使事情更简短,更简单),那么用具体的术语来谈论事情会更容易。


我将不得不写一个。我大部分都了解C ++的指针,我只是不了解何时将它们与C#中的值和引用类型相关使用,因为C#中没有明确的“安全”指针用法/运算符。
Mike Webb 2010年

1
我现在在工作,我将在以后编写程序。感谢您的帮助。我也会阅读文章。
Mike Webb 2010年

3
在单独的纸上进行地址类推有助于理解引用类型的概念。
马丁

@Jon Skeet:对nitpick不好意思,当我在第二个链接(C#/。NET中的引用类型和值类型)中尝试该示例时,我注意到“ .Text”被排除在“ Console.WriteLine(“ originalPrintedPage = { 0}“,originalPrintedPage);”
马丁

1
@Johnny_D:什么样的类型?对于引用类型,它只是有效地复制了引用-指针。对于值类型,我相信会涉及更多的微妙之处,但它基本上仍在复制一块内存。它不需要使用反射-它知道类型的大小以及该值在内存中的位置。
乔恩·斯基特

7

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

public void MyMethod(ref int a) { a = 25 }
int i = 20;
MyMethod(ref i); //Now i get's updated to 25.

希望能帮助到你 :)


很抱歉在这里挑剔。对象不派生。类型呢。对象通常是用于描述类型实例的术语。
布赖恩·拉斯穆森

你可以传递参数通过使用参考ref,但那是不一样通过它作为一个参考。我认为有必要区分两者。
乔恩·斯基特

@JonSkeet:我知道这是一个老问题,但是您能否澄清或分享一些有关您的最新评论的信息?我的意思是通过通过参考VS传递作为参考?在此先感谢:)我问,因为我一直以为,如果在外部方法中传递,两者都会以相同的结果结束,这会影响传递的对象值。
Mohammed Swillam

@MohammedElSayed:最好阅读jonskeet.uk/csharp/parameters.html
Jon Skeet

@JonSkeet:感谢您的快速回复,您的下一杯咖啡在我身上;)
Mohammed Swillam

1

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

如果需要克隆具有引用类型属性的对象,则需要使这些类型成为可克隆对象,或者通过根据需要实例化新实例来对其进行手动复制。

考虑使用IClonable接口,尽管它不是解决方案的最佳选择。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.