代码改变世界

.NET基础知识之八——深拷贝,浅拷贝

2013-12-04 00:25  左眼微笑右眼泪  阅读(416)  评论(0编辑  收藏  举报

目录

1、概念

2、使用赋值符号“=”

3、浅复制

4、深复制

5、问题一:如果类里面嵌套有多个类,然后嵌套类里面又嵌套类,那么像上面实现深拷贝的方法还能用吗?

6、问题二:实现深拷贝时,一定要实现ICloneable接口吗?

1、概念:

         浅拷贝是将一个对象里面的所有字段重新拷贝一个到另外一个对象中去,如果字段是值类型,那么它拷贝的就是值,如果字段类型是引用类型,那么它拷贝的就是地址;

        深拷贝是将一个对象里面的所有字段重新拷贝到另外一个对象中去,它与浅拷贝的区别是,如果字段是引用类型,它拷贝的不是地址,它会把引用类型里面每一个值重新拷贝一份,那就是说完全重新拷贝一个,新对象中的值不管怎么改变都不会影响以前的对象值。

         我们平时所知道的赋值符号“=”,如果是值类型,它就是拷贝值类型,如果是引用类型,它就是赋予的地址。

         由上面可以看出,“=”,浅拷贝,深拷贝对于值类型来说是没有区别的。针对引用类型来说是有区别的,看下面的例子。

         先定义一个People类,在People类里面有一个Address类,先看Address类:

   1: [Serializable]
   2: public class Address
   3: {
   4:     private string addressStr;
   5:  
   6:     public string AddressStr
   7:     {
   8:         get { return addressStr; }
   9:         set { addressStr = value; }
  10:     }
  11:  
  12:     private string addressNo;
  13:  
  14:     public string AddressNo
  15:     {
  16:         get { return addressNo; }
  17:         set { addressNo = value; }
  18:     }
  19:  
  20:     public override string ToString()
  21:     {
  22:         return AddressStr + ";" + AddressNo;
  23:     }
  24: }

          再来看People类:

   1: [Serializable]
   2: public class People:ICloneable
   3: {
   4:     private string strName;
   5:  
   6:     public string StrName
   7:     {
   8:         get { return strName; }
   9:         set { strName = value; }
  10:     }
  11:  
  12:     private int strNo;
  13:  
  14:     public int StrNo
  15:     {
  16:         get { return strNo; }
  17:         set { strNo = value; }
  18:     }
  19:  
  20:     private Address addStr;
  21:  
  22:     public Address AddStr
  23:     {
  24:         get
  25:         {
  26:             if (addStr == null)
  27:             {
  28:                 addStr = new Address();
  29:             }
  30:             return addStr;
  31:         }
  32:         set { addStr = value; }
  33:     }
  34:  
  35:     public override string ToString()
  36:     {
  37:         return StrName + ";" + strNo + addStr.ToString();
  38:     }
  39: }

         下面分三种情况来分析:

2、使用赋值符号“=”

         赋值符号,只是把引用类型的地址赋给了另外一个对象,对这个对象里面的任何修改都会对原对象产生影响。

   1: static void Main(string[] args)
   2: {
   3:     People people = new People()
   4:     {
   5:         StrName = "张三",
   6:         StrNo = 1,
   7:         AddStr = new Address { AddressNo = "10010", AddressStr = "北京市海淀区" }
   8:     };
   9:  
  10:     //使用等于号"="赋值的情况
  11:     People peopleZhang = new People();
  12:     peopleZhang = people;
  13:     Console.WriteLine("情况1(使用等于号' = '赋值的情况)原始值是:" + peopleZhang.ToString());
  14:     peopleZhang.StrNo = 2;
  15:     peopleZhang.AddStr.AddressNo = "432000";
  16:     Console.WriteLine("情况1(使用等于号' = '赋值的情况)修改后peopleZhang的值是:" + people.ToString());
  17:     Console.WriteLine("情况1(使用等于号' = '赋值的情况)修改后people的值是:" + people.ToString());
  18:     Console.ReadLine();

         输出结果如下:

image

3、浅复制

          浅复制是把对象的每个字段拷贝了一份给另外一个对象,如果这个引用对象里面有值类型,那么拷贝的是值类型的副本,如果是引用类型那么拷贝的是对象的地址。也就是说拷贝后的对象,如果修改对象里面的值类型不会对原对象的值类型的值有影响,但是如果修改对象里面引用类型的值,那么原有对象引用类型的值也会跟着改变。

          在People里面实现浅拷贝,可以自己实现,.NET里面也提供了一个方法MemberwiseClone,这个方法默认就是返回当前对象的副本,所以浅拷贝可以这样实现:

   1: public People ShallowCopy()
   2: {
   3:     return (People)this.MemberwiseClone();
   4: }

         前台调用如下:

   1: //使用浅克隆的情况
   2: People peopleWang = new People();
   3: peopleWang = people.ShallowCopy();
   4: Console.WriteLine("情况2(使用浅克隆的情况)原始值是:" + peopleWang.ToString());
   5: peopleWang.StrNo = 3;
   6: peopleWang.AddStr.AddressNo = "1234567";
   7: Console.WriteLine("情况2(使用浅克隆的情况)修改后peopleWang的值是:" + peopleWang.ToString());
   8: Console.WriteLine("情况2(使用浅克隆的情况)修改后people的值是:" + people.ToString());
   9: Console.ReadLine();

        输出结果如下:

image

4、深复制

          深复制是把对象的每个字段拷贝了一份给另外一个对象,如果这个引用对象里面有值类型,那么拷贝的是值类型的副本,如果是引用类型那么拷贝的不是对象的地址,而是把具体的值也拷贝一遍。也就是说拷贝后的对象,如果修改对象里面的值类型不会对原对象的值类型的值有影响,修改对象里面引用类型的值,也不会对原有对象引用类型的值有影响。

          实现深拷贝一般是实现ICloneable接口,具体实现方法如下:

   1: public Object Clone()
   2: {
   3:     People p = new People();
   4:     p.StrName = this.strName;
   5:     p.StrNo = this.strNo;
   6:     p.AddStr.AddressNo = this.addStr.AddressNo;
   7:     p.AddStr.AddressStr = this.addStr.AddressStr;
   8:  
   9:     return p;
  10: }

           前台调用如下:

   1: //使用深克隆的情况
   2: People peopleLi = new People();
   3: peopleLi = (People)people.Clone();
   4: //peopleLi = people.DeepClone();
   5: //peopleLi = people.DeepCloneBySerialize();
   6: Console.WriteLine("情况3(使用深克隆的情况)原始值是:" + peopleLi.ToString());
   7: peopleLi.StrNo = 4;
   8: peopleLi.AddStr.AddressNo = "8888888";
   9: Console.WriteLine("情况3(使用深克隆的情况)修改后peopleLi的值是:" + peopleLi.ToString());
  10: Console.WriteLine("情况3(使用深克隆的情况)修改后people的值是:" + people.ToString());
  11: Console.ReadLine();

           输出结果是:

image

          通过上面的实现代码,有下面两个问题:

5、问题一:如果类里面嵌套有多个类,然后嵌套类里面又嵌套类,那么像上面实现深拷贝的方法还能用吗?

           解决办法:可以通过序列化的形式来实现深拷贝,这样不管嵌套多少对象,我们不用关注具体的实现,只需要序列化后再反序列化就行了。代码如下:

   1: public People DeepCloneBySerialize()
   2: {
   3:     //调用此方法,必须把类标记为可序列化的,嵌套类也需要标记为可序列化的
   4:     using (Stream objectStream = new MemoryStream())
   5:     {
   6:         IFormatter formatter = new BinaryFormatter();
   7:         formatter.Serialize(objectStream, this);
   8:         objectStream.Seek(0, SeekOrigin.Begin);
   9:         return formatter.Deserialize(objectStream) as People;
  10:     }
  11: }

6、问题二:实现深拷贝时,一定要实现ICloneable接口吗?

           答案:不一定,ICloneable接口是微软提供的一个对象复制的接口,但是它并没有规定是深拷贝还是浅拷贝,由用户自己来决定实现哪一种的拷贝。也可以完全写一个方法实现深拷贝,不用实现这个接口。如下面的代码:

   1: public People DeepClone()
   2: {
   3:     People p = new People();
   4:     p.StrName = this.strName;
   5:     p.StrNo = this.strNo;
   6:     p.AddStr.AddressNo = this.addStr.AddressNo;
   7:     p.AddStr.AddressStr = this.addStr.AddressStr;
   8:  
   9:     return p;
  10: }

           这样就觉得ICloneable接口没有啥作用,而且还有一点不好的就是,如果父类实现了ICloneable接口,那么子类就必须要实现这个接口,因为如果子类不实现这个接口,那么调用子类的这个方法时,它就会去调用父类的这个方法,而父类的这个方法返回的是对父类这个对象 的拷贝,跟子类对象不是一个概念,所以就会有问题。

 

参考资料:

http://www.cnblogs.com/luminji/archive/2011/02/02/1948826.html