Cloning

What I learned about cloning is that you’re on your own.

 Discussions on the web don’t agree about the right way to implement cloning.  You can implement the ICloneable interface, a copy constructor, or your own cloing interface, but:
— You have to write your own implementation
— There is no standard of whether the Clone method is shallow or deep (deep also clones objects referenced by the object to clone; shallow copies the reference)

When you implement your cloning operation, you can do it simple or fancy.
— By simple, I mean something like just copying over the member variables, as in:
clone.name = this.name
This has the advantage of being very simple and straightforward.  It has the disadvantage that if you add a new member variable, you have to remember to add it to the clone method also.
— The fancy version is typically making your class Serializable and serializing the original object into the clone.  Then you get into endless discussions on the right way to serialize things, etc., etc.  Or use Reflection.

My conclusion:
I’m going with the simple version.  For the in-house piece of software I am writing, this is sufficient, cleaner, quicker.  If it was production code, then perhaps the solution would be to still implement the simple version and to create a unit test that uses reflection to examine the object and its clone and to make sure they are the same.

My other conclusion:
The C# object should have a default Clone method that performs a shallow clone.  If you want something other than a shallow clone, you can override it and write your own,  or probably a better idea is to choose to implement some kind of standard IDeepCloneable interface.  I think that Ruby has a default shallow Clone method.

Later
Ah, ha!  Stephen C. Perry in his book Core C# and .Net tells me that DotNet has a shallow Clone method:  object.MemberwiseClone().  It’s a little dangerous, I discovered – as a shallow clone, it clones all the variables but copies all the references, which you then have to rewrite to do deep clones of all the references.  So in my “simple” version where I cloned all the member variables and references item by item, if I forgot to clone a reference then it would be null, whereas with the MemberwiseClone, it would have a copy of the original object’s data.  I suppose it is wrong either way, so it doesn’t matter.  I’m still going to be depending on the unit test (which – ahem! – I didn’t write) to verify the correctness of the Clone method.

Suprisingly, when I was searching on “C#” and “Clone”, none of the articles I looked at mentioned MemberwiseClone.

Now that I searched on “C#” and “MemberwiseClone”, I came across an interesting article by Keith Nicholas.  He points out that when shallow copying the reference types, events get copied also, so that objects that had subscribed to the original object’s events will start getting event notifications from the cloned object also.  Won’t they be surprised!  So this version of “wrong” can cause a lot more damage than the other version of “wrong”.

Venkat Subramaniam has an article that didn’t provide a clearcut solution (hint:  there isn’t one), but it did have a great example of the problem:  Bob clones Sam, but they both end up sharing a brain! 

Many articles say you should use Reflection to implement an automated clone, but:
1.  That’s overkill for simple projects
2.  Now you are automatically copying everything (deep clone), but is that always appropriate?  Venkat’s point is that there is not enough information in the object itself to know if you should be copying or referencing.  (Bob and Sam have different brains, but they live in the same city, not two different cities with the same name.)

The other point that I forgot to make is that I like implementing the ICloneable interface over the copy constructor because the ICloneable interface makes it obvious that you are implementing a clone.  If I later add a reference item to my class, then hopefully I will see the Clone() method and realize, oh! I need to implement a clone of this reference item.  It is less obvious with the copy constructor.

My new conclusion is that I am going back to  my original conclusion, which is for simple projects to implement ICloneable and specifically copy/reference data as desired, not using MemberwiseClone.  For a more formal project, WRITE  A GOOD UNIT TEST that will catch errors if data items are added or deleted without updating Clone().

In a perfect world, the compiler would require that when ICloneable is implemented, the Clone() method must have a specific copy implementation for every variable or reference.  If one is missing, then the compiler generates an error.

 Here is some test code I was playing around with:

using System;
using System.Collections.Generic;
using System.Text;

namespace TestConsole
{
class Program
{
static void Main(string[] args)
{
ClassA aTiger = new ClassA(“Tiger”, “Jungle”);
ClassA aPenguin = (ClassA)aTiger.Clone();
aPenguin.Name = “Penguin”;
aPenguin.Location = “Iceberg”;

Console.WriteLine(“Clone a simple object”);
Console.WriteLine(“Tiger: {0} {1}”, aTiger.Name, aTiger.Location);
Console.WriteLine(“Penguin: {0} {1}”, aPenguin.Name, aPenguin.Location);
Console.WriteLine(“”);

ClassB bTiger = new ClassB(“Tiger”, “Jungle”, 5);
ClassB bPenguin = (ClassB)bTiger.Clone();
bPenguin.Animal.Name = “Penguin”;
bPenguin.Animal.Location = “Iceberg”;
bPenguin.Age = 1;

Console.WriteLine(“Clone an object that references another object”);
Console.WriteLine(“Tiger: {0} {1} {2}”, bTiger.Animal.Name, bTiger.Animal.Location, bTiger.Age);
Console.WriteLine(“Penguin: {0} {1} {2}”, bPenguin.Animal.Name, bPenguin.Animal.Location, bPenguin.Age);
Console.WriteLine(“”);

ClassC cPhiladelphiaZoo = new ClassC();
cPhiladelphiaZoo.AddAnimal(“Tiger”, “Jungle”, 5);
cPhiladelphiaZoo.AddAnimal(“Penguin”, “Iceberg”, 1);
ClassC cElmwoodParkZoo = (ClassC)cPhiladelphiaZoo.Clone();
cElmwoodParkZoo.Animals[0].Animal.Name = “Praire Dog”;
cElmwoodParkZoo.Animals[0].Animal.Location = “Underground”;
cElmwoodParkZoo.Animals[0].Age = 2;

Console.WriteLine(“Clone an object that contains a list of objects”);
Console.WriteLine(“Philadelphia Zoo: {0} {1} {2}”, cPhiladelphiaZoo.Animals[0].Animal.Name, cPhiladelphiaZoo.Animals[0].Animal.Location, cPhiladelphiaZoo.Animals[0].Age);
Console.WriteLine(“Elmwood Park Zoo: {0} {1} {2}”, cElmwoodParkZoo.Animals[0].Animal.Name, cElmwoodParkZoo.Animals[0].Animal.Location, cElmwoodParkZoo.Animals[0].Age);
Console.WriteLine(“”);

Console.ReadKey();
}
}

class ClassA : System.ICloneable
{
public string Name;
public string Location;

public ClassA(string username, string userlocation)
{
Name = username;
Location = userlocation;
}

public object Clone()
{
ClassA clone = new ClassA(Name, Location);
return clone;
}
}

class ClassB : System.ICloneable
{
public ClassA Animal;
public int Age;

public ClassB()
{
}

public ClassB(string name, string location, int age)
{
Animal = new ClassA(name, location);
Age = age;
}

public object Clone()
{
// Use the Clone method of ClassA, rather than using the ClassB constructor to specify parameters
ClassB clone = new ClassB();
clone.Animal = (ClassA)this.Animal.Clone();
clone.Age = this.Age;
return clone;
}
}

class ClassC : System.ICloneable
{
public List Animals = new List();

public ClassC()
{
}

public void AddAnimal(string name, string location, int age)
{
ClassB animal = new ClassB(name,location,age);
Animals.Add(animal);
}

public object Clone()
{
ClassC clone = new ClassC();
foreach (ClassB animal in this.Animals)
{
clone.Animals.Add((ClassB)animal.Clone());
}
return clone;
}
}
}

Advertisements

Post a Comment

Required fields are marked *
*
*

%d bloggers like this: