When working on some sort of data-driven project, I frequently have the need to allow deep-copying of data objects. There are several patterns that accomplish this, but I've settled on one in particular.
Most .NET developers are probably familiar with the ICloneable
interface. While this is a good starting point, it is not what I choose
to rely on, for two reasons. First, the return type of the Clone method
is object
, so a cast is required. Second, the interface doesn't really
give you any special functionality. Nonetheless, implementing interfaces
is usually a good thing, so my approach does use the interface, if only
as a tag.
(I am leaving out equality test implementations for the sake of brevity, but one usually wants to implement equality testing when an object can be cloned.)
The common pattern I see when implementing a Clone method is to declare one public virtual method on the base class:
abstract class Animal
{
public IList<Animal> Children { get; set; }
public virtual Animal Clone()
{
var copy = (Animal)MemberwiseClone();
// Deep-copy children
copy.Children = Children.Select(c => c.Clone()).ToList();
return copy;
}
}
Simple enough. Let's say that we have a derived class that needs some additional logic. We just override the Clone method, right?
class Dog : Animal
{
public Collar Collar { get; set; }
public virtual Animal Clone()
{
var copy = (Dog)base.Clone();
copy.Collar = Collar.Clone();
return copy;
}
}
This works, but as soon as you try to clone a Dog
directly, the
ugliness of this pattern is apparent.
Animal animal = otherAnimal.Clone(); // Great!
Dog dog = otherDog.Clone(); // Compile-time error
Unfortunately, the return type of Animal.Clone()
is Animal
and
subclasses may not change the return type, not even to narrow it. So to
clone a Dog
into a variable of type Dog
means we have to cast:
Dog dog = (Dog)otherDog.Clone();
Yuck. This is passable, but it's hardly optimal.
The good news is that with just one tweak, we can make this pleasant to deal with. First, the Clone method needs to be made protected and renamed. Second, we create a new public Clone method that is not virtual and calls the protected virtual method. Subclasses hide this method with a new implementation that does the same thing, but casts the result.
Here's the full implementation:
abstract class Animal : ICloneable
{
public IList<Animal> Children { get; set; }
public Animal Clone()
{
return CloneImpl();
}
object ICloneable.Clone()
{
return CloneImpl();
}
protected virtual Animal CloneImpl()
{
var copy = (Animal)MemberwiseClone();
// Deep-copy children
copy.Children = Children.Select(c => c.Clone()).ToList();
return copy;
}
}
class Dog : Animal
{
public Collar Collar { get; set; }
new public Dog Clone()
{
return (Dog)CloneImpl();
}
protected virtual Animal CloneImpl()
{
var copy = (Dog)base.CloneImpl();
copy.Collar = Collar.Clone();
return copy;
}
}
Now, we have nice class-specific methods that will return a properly-typed reference to the new copy.
Animal animal = otherAnimal.Clone(); // Works
Dog dog = otherDog.Clone(); // Also works!
It's worth noting that both patterns will properly copy objects of more specific types than the reference you use to copy them. For example, given this variable:
Animal animal = new Dog();
animal.Clone()
will return a Dog
instance, typed as Animal
. This
is what we'd expect.
Comments