What is the proper way of implementing ICloneable
in a class hierarchy? Say I have an abstract class DrawingObject
. Another abstract class RectangularObject
inherits from DrawingObject
. Then there are multiple concrete classes like Shape
, Text
, Circle
etc. that all inherit from RectangularObject
. I want to implement ICloneable
on DrawingObject
and then carry it down the hierarchy, copying available properties at each level and calling parent's Clone
at the next level.
The problem however is that since the first two classes are abstract, I cannot create their objects in the Clone()
method. Thus I must duplicate the property-copying procedure in each concrete class. Or is there a better way?
You can easily create a superficial clone with
object
's protected method MemberwiseClone.Example:
If you don't need anything like a deep copy, you will not have to do anything in the child classes.
If you need more intelligence in the cloning logic, you can add a virtual method to handle references :
On my opinion, the clearest way is to apply binary serialization with
BinaryFormatter
inMemoryStream
.There is MSDN thread about deep cloning in C# where the approach above is suggested.
At a minimum you let only concrete classes handle cloning, and abstract classes have
protected
copy constructors. Now on top of this you want to be able to take a variable ofDrawingObject
and clone it like this:You might consider this cheating, but I would use extension methods and reflection:
Edit 1
If you are concearned about speed (doing a reflection every time) you can a) Cache the constructor in a static dictionary.
Give your base class a protected and overridable
CreateClone()
method that creates a new (empty) instance of the current class. Then have theClone()
method of the base class call that method to polymorphically instantiate a new instance, which the base class can then copy its field values to.Derived non-abstract classes can override the
CreateClone()
method to instantiate the appropriate class, and all derived classes that introduce new fields can overrideClone()
to copy their additional field values into the new instance after invoking the inherited version ofClone()
.If you want to skip the first overriding step (at least in the default case), you can also suppose a default constructor signature (e.g. parameterless) and try to instantiate a clone instance using that constructor signature with reflection. Like this, only classes whose constructors do not match the default signature will have to override
CreateClone()
.A very simple version of that default
CreateClone()
implementation could look like this:I believe I have an improvement over @johnny5's excellent answer. Simply define copy constructors in all classes and in the base class use reflection in the Clone method to find the copy constructor and execute it. I think this is slightly cleaner as you don't need a stack of handle clone overrides and you don't need MemberwiseClone() which is simply too blunt an instrument in many situations.
Finally let me speak out in favor of ICloneable. If you use this interface you will get beat up by the style police because the .NET Framework Design Guidelines say not to implement it because, to quote the guidelines, "when using an object that implements a type with ICloneable, you never know what you are going to get. This makes the interface useless." The implication is that you don't know whether you are getting a deep or shallow copy. Well this is simply sophistry. Does this imply that copy constructors should never be used because "you never know what you are going to get?" Of course not. If you don't know what you are going to get, this is a simply a problem with the design of the class, not the interface.
Here's a copy-paste of some sample code I had lying around that I wrote years ago.
These days, I avoid having designs that require Clone support; I found most such designs to be somewhat flaky. Instead, I make extensive use of immutable classes to avoid the need for cloning in the first place.
Having said that, here's the sample cloning pattern: