DekGenius.com
[ Team LiB ] Previous Section Next Section

Recipe 3.28 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;

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));
    }
}

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. 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:

  1. It can be faster in terms of application performance.

  2. 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.

See Also

See the "ICloneable Interface" and "Object.MemberwiseClone Method" topics in the MSDN documentation.

    [ Team LiB ] Previous Section Next Section