C# Canonical Forms—Cloneable Object


Jump to: navigation, search
CSharp-Online.NET:Articles
C# Articles

C# Canonical Forms

© 2006 Weldon W. Nash, III

Is the Object Cloneable?

As you know, objects in C# and in the CLR live on the heap and are accessed through references. You’re not actually making a copy of the object when you assign one object variable to another, as in the following code.

Object obj = new Object();
Object objCopy = obj;

After this code executes, objCopy doesn’t refer to a copy of obj; rather, you now have two references to the same Object instance.

However, sometimes it makes sense to be able to make a copy of an object. For that purpose, the Standard Library defines the ICloneable interface. When your object implements this interface, it is saying that it supports the ability to have copies of itself made. In other words, it claims that it can be used as a prototype to create new instances of objects. Objects of this type could participate in a prototype factory design pattern. This is opposite of the C++ world, where objects are able to be copied by default and you have to explicitly restrict objects of a type from being copied.

Before I go any further, let’s have a look at the ICloneable interface:

public interface ICloneable
{
   object Clone();
}

As you can see, the interface only defines one method, Clone, that returns an object reference. That object reference is intended to be the copy. All you have to do is return a copy of the object and you’re done, right? Well, not so fast.

You see, there’s a not-so-subtle problem with the definition of this interface. The documentation for the interface doesn’t indicate whether the copy returned should be a deep copy or a shallow copy. In fact, the documentation leaves it open for the class designer to decide. The difference between a shallow copy and a deep copy is only relevant if the object contains references to other objects. A shallow copy of an object creates a copy of the object whose contained object references refer to the same objects as the prototype’s references. A deep copy, on the other hand, creates a copy of the prototype where all of the contained objects are copied as well. In a deep copy, the object containment tree is traversed all the way down to the bottom and copies of each of those objects are made. Therefore, the result of a deep copy shares no underlying objects with the prototype.

This is enough to drive a good software designer insane. It seems only logical that if you really want to make a copy of an object, then a deep copy is the only true way to go. Fine! From this point onward, when I say "clone," I mean a deep copy.

In order for an object to effectively implement a clone of itself, remember that I’m saying that a clone is a deep copy, so all of its contained objects must provide ameans of creating a deep copy of themselves. You can quickly see the problem that comes with that requirement. You cannot guarantee a deep copy if your object contains references to objects that themselves cannot be deep-copied. This is precisely why the documentation for the ICloneable interface suffers from the lack of specification of copy semantics. Plus, and importantly, this lack of specification forces you to clearly document the ICloneable implemenation on any object that implements it so that consumers will know if the object supports a shallow or deep copy.

Let’s consider options for implementing the ICloneable interface on objects. If your object contains only value types, such as int, long, or values based on struct definitions where the structs contain no reference types, then you can use a shortcut to implement the Clone method by using Object.MemberwiseClone(), as in the following code:

using System;
 
public sealed class Dimensions : ICloneable
{
   public Dimensions( long width, long height ) {
      this.width = width;
      this.height = height;
   }
 
   // ICloneable implementation
   public object Clone() {
      return this.MemberwiseClone();
   }
 
   private long width;
   private long height;
}

MemberwiseClone is a protected method implemented on System.Object that an object can use to create a shallow copy of itself. However, it’s important to note one caveat, and that is that MemberwiseClone() creates a copy of the object without calling any constructors on the new object. It’s an object-creation shortcut. If your object relies upon the constructor getting called during creation— for example, if you send debug traces to the console during object construction—then MemberwiseClone()is not for you. If you absolutely must use MemberwiseClone(), and your object requires work to be done during the constructor call, you must factor that work out into a separate method. Then you can call that method from the constructor, and, in your Clone method, you can call that worker method on the new object after calling MemberwiseClone() to create the new instance. Although doable, it’s a tedious approach. An alternative way to implement the clone is to make use of a private copy constructor, as in the following code:

using System;
 
public sealed class Dimensions : ICloneable
{
   public Dimensions( long width, long height ) {
      Console.WriteLine( "Dimensions( long, long) called" );
 
      this.width = width;
      this.height = height;
   }
 
   // Private copy constructor used when making a 
   // copy of this object.
   private Dimensions( Dimensions other ) {
      Console.WriteLine( "Dimensions( Dimensions ) called" );
 
      this.width = other.width;
      this.height = other.height;
   }
 
   // ICloneable implementation
   public object Clone() {
      return new Dimensions(this);
   }
 
   private long width;
   private long height;
}

This method of cloning an object is the safest way in the sense that you have full control over how the copy is made. Any changes that need to be done regarding the way the object is copied can be made in the copy constructor. You must take care to consider what happens when you declare a constructor in a class. Any time you do so, the compiler will not emit the default constructor that it normally does when you don’t provide a constructor. If this private copy constructor listed previously was the only constructor defined in the class, users of the class would never be able to create instances of it. That’s because the default constructor is now gone, and no other publicly accessible constructor would exist. In this case, you have nothing to worry about since you also defined a public constructor that takes two parameters. Nevertheless, it’s an important point to consider during class design.

Now, let’s also consider objects that, themselves, contain references to other objects. Suppose you have an employee database, and you represent each employee with an object of type Employee. This Employee type contains vital information such as the employee’s name, title, and ID number. The name and possibly the formatted ID number are represented by strings, which are themselves reference type objects. For the sake of example, let’s implement the employee title as a separate class named Title. If you follow the guideline I created previously where you always do a deep copy on a clone, then you could implement the following clone method:

using System;
 
// Title class
//
public sealed class Title : ICloneable
{
   public enum TitleNameEnum {
      GreenHorn,
      HotshotGuru
   }
 
   public Title( TitleNameEnum title ) {
      this.title = title;
 
      LookupPayScale();
   }
 
   private Title( Title other ) {
      this.title = other.title;
 
      LookupPayScale();
   }
 
   // ICloneable implementation
   public object Clone() {
      return new Title(this);
   }
 
   private void LookupPayScale() {
      // Looks up the pay scale in a database. 
      // Payscale is based upon the title.
   }
 
   private TitleNameEnum title;
   private double minPay;
   private double maxPay;
}
 
// Employee class
//
public sealed class Employee : ICloneable
{
   public Employee( string name, Title title, string ssn ) {
      this.name = name;
      this.title = title;
      this.ssn = ssn;
   }
 
   private Employee( Employee other ) {
      this.name = String.Copy( other.name );
      this.title = (Title) other.title.Clone();
      this.ssn = String.Copy( other.ssn );
   }
 
   // ICloneable implementation
   public object Clone() {
      return new Employee(this);
   }
 
   private string name;
   private Title title;
   private string ssn;
}

Notice that you cannot copy the Title object with MemberwiseClone() because a side effect of the constructor is to call LookupPayScale() on the new object to retrieve the payscale for the title from the database. Let’s assume it’s possible that the payscale for the position can change between the prototype’s creation and the clone operation, so you always want to look that up in the database. Also, note that copies of the contained objects are made using their respective ICloneable methods. For the Title object, you merely call its implementation of Clone(). It turns out that System.String implements ICloneable. However, you cannot use the Clone method to create a deep copy of Employee. If you read the fine print on the String.Clone() implementation, you’ll see that it just returns a reference to itself. This is a perfect example of the issues I was talking about regarding the inconsistencies of the Clone() implementations out there. Instead, you have to use the static String.Copy method in order to get a real copy of the source string.

The fact that System.String returns a reference to itself when its ICloneable.Clone method is called is an optimization that its implementors introduced. Even though the implementation bars you from making a true deep clone of any object that contains string object references, the optimization is valid for two reasons. First, the documentation doesn’t specify whether you need to implement a deep or shallow clone. I’ve already discussed the pros and cons of that ommision in the contract specification. Second, System.String is an immutable object. Immutablability in objects is a powerful concept that I cover in the section, "Prefer Type Safety at All Times." The general idea is that once you create a string object, you can never change it for as long as it lives. Therefore, it becomes an efficiency burden to implement String.Clone() so that it always performs a deep copy. Clients of System.String()work the same way, whether String.Clone() performs a deep or shallow copy because of its immutability.

In efforts to make the ICloneable implementation document itself, you can use a custom attribute to mark the Clone method. This way, consumers of your object can determine at design time or at run time whether your object supports a deep clone or a shallow clone. Consider the following custom attribute:

using System;
 
namespace CloneHelpers
{
 
public enum CloneStyle {
   Deep,
   Shallow
}
 
[AttributeUsageAttribute(AttributeTargets.Method)]
public sealed class CloneStyleAttribute : Attribute
{
   public CloneStyleAttribute( CloneStyle clonestyle ) {
      this.clonestyle = clonestyle;
   }
 
   public CloneStyle Style {
      get {
         return clonestyle;
      }
   }
 
   private CloneStyle clonestyle;
}
 
}

Using this attribute, you can tag your clone implementations such that they are explicit about what type of clone operation they perform. But keep in mind, as shown, the attribute is only amarker and doesn’t enforce anything at run time. That’s not to say that you cannot create some other type that enforces a policy at run time based on attached custom attributes. Let’s revisit the Dimensionsclass and apply this attribute appropriately:

using System;
using CloneHelpers;
 
public sealed class Dimensions : ICloneable
{
   public Dimensions( long width, long height ) {
      this.width = width;
      this.height = height;
   }
 
   // ICloneable implementation
   [CloneStyleAttribute(CloneStyle.Deep)]
   public object Clone() {
      return this.MemberwiseClone();
   }
 
   private long width;
   private long height;
}

There is no question as to how the Clone() method is implemented, and consumers of this object will be well informed.

After this discussion, I’m sure you’ll agree that implementing something so seemingly innocuous as ICloneable is not so simple after all.


Caution Avoid implementing ICloneable. As alarming as that sounds, Microsoft is actually making this recommendation. The problem stems from the fact that the contract doesn’t specify whether the copy should be deep or shallow. In fact, as noted in Krzysztof Cwalina and Brad Abrams’ Framework Design Guidelines: Conventions, Idioms, and Patterns for Reusable .NET Libraries (Boston, MA: Addison-Wesley Professional, 2005), Cwalina searched the entire code base of the .NET Framework and couldn’t find any code that uses ICloneable. Had the Framework designers and developers been using this interface, they probably would have stumbled across the omission in the ICloneable specification and fixed it.

However, this recommendation is not to say that you shouldn’t implement a Clone method if you need one. If your class needs a clone method, you can still implement one on the public contract of the class without actually implementing ICloneable.



Previous_Page_.gif Next_Page_.gif


Personal tools