In this topic
- About implementing cloning
- Cloning in ArcGIS
- Copying members (values and object references)
- Implementing IClone
- Guaranteeing deep cloning
About implementing cloning
Cloning is the process of copying an object, that is, the creation of an instance of a class representing information equivalent to the original instance. Creating a copy of a particular object is more complex than 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 assigning a new variable.
To copy the Point object—creating an 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 an 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 ArcObjects Help for .NET developers.
Copying members (values and object references)
The details of the clone operation are encapsulated in the class implementation. The class regulates the members that 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. An 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 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 deep and shallow levels of cloning:
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 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. 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 should be cloned. This is decided on a case-by-case basis, and both techniques can 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.
Be careful when cloning objects that hold object references. In many cases, a referenced object can hold references to more objects, which in turn, hold references to other objects, and so on.
Transient members
When coding a clone method, remember 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 that 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 have connection-specific information and are not clonable. An overview window and a tool control have a window handle and are not clonable. If a new instance is required, the object is created from the beginning.
Sometimes it is more appropriate for a class not to replicate a member in its clone.
If you need to implement IClone on such an object, ensure that any instance-specific information is populated from the beginning instead of copying the instance-specific values.
Implementing IClone
If you implement cloning in your custom components, 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 creates an instance of itself, then copies 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 the IPersist and IPersistStream interfaces. It is complicated to implement IPersistStream in .NET; therefore, this technique is not covered in this topic.
In the Clone method, begin by creating an 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 an 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. See the following code example:
Assign should receive a valid instance of the class.
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 ISpatialReference. If ISpatialReference is another object reference, you can perform a deep clone. If the object 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, create an 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 might 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 might decide to clear any stateful information held by the cloner before assigning the properties from the clonee. In this case, you might 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 might 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 also duplicates all sub-objects it contains. Even if the object supports IClone, you might 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.
//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 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 might 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
'1. Make sure the "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
'3. 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:
Sample: Clonable objectSample: Triangle graphic element
Development licensing | Deployment licensing |
---|---|
Engine Developer Kit | Engine Runtime |
ArcView | ArcView |
ArcEditor | ArcEditor |
ArcInfo | ArcInfo |