浅表拷贝得到一个新的实例,一个与原始对象类型相同、值类型字段相同的拷贝。但是如果字段是引用类型的,则拷贝的是该引用, 而不是的对象。若想将引用字段的对象也拷贝过去,则称为深拷贝。
System.Object提供了受保护的方法 MemberwiseClone,可用来实现“浅表”拷贝。
由于该方法标记为“受保护”级别,因此,我们只能在继承类或该类内部才能访问该方法:.
//浅复制
public Student ShallowClone()
{
return this.MemberwiseClone() as Student;
}
使用序列化与反序列化的方式
这种方式虽可实现深度拷贝,但在外面引用时一定要记得关闭所创建的MemoryStream流:(类可序列化的条件:
-
为Public -
增加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;
}
}
利用反射实现
在一个外国人写的博客中(http://www.codeproject.com/Articles/3441/Base-class-for-cloning-an-object-in-C),使用反射的方法来解决了这个问题。他写了一个BaseObject类,如果我们继承这个类就可以实现深度拷贝,下面是他的实现方法:
创建一个实现 ICloneable 接口的有默认行为的抽象类,所谓的默认行为就是使用以下库函数来拷贝类里的每一个字段。
-
遍历类里的每个字段,看看是否支持ICloneable接口。
-
如果不支持ICloneable接口,按下面规则进行设置,也就是说如果字段是个值类型,那将其拷贝,如果是引用类型则拷贝字段指向通一个对象。
-
如果支持ICloneable,在科隆对象中使用它的克隆方法进行设置。
-
如果字段支持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