net对象的克隆
class Person { public string name; public List<string> hobby; } void main() { Person p1 = new Person(); p1.name = "tom"; p1.hobby = new List<string>(){"eat","sleep"}; Person p2 = p1; }
当p1=null时,p2的name和hobby值是还存在的, p1=null的操作其实如下的红色叉号一样,取消引用,并不影响p2:
1、值类型与引用类型以及区别(知识储备)
- 值类型(Value Type)(如 char、int 和 float)、枚举类型和结构类型。
- 引用类型(Reference Type) 包括类 (Class) 类型、接口类型、委托类型和数组类型。
- 值类型的变量直接包含其数据,
- 引用类型的变量则存储对象引用。
- 对于引用类型,两个变量可能引用同一个对象,因此对一个变量的操作可能影响另一个变量所引用的对象。对于值类型,每个变量都有自己的数据副本,对一个变量的操作不可能影响另一个变量。
2、堆栈(知识储备)
- 堆栈(stack)是一种先进先出的数据结构,在内存中,变量会被分配在堆栈上来进行操作。
- 堆(heap)是用于为类型实例(对象)分配空间的内存区域,在堆上创建一个对象,会将对象的地址传给堆栈上的变量(反过来叫变量指向此对象,或者变量引用此对象)。
3、拷贝的概念
a.浅拷贝(Shallow Copy影子克隆):只复制对象的基本类型,对象类型,仍属于原来的引用。(即引用成员的克隆只克隆成员的地址,不克隆地址所指向的在堆上的分配的内存对象)
b.深拷贝(Deep Copy 深度克隆):不仅复制对象的基本类,同时也复制原对象中的对象.完全产生新对象。(相当于完全new出一个新的对象,只不过子对象的成员已克隆有值了)
4、克隆存在的必要性
我认为当设置一个对象的状态要付出昂贵的代价,并且又需要取得该对象的一个拷贝以便改变当前的一些状态时,克隆就显得十分必要。下面列举一个刚好能体现我刚刚所说的情况的例子。 就拿 DataTable 类来说吧。建立一个 DataTable 会包含诸如以下的操作:为取得架构和数据而查询数据库、添加约束、设置主键等等。那么,当需要该 DataTable 的一个新的拷贝,哪怕是对架构作极小的改变或添加新的一行记录等等, 明智的选择会是克隆已存在的对象再对其进行操作,而不是创建一个新的DataTable, 那样将需要更多的时间和资源。克隆也广泛应用于数组和集合,这些时候往往会多次需要已存在对象的一个拷贝。
5、深拷贝和浅拷贝值成员和引用成员的拷贝方式
2.1 对于值类型:
a.浅拷贝: 对值类型字段只是简单的拷贝一个副本到目标对象,改变目标对象中值类型字段的值不会反映到原始对象中,因为拷贝的是副本
b.深拷贝: 通过赋值等操作直接实现,将对象中的值类型的字段拷贝到新的对象中。 和浅拷贝相同
2.2 对于引用类型:
a.浅拷贝: MemberwiseClone 方法创建一个浅副本,方法是创建一个新对象,如果字段是值类型的,则对该字段执行逐位复制。如果字段是引用类型,则复制引用原始对象,与原对象引用同一对象。 指的是拷贝他的一个引用(内存地址)到目标对象。改变目标对象中引用类型字段的值它将反映到原始对象中,因为拷贝的是指向堆是上的一个地址
b.深拷贝:拷贝对象应用,也拷贝对象实际内容,也就是创建了一个新的改变新对象 不会影响到原始对象的内容,这种情况需要为其实现ICloneable接口中提供的Clone方法。
6、两者的差别
a. 差别就是在对于引用类型的实现深拷贝和浅拷贝的时候的机制不同,前者是MemberwiseClone 方法实现,后者是通过继承实现ICloneable接口中提供的Clone方法,实现对象的深拷贝。
b. 深拷贝与浅拷贝不同的是对于引用拷贝的处理,深拷贝将会在新对象中创建和原是对象中对应值类型的字段并且赋值。浅拷贝不会创建新引用类型,会返回相同的类型引用。深拷贝会重新创建新对象,返回新对象的引用!
7、代码的实现
深度拷贝
1 class Program 2 { 3 static void Main(string[] args) 4 { 5 ShallowCopy obj1 = new ShallowCopy(); 6 obj1.Name = "Tom"; 7 ShallowCopy obj2 = (ShallowCopy)obj1.Clone(); 8 obj1.Name = "Jerry"; 9 obj1.list[0] = 1; 10 obj1.Display(); 11 obj2.Display(); 12 Console.WriteLine(object.ReferenceEquals(obj1, obj2)); 13 Console.ReadKey(); 14 } 15 } 16 [Serializable] 17 public class ShallowCopy : ICloneable 18 { 19 public List<int> list = new List<int>() { 4, 5, 6, 7 }; 20 public string Name { get; set; } 21 22 public Object Clone() 23 { 24 MemoryStream memoryStream = new MemoryStream(); 25 BinaryFormatter formatter = new BinaryFormatter(); 26 formatter.Serialize(memoryStream, this); 27 memoryStream.Position = 0; 28 ShallowCopy obj = (ShallowCopy)formatter.Deserialize(memoryStream); 29 return obj; 30 } 31 public void Display() 32 { 33 foreach (int i in list) 34 { 35 Console.Write(i + ", "); 36 } 37 Console.WriteLine(Name); 38 } 39 }
执行结果
浅层拷贝
1 class Program 2 { 3 static void Main(string[] args) 4 { 5 ShallowCopy obj1 = new ShallowCopy(); 6 obj1.Name = "Tom"; 7 ShallowCopy obj2 = (ShallowCopy)obj1.Clone(); 8 obj1.Name = "Jerry"; 9 obj1.list[0] = 1; 10 obj1.Display(); 11 obj2.Display(); 12 Console.WriteLine(object.ReferenceEquals(obj1, obj2)); 13 Console.ReadKey(); 14 } 15 } 16 [Serializable] 17 public class ShallowCopy : ICloneable 18 { 19 public List<int> list = new List<int>() { 4, 5, 6, 7 }; 20 public string Name { get; set; } 21 22 public Object Clone() 23 { 24 object obj = this.MemberwiseClone(); 25 return obj; 26 } 27 public void Display() 28 { 29 foreach (int i in list) 30 { 31 Console.Write(i + ", "); 32 } 33 Console.WriteLine(Name); 34 } 35 }
执行结果:
8、其他克隆方式
8.1 手工克隆
一个能够保证对象完全按照你所想的那样进行克隆的方式是手工克隆对象的每一个域(field)。这种方式的缺点是麻烦而且容易出错:如果你在类中增加了一个域,你很可能会忘记更新Clone方法。还要在克隆引用对象指向原始对象的时候,注意避免无限循环引用。下面是一个进行深拷贝的简单例子:
1 public class Person : ICloneable 2 { 3 public string Name; 4 5 public Person Spouse; 6 7 public object Clone() 8 9 { 10 Person p = new Person(); 11 12 p.Name = this.Name; 13 14 if (this.Spouse != null) 15 16 p.Spouse = (Person)this.Spouse.Clone(); 17 18 return p; 19 20 } 21 }
8.2 用反射进行克隆
用反射进行克隆是使用Activator.CreateInstance方法来创建一个相同类型的新对象,然后用反射对所有域进行浅拷贝。这种方法的优点是它是全自动的,不需要在对象中添加或删除成员的时候修改克隆方法。另外它也能被写成提供深拷贝的方法。缺点是使用了反射,因此会比较慢,而且在部分受信任的环境中是不可用的。
1 class Program 2 { 3 static void Main(string[] args) 4 { 5 MyContainer con1 = new MyContainer(); 6 MyContainer con2 = (MyContainer)con1.Clone(); 7 con1.myArray[2].ID = 5; 8 Console.WriteLine(con1.myArray[2].ID); 9 Console.WriteLine(con2.myArray[2].ID); 10 Console.WriteLine(con1.myArray[2].Name + " " + con2.myArray[2].Name); 11 Console.WriteLine(object.ReferenceEquals(con1, con2)); 12 Console.ReadKey(); 13 } 14 } 15 public class MyClass 16 { 17 public int ID { get; set; } 18 public string Name { get; set; } 19 } 20 public class MyContainer : BaseObject 21 { 22 public MyClass[] myArray = new MyClass[5]; 23 public MyContainer() 24 { 25 for (int i = 0; i < 5; i++) 26 { 27 MyClass myClass = new MyClass(); 28 myClass.ID = i; 29 myClass.Name = "Tom"; 30 this.myArray[i] = myClass; 31 } 32 } 33 } 34 35 /// <summary> 36 /// BaseObject类是一个用来继承的抽象类。 37 /// 每一个由此类继承而来的类将自动支持克隆方法。 38 /// 该类实现了Icloneable接口,并且每个从该对象继承而来的对象都将同样地 39 /// 支持Icloneable接口。 40 /// </summary> 41 public abstract class BaseObject : ICloneable 42 { 43 /// <summary> 44 /// 克隆对象,并返回一个已克隆对象的引用 45 /// </summary> 46 /// <returns>引用新的克隆对象</returns> 47 public object Clone() 48 { 49 //首先我们建立指定类型的一个实例 50 object newObject = Activator.CreateInstance(this.GetType()); 51 //我们取得新的类型实例的字段数组。 52 FieldInfo[] fields = newObject.GetType().GetFields(); 53 int i = 0; 54 foreach (FieldInfo fi in this.GetType().GetFields()) 55 { 56 //我们判断字段是否支持ICloneable接口。 57 Type ICloneType = fi.FieldType.GetInterface("ICloneable", true); 58 if (ICloneType != null) 59 { 60 //取得对象的Icloneable接口。 61 ICloneable IClone = (ICloneable)fi.GetValue(this); 62 //我们使用克隆方法给字段设定新值。 63 fields[i].SetValue(newObject, IClone.Clone()); 64 } 65 else 66 { 67 // 如果该字段不支持ICloneable接口,直接设置即可。 68 fields[i].SetValue(newObject, fi.GetValue(this)); 69 } 70 //现在我们检查该对象是否支持IEnumerable接口,如果支持, 71 //我们还需要枚举其所有项并检查他们是否支持IList 或 IDictionary 接口。 72 Type IEnumerableType = fi.FieldType.GetInterface("IEnumerable", true); 73 if (IEnumerableType != null) 74 { 75 //取得该字段的IEnumerable接口 76 IEnumerable IEnum = (IEnumerable)fi.GetValue(this); 77 Type IListType = fields[i].FieldType.GetInterface("IList", true); 78 Type IDicType = fields[i].FieldType.GetInterface("IDictionary", true); 79 int j = 0; 80 if (IListType != null) 81 { 82 //取得IList接口。 83 IList list = (IList)fields[i].GetValue(newObject); 84 foreach (object obj in IEnum) 85 { 86 //查看当前项是否支持支持ICloneable 接口。 87 ICloneType = obj.GetType().GetInterface("ICloneable", true); 88 if (ICloneType != null) 89 { 90 //如果支持ICloneable 接口, 91 //我们用它来设置列表中的对象的克隆 92 ICloneable clone = (ICloneable)obj; 93 list[j] = clone.Clone(); 94 } 95 //注意:如果列表中的项不支持ICloneable接口,那么 96 //在克隆列表的项将与原列表对应项相同 97 //(只要该类型是引用类型) 98 j++; 99 } 100 } 101 else if (IDicType != null) 102 { 103 //取得IDictionary 接口 104 IDictionary dic = (IDictionary)fields[i].GetValue(newObject); 105 j = 0; 106 foreach (DictionaryEntry de in IEnum) 107 { 108 //查看当前项是否支持支持ICloneable 接口。 109 ICloneType = de.Value.GetType(). 110 GetInterface("ICloneable", true); 111 if (ICloneType != null) 112 { 113 ICloneable clone = (ICloneable)de.Value; 114 dic[de.Key] = clone.Clone(); 115 } 116 j++; 117 } 118 } 119 } 120 i++; 121 } 122 return newObject; 123 } 124 }
8.3 使用序列化进行克隆
克隆一个对象的最简单的方法是将它序列化并立刻反序列化为一个新对象。和反射方法一样,序列化方法是自动的,无需在对对象成员进行增删的时候做出修改。缺点是序列化比其他方法慢,甚至比用反射还慢,所有引用的对象都必须是可序列化的(Serializable)。另外,取决于你所使用的序列化的类型(XML,SOAP,二进制)的不同,私有成员可能不能像期望的那样被克隆。
8.4 使用IL进行克隆 (代码希望有大牛能实现,本人小白^_^)
一种罕见的解决方案是使用IL(中间语言)来进行对象克隆。这种方式创建一个动态方法(DynamicMethod),获取中间语言生成器(ILGenerator),向方法中注入代码,把它编译成一个委托,然后执行这个委托。委托会被缓存,因此中间语言只在初次克隆的时候才会生成,后续的克隆都不会重新生成一遍。尽管这种方法比使用反射快,但是这种方法难以理解和维护。
8.5 使用扩展方法进行克隆 (代码希望有大牛能实现,本人小白^_^)
Havard Stranden用扩展方法(extention method)创建了一个自定义的克隆框架。这个框架能够创建对象及其引用的对象的深拷贝,不管对象结构有多复杂。缺点是,这是一个不提供源代码的自定义框架(更新:现在已经包括源代码了,参见本文评论),并且它不能在不使用无参数构造器的时候,拷贝由私有方法创建的对象。另一个问题,也是所有自动化的深克隆方法共有的问题是,深拷贝通常需要灵活地处理不能进行简单自动化特殊情况(例如未受管理的资源)。
9、总结
.NET中使用Object的MemberwiseClone()方法来实现浅拷贝,通过序列化和反序列化实现深拷贝。
10、参考
http://www.cnblogs.com/jiangj/archive/2010/08/20/1804518.html shaya
http://www.cnblogs.com/nianshi/archive/2008/01/04/1025542.html 念时
http://www.cnblogs.com/caoben313/archive/2010/08/11/1797230.html 林声歌
http://liping13599168.cnblogs.com/ Leepy
http://www.soaspx.com/dotnet/csharp/csharp_20110716_7878.html
http://www.csharpwin.com/csharpspace/9308r573.shtml
http://www.cnblogs.com/luckeryin/archive/2010/03/17/1688106.html
作者:MrZivChu
2013-08-02 23:22:52