C# 深拷贝浅拷贝

为对象创建副本的技术称为拷贝(也叫克隆)。我们将拷贝分为浅拷贝和深拷贝。

浅拷贝 将对象中的所有字段复制到新的对象(副本)中。其中,值类型字段的值被复制到副本中后,在副本中的修改不会影响到源对象对应的值。而引用类型的字段被复制到副本中的是引用类型的引用,而不是引用的对象,在副本中对引用类型的字段值做修改会影响到源对象本身。

深拷贝 同样,将对象中的所有字段复制到新的对象中。不过,无论是对象的值类型字段,还是引用类型字段,都会被重新创建并赋值,对于副本的修改,不会影响到源对象本身。

无论是浅拷贝还是深拷贝,微软都建议用类型继承ICloneable接口的方式明确告诉调用者:该类型可以被拷贝。当然,ICloneable接口只提供了一个声明为Clone的方法,我们可以根据需求在Clone方法内实现浅拷贝或深拷贝。

如下,构造两个类,使用默认拷贝函数,代码如下:

 

  1.  
    using System;
  2.  
    using System.Collections.Generic;
  3.  
    using System.Linq;
  4.  
    using System.Text;
  5.  
    using System.Threading.Tasks;
  6.  
    using System.IO;
  7.  
    using System.Runtime.Serialization;
  8.  
    using System.Runtime.Serialization.Formatters.Binary;
  9.  
    namespace DeepLowClone
  10.  
    {
  11.  
    [Serializable]
  12.  
    public class Student:ICloneable
  13.  
    {
  14.  
    #region imp Iclone
  15.  
    public object Clone()
  16.  
    {
  17.  
    return this.MemberwiseClone();
  18.  
    }
  19.  
     
  20.  
    #endregion imp Iclone
  21.  
     
  22.  
    private String mName;
  23.  
     
  24.  
    public String Name
  25.  
    {
  26.  
    get { return mName; }
  27.  
    set { mName = value; }
  28.  
    }
  29.  
     
  30.  
    private Department mDepartment;
  31.  
     
  32.  
    public Department OwnedDepartment
  33.  
    {
  34.  
    get { return mDepartment; }
  35.  
    set { mDepartment = value; }
  36.  
    }
  37.  
     
  38.  
    private String mStudentID;
  39.  
     
  40.  
    public String StudentID
  41.  
    {
  42.  
    get { return mStudentID; }
  43.  
    set { mStudentID = value; }
  44.  
    }
  45.  
     
  46.  
    private double mTotalScore;
  47.  
     
  48.  
    public double TotalScore
  49.  
    {
  50.  
    get { return mTotalScore; }
  51.  
    set { mTotalScore = value; }
  52.  
    }
  53.  
     
  54.  
    }
  55.  
     
  56.  
    [Serializable]
  57.  
    public class Department
  58.  
    {
  59.  
    private String mName;
  60.  
     
  61.  
    public String Name
  62.  
    {
  63.  
    get { return mName; }
  64.  
    set { mName = value; }
  65.  
    }
  66.  
     
  67.  
    private String mID;
  68.  
     
  69.  
    public String DepID
  70.  
    {
  71.  
    get { return mID; }
  72.  
    set { mID = value; }
  73.  
    }
  74.  
     
  75.  
    }
  76.  
    }


测试代码如下:

 

 

  1.  
    using System;
  2.  
    using System.Collections.Generic;
  3.  
    using System.Linq;
  4.  
    using System.Text;
  5.  
    using System.Threading.Tasks;
  6.  
     
  7.  
    namespace DeepLowClone
  8.  
    {
  9.  
    class Program
  10.  
    {
  11.  
    static void Main(string[] args)
  12.  
    {
  13.  
    Student std1 = new Student() {
  14.  
    Name = "jane",
  15.  
    TotalScore = 600.0,
  16.  
    StudentID ="040432",
  17.  
    OwnedDepartment = new Department
  18.  
    {
  19.  
    Name="gis",
  20.  
    DepID="04"
  21.  
    }
  22.  
    };
  23.  
     
  24.  
    PrintStudentInfo(std1);
  25.  
    {
  26.  
    Console.WriteLine("{0},{1} at Department({2} {3}) total score is {4};",
  27.  
    std.StudentID, std.Name,
  28.  
    std.OwnedDepartment.DepID, std.OwnedDepartment.Name,
  29.  
    std.TotalScore);
  30.  
    }
  31.  
    }
  32.  
    }

 

 

输出结果如下:

 

我们可以看到,当对源对象std1的StudentID 及Score作修改之后,副本对象的这两个属性并没有被修改过来,而对std2的Department对象的修改则反应到副本对象上。按浅拷贝的概念,StudentID和Department一样是引用类型,副本应该会保持和源对象一样,但是结果并不是这样。理论上string类型是引用类型,但是由于该引用类型的特殊性(无论是实现还是语义),Object.MemberwiseClone方法仍旧为其创建了副本。也就是说,在浅拷贝过程,我们应该将字符串看成是值类型。

为了实现对象的完全拷贝,我们重写Clone如下:

 

 

  1.  
    #region imp Iclone
  2.  
    //public object Clone()
  3.  
    //{
  4.  
    // return this.MemberwiseClone();
  5.  
    //}
  6.  
     
  7.  
    public object Clone()
  8.  
    {
  9.  
    Student cloneobj;
  10.  
    using (Stream ms = new MemoryStream())
  11.  
    {
  12.  
    IFormatter formatter = new BinaryFormatter();
  13.  
    // serialize the obj as stream
  14.  
    formatter.Serialize(ms, this);
  15.  
    ms.Seek(0, SeekOrigin.Begin);
  16.  
    //De-serialize the stream to obj
  17.  
    cloneobj = formatter.Deserialize(ms) as Student;
  18.  
    ms.Close();
  19.  
    }
  20.  
    return cloneobj;
  21.  
    }
  22.  
     
  23.  
    #endregion imp Iclone

 

 

使用该方法进行深拷贝时,所有类型都必须标识为Serializable否则formatter.Serialize(ms,this)语句将报异常这样的异常An unhandled exception of type 'System.Runtime.Serialization.SerializationException' occurred in mscorlib.dll

运行结果如下:

 

我们可以看到源对象的修改不再影响副本。

以上我们是利用二进制序列化和反序列化实现深拷贝的。我们也可以手动对字段逐个进行赋值。但这种方法容易出错,也就是说,如果类型的字段发生变化或有增减,那么该拷贝方法也要发生相应的变化。

另外还可以通过反射及xml序列化反序列化来实现深拷贝。

posted on 2018-08-22 07:30  boood  阅读(105)  评论(0)    收藏  举报

导航