C# 中的 浅表拷贝和深层拷贝
浅表拷贝得到一个新的实例,一个与原始对象类型相同、值类型字段相同的拷贝。但是,如果字段是引用类型的,则拷贝的是该引用, 而不是的对象。若想将引用字段的对象也拷贝过去,则称为深拷贝。
1.System.Object提供了受保护的方法 MemberwiseClone,可用来实现“浅表”拷贝。由于该方法标记为“受保护”级别,因此,我们只能在继承类或该类内部才能访问该方法:
//浅复制 public Student ShallowClone() { return this.MemberwiseClone() as Student; }
2.使用序列化与反序列化的方式,这种方式虽可实现深度拷贝,但在外面引用时一定要记得关闭所创建的MemoryStream流:(类可序列化的条件:1.为Public,2.增加Serializable属性标记类)
using System.Runtime.Serialization; using System.Runtime.Serialization.Formatters.Binary; //深拷贝 public Student DeepClone() { using (Stream objectStream = new MemoryStream()) { IFormatter formatter = new BinaryFormatter(); formatter.Serialize(objectStream, this); objectStream.Seek(0, SeekOrigin.Begin); return formatter.Deserialize(objectStream) as Student; }
3.在一个外国人写的博客中(http://www.codeproject.com/Articles/3441/Base-class-for-cloning-an-object-in-C),使用反射的方法来解决了这个问题。他写了一个BaseObject类,如果我们继承这个类就可以实现深度拷贝,下面是他的实现方法:
创建一个实现 ICloneable
接口的有默认行为的抽象类,所谓的默认行为就是使用以下库函数来拷贝类里的每一个字段。
1.遍历类里的每个字段,看看是否支持ICloneable
接口。
2.如果不支持ICloneable
接口,按下面规则进行设置,也就是说如果字段是个值类型,那将其拷贝,如果是引用类型则拷贝字段指向通一个对象。
3.如果支持ICloneable
,在科隆对象中使用它的克隆方法进行设置。
4.如果字段支持IEnumerable接口,需要看看它是否支持IList或者IDirectionary接口,如果支持,迭代集合看看是否支持ICloneable接口。
我们所要做的就是使得所有字段支持ICloneable接口。
BaseObject的实现:
/// <summary> /// BaseObject class is an abstract class for you to derive from. /// Every class that will be dirived from this class will support the /// Clone method automaticly.<br> /// The class implements the interface ICloneable and there /// for every object that will be derived <br> /// from this object will support the ICloneable interface as well. /// </summary> using System.Reflection;
using System.Collections;
public abstract class BaseObject : ICloneable { /// <summary> /// Clone the object, and returning a reference to a cloned object. /// </summary> /// <returns>Reference to the new cloned /// object.</returns> public object Clone() { //First we create an instance of this specific type. object newObject = Activator.CreateInstance( this.GetType() ); //We get the array of fields for the new type instance. FieldInfo[] fields = newObject.GetType().GetFields(); int i = 0; foreach( FieldInfo fi in this.GetType().GetFields() ) { //We query if the fiels support the ICloneable interface. Type ICloneType = fi.FieldType. GetInterface( "ICloneable" , true ); if( ICloneType != null ) { //Getting the ICloneable interface from the object. ICloneable IClone = (ICloneable)fi.GetValue(this); //We use the clone method to set the new value to the field. fields[i].SetValue( newObject , IClone.Clone() ); } else { // If the field doesn't support the ICloneable // interface then just set it. fields[i].SetValue( newObject , fi.GetValue(this) ); } //Now we check if the object support the //IEnumerable interface, so if it does //we need to enumerate all its items and check if //they support the ICloneable interface. Type IEnumerableType = fi.FieldType.GetInterface ( "IEnumerable" , true ); if( IEnumerableType != null ) { //Get the IEnumerable interface from the field. IEnumerable IEnum = (IEnumerable)fi.GetValue(this); //This version support the IList and the //IDictionary interfaces to iterate on collections. Type IListType = fields[i].FieldType.GetInterface ( "IList" , true ); Type IDicType = fields[i].FieldType.GetInterface ( "IDictionary" , true ); int j = 0; if( IListType != null ) { //Getting the IList interface. IList list = (IList)fields[i].GetValue(newObject); foreach( object obj in IEnum ) { //Checking to see if the current item //support the ICloneable interface. ICloneType = obj.GetType(). GetInterface( "ICloneable" , true ); if( ICloneType != null ) { //If it does support the ICloneable interface, //we use it to set the clone of //the object in the list. ICloneable clone = (ICloneable)obj; list[j] = clone.Clone(); } //NOTE: If the item in the list is not //support the ICloneable interface then in the //cloned list this item will be the same //item as in the original list //(as long as this type is a reference type). j++; } } else if( IDicType != null ) { //Getting the dictionary interface. IDictionary dic = (IDictionary)fields[i]. GetValue(newObject); j = 0; foreach( DictionaryEntry de in IEnum ) { //Checking to see if the item //support the ICloneable interface. ICloneType = de.Value.GetType(). GetInterface( "ICloneable" , true ); if( ICloneType != null ) { ICloneable clone = (ICloneable)de.Value; dic[de.Key] = clone.Clone(); } j++; } } } i++; } return newObject; } }
写了个例子,试了下上面第3种方式,我发现如果类中含有Array类型的成员变量,是可以深度复制的。但如果含有List成员变量,List成员还是浅复制(并且会报原始数据被修改)。原因是Array继承了ICloneable,但List没有继承,BaseObject中的实现方法就会把List成员变量当成值类型的变量直接赋值。我的解决办法是创建MyList类,实现Clone方法:
[Serializable] public class MyList<T> : List<T>,ICloneable { //clone一个新的List public object Clone() { MyList<T> newList = new MyList<T>(); foreach(T item in this){ //分别创建当中的每个成员对象 newList.Add(Activator.CreateInstance<T>()); } return newList; } } [Serializable] public class Student:BaseObject { public int Age; public string Name; public MyList<Pet> PetList; //浅复制 public Student ShallowClone() { return this.MemberwiseClone() as Student; } //深拷贝 public Student DeepClone() { using (Stream objectStream = new MemoryStream()) { IFormatter formatter = new BinaryFormatter(); formatter.Serialize(objectStream, this); //序列 objectStream.Seek(0, SeekOrigin.Begin); return formatter.Deserialize(objectStream) as Student;//反序列 } } public string ToString() { StringBuilder sb = new StringBuilder(); sb.AppendFormat("Age:{0}, Name:{1}", this.Age, this.Name); sb.AppendLine(); foreach (Pet p in this.PetList) { sb.AppendFormat("PetName:{0}, Weight:{1}", p.Name, p.Weight); sb.AppendLine(); } return sb.ToString(); } } [Serializable] public class Pet : BaseObject { public string Name; public int Weight; } class Program { static void Main(string[] args) { MyList<Pet> pets1 = new MyList<Pet>(); pets1.Add(new Pet { Weight=60,Name="pp"}); pets1.Add(new Pet { Weight =80, Name = "bb" }); Student stu1 = new Student { Age = 15, Name = "ZZW", PetList = pets1 }; Student stu2 = (Student)stu1.Clone(); Console.WriteLine("Before modidfy....."); Console.WriteLine("Stu1:" + stu1.ToString()); Console.WriteLine("Stu2:" + stu2.ToString()); stu2.Age = 66; stu2.Name = "jjj"; foreach (Pet p in stu2.PetList) { p.Name = "xx"; p.Weight = 100; } Console.WriteLine("After Stu2 modidfy....."); Console.WriteLine("Stu1:" + stu1.ToString()); Console.WriteLine("Stu2:" + stu2.ToString()); Console.ReadKey(); } }
输出结果:
Before modidfy..... Stu1:Age:15, Name:ZZW PetName:pp, Weight:60 PetName:bb, Weight:80 Stu2:Age:15, Name:ZZW PetName:pp, Weight:60 PetName:bb, Weight:80 After Stu2 modidfy..... Stu1:Age:15, Name:ZZW PetName:pp, Weight:60 PetName:bb, Weight:80 Stu2:Age:66, Name:jjj PetName:xx, Weight:100 PetName:xx, Weight:100