Implement ICloneable as a Senior

iamprovidence
5 min readApr 7, 2023

--

In software development, it’s often necessary to create new objects that are similar to existing ones, with only a few differences between those. Rather than creating these objects from scratch, a more efficient approach is to clone an existing one and make the necessary modifications to the copy.

Surely, you’ve already figured out I am talking about the Prototype pattern here. In this article, you will see how to implement it with a help of ICloneable interface. What the caveats are hidden in default implementation and how it can be improved.

Prepare yourself, we are starting.

I assume, you are a developer, so not to waste your time, I will start from code right away. Here is a small hierarchy of classes that need to be copied. Don’t really focus on it, we will break it and see each case one by one. It is just here, so you have a full picture of what we are doing:

public class IdInfo
{
public int IdNumber { get; set; }
}

public class Address : ICloneable
{
public string Street { get; set; }

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

public class Person : ICloneable
{
public IdInfo IdInfo { get; set; }
public int Age { get; set; }
public string Name { get; set; }
public List<Address> Addresses { get; set; } = new ();

public object Clone()
{
// clone the Person object using MemberwiseClone
Person clonedPerson = (Person)this.MemberwiseClone();

// clone reference types
clonedPerson.IdInfo = new IdInfo
{
IdNumber = IdInfo.IdNumber
};

// clone collection of reference types
clonedPerson.Addresses = new List<Address>();
foreach (Address address in this.Addresses)
{
Address clonedAddress = (Address)address.Clone();

clonedPerson.Addresses.Add(clonedAddress);
}

return clonedPerson;
}
}

Clone the object

The guys from Microsoft were really generous to us. They provided us with a quick way to implement Prototype pattern.

Firstly, you need to inherit ICloneable interface with only one Clone() method. Inside this Clone() method use MemberwiseClone(). This is a special method, that only is accessible inside your class. It will create a new object of our Person class and copy all the properties it has. It perfectly handles value types such as int, double, enum and so on. So it just a helpful way to get rid of manually copying each property.

public class Person : ICloneable
{
public int Age { get; set; }
public string Name { get; set; }

. . .

public object Clone()
{
Person clonedPerson = (Person)this.MemberwiseClone();

. . .
}
}

Clone reference types

MemberwiseClone() is great, but it does not always work. When you need to copy something more complicated than number or text, like classes, it just doesn’t work. It will copy the reference but not the value itself, meaning Person object will use the same IdInfo as the original one. Changes in one object will cause changes in another one. This just unacceptable.

var john = new Person() 
{
IdInfo = new IdInfo
{
IdNumber = 1,
},
Name = "John",
};


var jane = john.Clone() as Person;
jane.Name = "Jane";
jane.IdInfo.IdNumber = 2;

// suddenly John has wrong Id
john.IdInfo.IdNumber; // 2

To handle this case, we have to create a new instance of IdInfo by ourselves, for example initializing each property.

public class Person : ICloneable
{
. . .

public object Clone()
{
. . .
clonedPerson.IdInfo = new IdInfo
{
IdNumber = IdInfo.IdNumber
};
. . .
}
}

It works, but has some inconveniences. For example, every time we add a new property to IdInfo class, we have to update each place where it is constructed.

Clone reference types with ICloneable

A better way to handle cloning of classes, would be to implement ICloneable, like it is done with Address. This way, when we introduce a new property, we don’t need to update each constructor, just a single Clone() method.

Talking, about Address. It is stored in a collection, let’s see how it is handled:

public class Person : ICloneable
{
. . .

public object Clone()
{
. . .
clonedPerson.Addresses = new List<Address>();
foreach (Address address in this.Addresses)
{
Address clonedAddress = (Address)address.Clone();

clonedPerson.Addresses.Add(clonedAddress);
}
. . .
}
}

😰 Yeah, with collections it got even worse, since you need to copy all the objects in the loop.

Harder, better, faster, stronger

Look how complicated it become in our simple test example, in real world it would be even worse. You need to implement ICloneable for all classes, you have to remember to copy all the properties, you process value and reference types differently and so on…

What If I tell you it can be done in a few lines, covering all the cases. Check this out:

public class Person : ICloneable
{
. . .

public object Clone()
{
var serializedObject = JsonConvert.SerializeObject(this);

return JsonConvert.DeserializeObject<Person>(serializedObject);
}
}

That's it 😃 Just simply serialize an object and deserialize it back. As a result, you will have a new object which is an exact copy of the original one. This implementation is not only robust, but also bug resistant.

We can go even further. With a little help of generics:

static class Prototype
{
public static T DeepClone<T>(this T self)
{
var serializedObject = JsonConvert.SerializeObject(self);

return JsonConvert.DeserializeObject<T>(serializedObject);
}
}

Now, every object implements prototype pattern 😃 However, I wonder why you would need that 🤔

Conclusion

In this article, you have seen a simple and elegant approach to implement the Prototype pattern in C#. It relies on serialization, which is definitely a more concise way to achieve the desired outcome.

However, it has its own flaws. With great opportunities, comes great performance drawback. It uses more memory and takes relatively longer time. But hey, we are not in eighties, who cares about performance these days 😅

Let me know in the comments what you think about it. Is there any fancy implementation you came across? Maybe other languages implement it differently, so we can steal their ideas? Let’s discuss 💬

Share it 🔄 Clap it 👏 Support ☕️ And follow ✅ You know the order 😉

--

--

iamprovidence

👨🏼‍💻 Full Stack Dev writing about software architecture, patterns and other programming stuff https://www.buymeacoffee.com/iamprovidence