You need a method of performing a shallow cloning operation, a deep cloning operation, or both on a data type that may also reference other types.
Shallow copying
means that the copied
object’s fields will reference the same objects as
the original object. To allow shallow copying, add the following
Clone
method to your
class:
using System; using System.Collections; public class ShallowClone : ICloneable { public int data = 1; public ArrayList listData = new ArrayList( ); public object objData = new object( ); public object Clone( ) { return (this.MemberwiseClone( )); } }
Deep copying
or cloning means
that the copied object’s fields will reference new
copies of the original object’s fields. This method
of copying is more time-consuming than the shallow copy. To allow
deep copying, add the following Clone
method to
your class:
using System; using System.Collections; using System.Runtime.Serialization.Formatters.Binary; using System.IO; [Serializable] public class DeepClone : ICloneable { public int data = 1; public ArrayList listData = new ArrayList( ); public object objData = new object( ); public object Clone( ) { BinaryFormatter BF = new BinaryFormatter( ); MemoryStream memStream = new MemoryStream( ); BF.Serialize(memStream, this); memStream.Flush( ); memStream.Position = 0; return (BF.Deserialize(memStream)); } }
Add an overloaded Clone
method to your class to
allow for deep or shallow copying. This method allows you to decide
at runtime how your object will be copied. The code might appear as
follows:
using System; using System.Collections; using System.Runtime.Serialization.Formatters.Binary; using System.IO; [Serializable] public class MultiClone : ICloneable { public int data = 1; public ArrayList listData = new ArrayList( ); public object objData = new object( ); public object Clone(bool doDeepCopy) { if (doDeepCopy) { BinaryFormatter BF = new BinaryFormatter( ); MemoryStream memStream = new MemoryStream( ); BF.Serialize(memStream, this); memStream.Flush( ); memStream.Position = 0; return (BF.Deserialize(memStream)); } else { return (this.memberwiseClone( )); } } public object Clone( ) { return (Clone(false)); } }
Cloning
is the
ability to make an exact copy (a clone) of an instance of a type.
Cloning may take one of two forms: a shallow copy or a deep copy.
Shallow copying is relatively easy. It involves copying the object
that the Clone
method was called on. The reference
type fields in the original object are copied over, as are the value
type fields. This means that if the original object contains a field
of type StreamWriter
, for instance, the cloned
object will point to this same instance of the original
object’s StreamWriter
; a new
object is not created.
There is no need to deal with static
fields when
performing a cloning operation. There is only one memory location
reserved for each static field per class. Besides, the cloned object
will have access to the same static fields as the original.
Support for shallow copying is implemented by the
MemberwiseClone
method of the
Object
class, which serves as the base class for
all .NET classes. So the following code allows a shallow copy to be
created and returned by the Clone
method:
public object Clone( ) { return (this.MemberwiseClone( )); }
Making a deep
copy is the second way of cloning an object. A deep copy will make a
copy of the original object just as the shallow copy does. However, a
deep copy will also make separate copies of each reference type field
in the original object. Therefore, if the original object contains a
StreamWriter
type field, the cloned object will
also contain a StreamWriter
type field, but the
cloned object’s StreamWriter
field will point to a new StreamWriter
object, not
the original object’s
StreamWriter
object.
Support for deep copying is not automatically provided by the
Clone
method or the .NET Framework. Instead, the
following code illustrates an easy way of implementing a deep copy:
BinaryFormatter BF = new BinaryFormatter( ); MemoryStream memStream = new MemoryStream( ); BF.Serialize(memStream, this); memStream.Flush( ); memStream.Position = 0; return (BF.Deserialize(memStream));
Basically, the original object is serialized out to a memory stream
using binary serialization, then it is deserialized into a new
object, which is returned to the caller. Note that it is important to
flush memory and reposition the memory stream pointer back to the
start of the stream before calling the Deserialize
method; otherwise, an exception indicating that the serialized object
contains no data will be thrown.
Performing a deep copy using object serialization allows the underlying object to be changed without having to modify the code that performs the deep copy. If you performed the deep copy by hand, you’d have to make a new instance of all the instance fields of the original object and copy them over to the cloned object. This is a tedious chore in and of itself. If a change is made to the fields of the object being cloned, the deep copy code must also change to reflect this modification. Using serialization, we rely on the serializer to dynamically find and serialize all fields contained in the object. If the object is modified, the serializer will still make a deep copy without any code modifications. Two reasons you would possibly want to do a deep copy by hand are:
It can be faster in terms of application performance.
The serialization technique presented in this recipe works properly only when everything in your object is serializable. Of course, manual cloning doesn’t always help there either—some objects are just inherently nonclonable. Suppose you have a network management application where an object represents a particular printer on your network. What’s it supposed to do when you clone it? Fax a purchase order for a new printer?
One problem inherent with deep copying is performing a deep copy on a nested data structure with circular references. This recipe manages to make it possible to deal with circular references, although it’s a tricky problem. So, in fact, you don’t need to avoid circular references if you are using this recipe.