Recipe 3.26. Building Cloneable
Classes
problem
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.
Solution
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;
using System.Collections.Generic;
public class ShallowClone : ICloneable
{
public int data = 1;
public List<string> listData = new List<string>();
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.Collections.Generic;
using System.Runtime.Serialization.Formatters.Binary;
using System.IO;
[Serializable]
public class DeepClone : ICloneable
{
public int data = 1;
public List<string> listData = new List<string>();
public object objData = new object();
public object Clone()
{
BinaryFormatter BF = new BinaryFormatter();
MemoryStream memStream = new MemoryStream();
BF.Serialize(memStream, this);
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.Collections.Generic;
using System.Runtime.Serialization.Formatters.Binary;
using System.IO;
[Serializable]
public class MultiClone : ICloneable
{
public int data = 1;
public List<string> listData = new List<string>();
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.Position = 0;
return (BF.Deserialize(memStream));
}
else
{
return (this.MemberwiseClone());
}
}
public object Clone()
{
return (Clone(false));
}
}
Discussion
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, per
application domain. 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 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, you 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.
One reason you might want to do a deep copy by
hand is that 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 eithersome
objects are just
inherently
noncloneable. Suppose you have a
network management application in which 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.
See Also
See the "ICloneable Interface" and
"Object.MemberwiseClone Method" topics in the MSDN
documentation.
|