深入了解 .NET 中的对象克隆






3.61/5 (50投票s)
2003年8月1日
4分钟阅读

207398

1270
本文讨论了对象克隆的必要性、实现方式以及一些相关的注意事项。
引言
在本文中,我将讨论 .NET 编程中一个有趣方面——对象克隆。本文讨论了对象克隆的必要性、实现方式以及一些相关的注意事项。
背景
大家一定都知道 .NET 对象有两种类型:**值类型**和**引用类型**。值类型对象的变量存储对象本身的数据,并具有“赋值时复制”的行为。也就是说,接下来的讨论不适用于值类型。
另一方面,引用类型变量实际上是指向堆上某些内存的指针。因此,如果创建一个新的引用类型变量并将其分配给一个现有对象,您实际上是创建了另一个指向堆上相同内存的指针。那么,如果您需要创建对象的新副本并将其存储在变量中,该怎么办?本文就是为此而写的!
那么,为什么需要克隆?
我认为,如果设置对象的状态成本很高,而您只需要对象的副本来对现有状态进行一些更改,那么克隆就非常必要。让我们举一个很好的例子来理解我刚才说的话。考虑 DataTable
类。构建 DataTable
可能涉及查询数据库以获取架构和数据、添加约束、设置主键等操作。因此,如果您只需要此 DataTable
的新副本,只是为了对架构进行少量更改或添加新行等,那么克隆现有对象并在此副本上进行操作会更明智,而不是重新创建新的 DataTable
,这可能需要更多的时间和资源。
克隆也广泛适用于 Array
和集合,在这些情况下,您经常需要现有元素的副本。
克隆的类型
我们可以根据“克隆多少”将克隆分为两种类型:**深克隆**和**浅克隆**。浅克隆是与原始对象类型相同的新实例,其中包含值类型字段的副本。但是,如果字段是引用类型,则复制的是引用,而不是被引用的对象。因此,原始对象中的引用和克隆中的引用指向同一个对象。另一方面,对象的深克隆包含直接或间接由对象引用的所有内容的副本。我们来看一个例子。
X 是一个对象,它引用对象 A,对象 A 也引用对象 M。X 的浅拷贝是对象 Y,它也引用对象 A。相比之下,X 的深拷贝是对象 Y,它直接引用对象 B,并间接引用对象 N,其中 B 是 A 的副本,N 是 M 的副本。下图直观地展示了这一点,以便更好地理解。
实现克隆
System.Object
提供了一个受保护的方法 MemberwiseClone
,可用于实现浅克隆。此方法被标记为受保护,因此您可以在派生类上下文或该类本身内部访问此方法。
.NET 定义了一个名为 IClonable
的接口,需要实现此接口的类才具有超出浅克隆范围的功能(用于深克隆)。我们需要在接口的 Clone
方法中提供适当的实现来完成此操作。
有多种方法可以实现深克隆。一种方法是将对象序列化到内存流中,然后将其反序列化回新对象。我们将需要使用 **Binary** 格式化程序或 **SOAP** 格式化程序,它们执行深序列化。这种方法的缺点是类及其成员(整个对象图)必须标记为可序列化,否则格式化程序将抛出异常。
**反射**可能是实现此目的的另一种方法。Amir Harel 撰写的一篇好文章引起了我的注意。他使用此方法提供了一个很好的克隆实现。文章中的讨论也很好!链接如下:
请注意,对于上述任一方法,成员类型本身必须支持克隆,深克隆才能成功。也就是说,对象图必须是可序列化的,或者各个成员必须提供 IClonable
的实现。如果不是这种情况,那么我们就根本无法深克隆该对象!
结论
作为程序员,提供克隆功能是一件很方便的事情。但是,人们应该意识到提供此功能的必要性,并且在某些情况下,对象**绝对不应该**提供此功能。例如,SQLTransaction
类不支持克隆。此类表示 SQL Server 数据库中的事务。克隆该对象没有意义,因为我们可能无法理解数据库中活动事务的克隆!因此,如果您认为克隆对象的状态可能会在应用程序逻辑中造成不一致,请不要支持克隆。
在本文中,我总结了我对 .NET 中对象克隆的所有理解。如果您有任何其他想法、意见或异议,请随时讨论。这将有助于我建立更好的理解!