如何拷贝一个对象

这是我工作中遇到的一个bug,困扰了我一个下午。

但是我会用一个好理解的例子来说明。

应该在所有面向对象编程语言中是通用的,这是关于”克隆一个实体“的问题。

问题

首先,我手上有两个类,一个是Person,另一个是Car

Person有一个属性叫做car,创建新Person时,要传入Car的实例。

 1public static void test1()
 2{
 3    Car car = new Car("BMW", "Black", 100000);//brand , color, price
 4    Person person = new Person("John", 20, car);
 5    System.Console.WriteLine(person.ToString());
 6    //Name: John, Age: 20, Car: Brand: BMW, Color: Black, Price: 100000
 7    car.price = 200000;// if i change the price of the car
 8    System.Console.WriteLine(person.ToString());
 9    //Name: John, Age: 20, Car: Brand: BMW, Color: Black, Price: 200000
10    System.Console.WriteLine("===========");
11}

创建了一个价格为100000的car

然后创建了一个person,传入car

在外部改变了car的价格

已经创建的person.car.price的价格也改变了!

person在创建的时候(假设他这时购买了这辆车),他的car的价格就是100000,不应该因为car涨价了而改变。

或者想象一下,我又修改了一下car的属性,比如说car.color = "red",那么person.car.color也会变成red,我未经同意给人家车子换了个喷漆。

函数传递参数

这涉及到一个原理: 在函数传递参数的时候,如果用的是基础类型,比如intstring,那么传递的是值,如果是引用类型,比如Car class,那么传递的是引用。

这也是为什么上面的例子中,当我改变了外面car的价格,已经创建的person.car.price的价格也会改变。(它引用了内存地址中同一个位置的car

在这里例子中,我们只需要不再动car就可以了

但是实际情况是:这可能是循环中的一部分,那个car在下一次循环中可能还会被利用。从而导致car不可避免的被修改。

解决方案

其实解决方案也很简单,就是弄一个Car副本。

有两种方式可以按照需求使用 虽然我的问题中两种办法都用不了,不过我也另辟蹊径解决了)

创建一个新的Car

他的意识其实就是所谓的“深拷贝”,就是把一个对象的所有属性都拷贝一份,

传入的参数从car变成了new Car(car.brand, car.color, car.price)

 1private static void test2()
 2{
 3    Car car = new Car("BMW", "Black", 100000);//brand , color, price
 4    // if i want to copy the car(this is not a good way)
 5    Person person = new Person("John", 20, new Car(car.brand, car.color, car.price));
 6    System.Console.WriteLine(person.ToString());
 7    //Name: John, Age: 20, Car: Brand: BMW, Color: Black, Price: 100000
 8    car.price = 200000;// if i change the price of the car
 9    System.Console.WriteLine(person.ToString());
10    //Name: John, Age: 20, Car: Brand: BMW, Color: Black, Price: 100000
11    System.Console.WriteLine("===========");
12}

这个办法挺好的,但是有一个大大的问题,如果Car类中有很多属性,写起来太复杂了。

❗️甚至有可能这个Car类是别人提供的库,还是个套娃,他里面层级深的可怕,还不给new。

使用MemberwiseClone方法

  • 在c#中,MemberwiseClone方法
  • 在py中,copy函数
  • 在java中,clone方法

MemberwiseClone这个方法是Object类中的一个方法,他的就是所谓的“浅拷贝”,就是把一个对象的所有属性都拷贝一份,但是如果属性是引用类型,那么只是拷贝了引用,而不是拷贝了引用的值。

MemberwiseClone是私有属性,为了调用它,需要在car类中创建一个Clone方法

Clone方法(在Car.cs中):

1internal Car Clone()
2{
3    return (Car)this.MemberwiseClone();
4}

这回传入的参数从car变成了car.Clone()

 1private static void test3()
 2{
 3    Car car = new Car("BMW", "Black", 100000);//brand , color, price
 4    // "the better clone way"you will need to implement Clone() method,go see the colne method in Car.cs
 5    Person person = new Person("John", 20, car.Clone()); 
 6    System.Console.WriteLine(person.ToString());
 7    //Name: John, Age: 20, Car: Brand: BMW, Color: Black, Price: 100000
 8    car.price = 200000;// if i change the price of the car
 9    System.Console.WriteLine(person.ToString());
10    //Name: John, Age: 20, Car: Brand: BMW, Color: Black, Price: 200000
11    System.Console.WriteLine("===========");
12}

另辟蹊径–我的办法

我是用的是第三方的Docx库,他的Paragraph类就是我要复制的对象。

  • 他没有提供Copy方法
  • new Paragraph()也不行,会有很多属性是null(并且参数一大堆)
  • Paragraph对象是和文档绑定的,我需要一个Paragraph对象,总不能去复制下整个文档。

⭐️我创建了一个Clone_p函数,他会专门返回一个Paragraph对象

传入的确实是引用,但是使用了专门copy函数后,就不会不小心修改到他了,在内存中也是独立的一个位置(docx初始化完毕之后,所有paragraph都会读到内存中放置)完美解决问题!

1public Paragraph Clone_p(Paragraph p) {
2    return find_p_by_pid(Get_pid(p));
3}
4
5public Paragraph find_p_by_pid(string value) => Paragraphs.Where(p => p.Xml.FirstAttribute.ToString()==value).ToList()[0];
6
7public static string Get_pid(Paragraph p) {
8    return p.Xml.FirstAttribute.ToString();
9}

Iterm 调教(快捷键和主题)
E-H阅读插件/E-H下载插件