The Pitfall of ICloneable

Or, why you should never use ICloneable.

So I decided I needed to support cloning for my objects so I can modify the object and compare it to the original (very useful for unit-testing, dirty checking, etc.). The .Net framework comes with a handy interface to publish this ability called ICloneable. The documentation states "Supports cloning, which creates a new instance of a class with the same value as an existing instance". Great! It comes with one method: Clone. Can't be too hard to implement this interface, right?

A quick peek at the documentation for the Clone method says the implementation "Creates a new object that is a copy of the current instance". And so every textbook implementation goes on to show you how you do that:

public object Clone()
{
   return this.MemberwiseClone();
}

But what is the point of that, besides adding more code to maintain? EVERY object already supports MemberwiseClone! Oh yes, we were trying to satisfy an interface contract... But hold on, it gets worse as the documentation then opens up an infinite can of worms with this remark:

Clone can be implemented either as a deep copy or a shallow copy. In a deep copy, all objects are duplicated; whereas, in a shallow copy, only the top-level objects are duplicated and the lower levels contain references.

The resulting clone must be of the same type as or a compatible type to the original instance.

The Clone method, and thus the ICloneable interface just became a free-for-all, do as you like, contract. You may deep copy, shallow copy, heck you can even change the type! Technically this can all result in correct code, but the sheer absence of semantics turns the use of this interface into a humongous pitfall. Within your own sphere of influence you can set an expected semantic, but as soon as your code leaves this safe place, or you introduce code from another source which applied different semantics to the ICloneable interface you are in for a debugging session from hell.

ICloneable just became unusable.

Go ahead and mark it as obsolete in your copy of the framework. Not all is lost however, you can work around this issue by defining your own interface contract with its own semantics, but do not inherit it from ICloneable as this would reintroduce the issue. For example:

/// <summary>
/// Supports deep-copy cloning, which creates a 
/// new instance of a class of the same type and 
/// with the same value as an existing instance.
/// </summary>
/// <typeparam name="T">Type of the class 
/// being cloned.</typeparam>
public interface IDeepCloneable<T>
{
   /// <summary>
   /// Creates an equally typed deep-copy clone 
   /// of this object.
   /// </summary>
   /// <returns>An equally typed deep-copy clone 
   /// of this object.</returns>
   T Clone();
}

This interface contract sets a clear copy and type expectation (and gets rid of the pesky Object return type, that was so 1.0 baby!), but defining cloning semantics is not an easy task. Data containers, business- and UI-objects all have different needs and come with different cloning behaviors. Defining an interface for each of those will help keep expectations clear and ease integration with 3rd party components. However, unless you explicitly implement those interfaces on all classes you will also have to make sure the method names in those interfaces are unique else it will still be too easy to mix incompatible implementations. Always keep the consumer in mind while designing your interface!

I'm curious to know how you approached your cloning challenge. Leave me a comment!

 

References

My Latest Track