Implementing cloning


Summary.NET objects are of two types: value and reference. Variables of value type objects hold the object bits themselves and have "copy-on-assignment" behavior. Variables of reference (ref) type are actually pointers to memory. That is, when you create a new variable of ref type and assign it to an existing object, you are actually creating another pointer to the same memory. This topic explains how to create a new copy of an object and hold it in a variable.

Development licensing Deployment licensing
Engine Developer Kit Engine Runtime
ArcView ArcView
ArcEditor ArcEditor
ArcInfo ArcInfo

In this topic


About implementing cloning

Cloning is the process of copying an object—the creation of a new instance of a class representing information equivalent to the original instance. Creating a copy of a particular object is more complex than simply assigning a new variable. For example, the following code example creates two variables that point to the same object in memory.
 

[C#]
IPoint pointOne = new PointClass();
IPoint pointTwo = pointOne;

[VB.NET]
Dim pointOne As IPoint = New PointClass()
Dim pointTwo as IPoint = pointOne
See the following illustration of two variables that point to the same object in memory:
 
 
Copying an object is more complex than simply assigning a new variable.
To actually copy the Point object, creating a new instance of a Point with comparable data to the first Point, use the IClone interface. See the following code example:
 

[C#]
IClone clone = pointOne as IClone;
pointTwo = clone.Clone() as IPoint;

[VB.NET]
Dim clone As IClone
clone = Ctype(pointOne, IClone)
pointTwo = Ctype(clone.Clone(), IPoint)
See the following illustration of the IClone interface:
 
 
Cloning creates a new instance in memory.

Cloning in ArcGIS

The technique shown previously is used extensively in ArcGIS by ArcObjects classes that implement the IClone interface.
 
For example, before the application passes an object to a property page, it clones the object. If the OK or Apply button is clicked, the properties of the cloned object are set into the original object. Another use of cloning in ArcObjects is by methods or properties that specifically return a copy of an object, for example, the IFeature.ShapeCopy property.
 
You can find other examples of how cloning is used by searching the samples included in the ArcGIS Developer Help.

Copying members: Values and object references

The details of the clone operation are encapsulated in the class implementation—the class regulates which of its members should be copied and how they should be copied.
 
Each class that implements cloning decides how to clone itself.
Terminology used in this section—The original object will be referred to as the cloner; the object that performs the cloning operation. The object resulting from the cloning process will be called the clonee.
Shallow and deep cloning
For a simple object whose members contain only value type information, the cloning process is relatively simple. A new instance of the class is created, and the values of all the members of the clonee are set to equal the values of the cloner. The clonee object is independent of the cloner.
 
For an object whose members contain object references, the cloning process becomes more complex. If the cloner copies only the object references to the clonee, it is sometimes known as a shallow clone. If new instances of each referenced object are created, and the clonee's members are set to reference these new objects, it is referred to as a deep clone. See the following illustration that shows the different levels of cloning: shallow and deep:
 
 
Both shallow and deep cloning are used by ArcObjects classes. An example of a deep clone, where referenced objects are also cloned, is that of a graphic element. Both the geometry and symbol of the graphic element are cloned; the geometry and symbol properties of the clonee are entirely separate from the original object's geometry and symbol.
 
In other cases, it is logical to simply copy an object reference to the new object. Shallow cloning is used, for example, on the geometries in a feature class. Every geometry has an object reference indicating its coordinate system (for example, IGeometry.SpatialReference). Cloning the geometry produces an object with a reference to the same underlying spatial reference object. In this case, only the object reference is copied, as it is logical for all geometries in a feature class to hold a reference to the same spatial reference object, as does the layer itself. The spatial reference can then be changed by a single method call.
 
There is no simple rule for deciding whether an object reference should be copied or the referenced object itself should be cloned. This is decided on a case-by-case basis, and both techniques may be included in a single class.
 
For each of its private members, a class needs to make the appropriate choice between shallow or deep cloning.
You must be careful when cloning objects that hold object references. In many cases, a referenced object may hold references to yet more objects, which in turn hold references to other objects, and so on.
 
Transient members
When coding a clone method, keep in mind that some members should not be directly copied at all—a window handle (hWnd), handle device context (HDC), file handles, and Graphical Device Interface (GDI) resources, for example, contain instance-specific information, which should not be duplicated directly to another object.
 
In most cases, it is inappropriate to clone an object that contains this type of instance-specific information. For example, a workspace and feature class both have connection-specific information and are not clonable; an overview window and a tool control both have a window handle and are not clonable. If a new instance is required, the object is created from scratch.
 
Sometimes it is more appropriate for a class not to replicate a member in its clone at all.
If you need to implement IClone on such an object, ensure that any instance-specific information is populated from scratch instead of simply copying the instance-specific values.
 

Implementing IClone

If you implement cloning in your custom components, you need to decide how you want to copy the information contained in your class—whether shallow or deep cloning is most appropriate for each member—and how to implement it.
 
Coding IClone
In general, there are two primary techniques to implement cloning. In the first, the cloner object will create a new instance of itself, then copy all of its members onto it.
 
The second technique takes advantage of the object's persistence mechanism functionality by temporarily saving the object to a memory stream (ObjectStream), then "rehydrating" it by loading it back from the memory stream. This technique requires the object to implement interfaces IPersist and IPersistStream. It is quite complicated to implement IPersistStream in .NET; therefore, this technique is not covered in this topic.      
 
In the Clone method, begin by creating a new instance of the class, which is the clonee. You can then call IClone.Assign() to copy the properties of the cloner to the clonee. Last, return a reference to the clonee from clone. The following code example is part of a sample Clonable object:
 

[C#]
public IClone Clone()
{
    ClonableObjClass obj = new ClonableObjClass();
    obj.Assign(this);
 
    return (IClone)obj;
}

[VB.NET]
Public Function Clone() As IClone Implements IClone.Clone
    Dim obj As ClonableObjClass = New ClonableObjClass()
    obj.Assign(Me)

    Return CType(obj, IClone)
End Function
Clone should create a new instance of the class.
The Assign method receives a reference to a second instance of the class, src—this is the clonee. First, check src to see if it is pointing to a valid object—if not, raise the appropriate standard Component Object Model (COM) error.
Assign should receive a valid instance of the class.

[C#]
public void Assign(IClone src)
{
 //1. Make sure src is pointing to a valid object.
 if (null == src)
 {
    throw new COMException("Invalid object.");
 }

  //2. Verify the type of src.
 if (!(src is ClonableObjClass))
 {
    throw new COMException("Bad object type.");
 }

 //3. Assign the properties of src to the current instance.
 ClonableObjClass srcClonable = (ClonableObjClass)src;
 m_name = srcClonable.Name;
 m_version = srcClonable.Version;
 m_ID = srcClonable.ID;

 //Use shallow cloning (use a reference to the same member object). 
    m_spatialRef = srcClonable.SpatialReference)
}

[VB.NET]
Public Sub Assign(ByVal src As IClone) Implements IClone.Assign
  '1. Make sure src is pointing to a valid object.
  If Nothing Is src Then
     Throw New COMException("Invalid object.")
  End If

  '2. Verify the type of src.
  If Not (TypeOf src Is ClonableObjClass) Then
     Throw New COMException("Bad object type.")
  End If

  '3. Assign the properties of src to the current instance.
  Dim srcClonable As ClonableObjClass = CType(src, ClonableObjClass)
  m_name = srcClonable.Name
  m_version = srcClonable.Version
  m_ID = srcClonable.ID

  'Use shallow cloning (use a reference to the same member object).
  m_spatialRef = spatialRef = srcClonable.SpatialReference)
End Sub
The cloner copies values from the clonee. See the following illustration:
 
 
The previous Assign code shows a shallow clone of the SpatialReference property. If SpatialReference is another object reference, you can perform a deep clone. If the object itself supports IClone, this is straightforward. See the following code example:
 

[C#]
IClone cloned = srcClonable.SpatialReference as IClone;
if (null != cloned)
{
    m_spatialRef = (ISpatialReference)cloned.Clone();
}

[VB.NET]
Dim cloned As IClone = CType(srcClonable.SpatialReference, IClone)
If Not cloned Is Nothing Then
    m_spatialRef = CType(cloned, ISpatialReference)
End If
If the member object does not support IClone, you must create a new object and set its properties from the existing property of the source object, scr.
 
Consider whether it is more appropriate to copy only an object reference (for example, all the geometries of a feature class hold a reference to the same spatial reference), clone the object reference, or leave the member uncopied to be set by the client code as appropriate.
 
When coding the Assign method, consider the choice of shallow or deep cloning. Consider that some member variables may not be suitable for cloning.
As an example, consider how a RandomColorRamp performs an Assign. The cloner RandomColorRamp will have the same MinSaturation, MaxSaturation, MinValue, MaxValue, StartHue, EndHue, UseSeed, Seed, and Name as the clonee. However, the Assign method does not copy the value of Size or call the CreateRamp method; this means the color ramp has no array of colors and cannot be used in a renderer at that point. After a call to Assign, the client must set up the Colors array of the RandomColorRamp by setting its Size property and calling its CreateRamp method.
 
Another consideration when coding your Assign method should be the current state of both the cloner and clonee objects. You may decide to clear any stateful information held by the cloner before assigning the properties from the clonee. In this case, you may want to add an internal initialization function to set the values of the class to a known initial state. This function could then be called from your class initialization function.
 
You may want to clear or reinitialize any member variables before performing an Assign to ensure the result is a faithful clone.

Guaranteeing deep cloning

To verify deep cloning of your object's ref members, consider using the ObjectCopy object. It provides a mechanism to duplicate an object using an object's persistence mechanism (IPersistStream). The object's state is written to a temporary stream, then rehydrated from that stream into a new instance of the object. This process is also known as a deep clone since an object will also duplicate all subobjects it contains. Even if the object supports IClone, you may still want to use ObjectCopy since it does a full copy, or deep clone, of the object. See the following code example:
 
To use ObjectCopy to deep clone an object, the object must implement the IPersistStream interface.

[C#]
//Deep clone the spatial reference using an ObjectCopy.
if (null == srcClonable.SpatialReference)
   m_spatialRef = null;
else
{ 
    IObjectCopy objectCopy = new ObjectCopyClass();
   object obj = objectCopy.Copy((object)srcClonable.SpatialReference);
   m_spatialRef = (ISpatialReference)obj;
}

[VB.NET]
'Deep clone the spatial reference using an ObjectCopy.
If Nothing Is srcClonable.SpatialReference Then
   m_spatialRef = Nothing
Else
   Dim objectCopy As IObjectCopy = New ObjectCopyClass()
   Dim obj As Object = objectCopy.Copy(CObj(srcClonable.SpatialReference))
   m_spatialRef = CType(obj, ISpatialReference)
End If
The IsEqual method should compare the cloner (this) and the clonee (other) to see if all the members are equal in value; it returns true if all the members are equal. See the following code example:
 

[C#]
public bool IsEqual(IClone other)
{
//1. Make sure the "other" object is pointing to a valid object.
if (null == other)
 throw new COMException("Invalid object.");

//2. Verify the type of "other."
if (!(other is ClonableObjClass))
 throw new COMException("Bad object type.");

ClonableObjClass otherClonable = (ClonableObjClass)other;

//Test that all the object's properties are the same.
if (otherClonable.Version == m_version && 
   otherClonable.Name == m_name && 
   otherClonable.ID == m_ID && 
   ((IClone)otherClonable.SpatialReference).IsEqual((IClone)m_spatialRef)))
   return true;

return false;
}

[VB.NET]
Public Function IsEqual(ByVal other As IClone) As Boolean Implements IClone.IsEqual
'1. Make sure that "other" is pointing to a valid object.
If Nothing Is other Then
 Throw New COMException("Invalid object.")
End If

'2. Verify the type of "other."
If Not (TypeOf other Is ClonableObjClass) Then
 Throw New COMException("Bad object type.")
End If

Dim otherClonable As ClonableObjClass = CType(other, ClonableObjClass)

'Test that all the object's properties are the same.
If otherClonable.Version = m_version AndAlso _
 otherClonable.Name = m_name AndAlso _
 otherClonable.ID = m_ID AndAlso _
 CType(otherClonable.SpatialReference, IClone).IsEqual(CType(m_spatialRef, IClone)) Then      Return True
 Return True
End If

Return False
End Function
If a property holds an object reference that supports IClone, use IClone.IsEqual on the member object to evaluate if it is equal to the member object of the passed-in reference, other. Remember to check all the members of all the interfaces that are supported by the object.
IsEqual should determine if two different objects have values that can be considered equivalent.
You decide what your class considers to be equal values. You may decide that two IColor members are equal if they have the same red, green, blue (RGB) value, even though one is an RGB color and one is a cyan, magenta, yellow, and black (CMYK) color.
 
To implement IsIdentical, compare the interface pointers to see if the cloner (this) and the clonee (other) point to the same underlying object in memory. See the following code example:
 

[C#]
public bool IsIdentical(IClone other)
{
//1. Make sure the "other" object is pointing to a valid object.
if (null == other)
  throw new COMException("Invalid object.");

 //2. Verify the type of "other."
if (!(other is ClonableObjClass))
  throw new COMException("Bad object type.");

//3. Test if the other is "this."
if ((ClonableObjClass)other == this)
  return true;

return false;
}

[VB.NET]
Public Function IsIdentical(ByVal other As IClone) As Boolean Implements IClone.IsIdentical
'Make sure that other is pointing to a valid object.
If Nothing Is other Then
  Throw New COMException("Invalid object.")
End If

'Verify the type of other.
If Not (TypeOf other Is ClonableObjClass) Then
  Throw New COMException("Bad object type.")
End If

'Test if the other is this.
If CType(other, ClonableObjClass) Is Me Then
  Return True
End If

Return False
End Function
IsIdentical should compare interface pointers to see if they reference the same underlying object. See the following illustration:
 
 


See Also:

Clonable object
Triangle graphic element