How to Avoid the Need to Implement ICloneable Interface and What to Do Instead

by Zoran Horvat

Questioning the Role of the ICloneable Interface

Many programmers ask themselves what is the purpose of the ICloneable interface. This interface comes with only a single method – Clone – and it is supposed to create a copy of the host object:

interface ICloneable
{
    object Clone();
}

This interface, however simple, comes with a couple of issues:

  • It is unclear whether the Clone method will create a deep or shallow copy
  • Return value is System.Object and it requires downcast to access concrete features of the resulting object

Each of these problems alone is sufficient to give up the ICloneable interface entirely.

The first issue, being unable to tell whether deep or shallow copy will be made, is of semantical kind. We strongly dislike interfaces which do not communicate intention clearly enough. Such interfaces require the programmer to read concrete implementation before deciding how to write the client-side code. Not only that it will take additional time to get acquainted with the cloneable class, but it also couples client implementation with implementation of the concrete cloneable class. Change made to either side without changing the other will potentially introduce a defect in code.

The second issue, return type, is more of a syntactical kind. We strongly dislike methods that return weak types such as System.Object or System.String. We want to see objects of concrete types, so that we can access their concrete members – methods or properties.

An Example That Will Expose the Problems with ICloneable

Take a look at this simple class:

class Person : ICloneable
{
    public string Name { get; }
    public string Surname { get; }

    public Person(string name, string surname)
    {
        this.Name = name;
        this.Surname = surname;
    }

    public Person(Person other):
        this(other.Name, other.Surname)
    {
    }

    public object Clone()
    {
        return new Person(this);
    }
}

This class is modeling a person, identified by first and last name. Person class implements ICloneable and it just returns a new Person object with same name and surname.

On a related note, Clone method is implemented via a copy constructor. It is a good practice to equip the class with a copy constructor in any situation in which making copies of objects is a requirement.

But now, the fundamental question: What can we do with an object of Person class that we couldn’t do without implementing the ICloneable interface. The only viable answer that comes to my mind is representing an object as an object implementing the ICloneable interface alone:

void DoSomething(ICloneable obj)
{
    object clone = obj.Clone();
    // Now do something with clone
}

In this case, we have a method which expects an object implementing ICloneable. It does not expect an object of class Person, because that would be too specific for this method. The only thing this method wants to do is to clone an object and then deal with the clone, like send it over the wire or persist it in the database.

But then, we immediately see the problem. Clone method has returned a plain object. DoSomething function has no idea what concrete type of this object is.

Bottom line is that having ICloneable interface didn’t bring us any benefits. At the level of the Person class we didn’t need it because we already have the copy constructor which is basically doing the same thing as the Clone method. (Only better, because the result of calling the copy constructor is a strongly typed Person object, while the result of calling the Clone method is System.Object.)

On the consuming side, we didn’t have much benefits either. We could narrow down the type of input argument of the DoSomething function, and that is really nice. DoSomething function doesn’t have to depend on the Person class anymore. But then, the object it will obtain through calling the Clone method is just a System.Object and it is quite useless in most of the cases.

Even if DoSomething function attempted to send the cloned object over the wire, the other big issue appears. Is it the deep or the shallow copy then? Is it safe to pass the object to any other object after cloning? I mean, it could happen that some references contained in the cloneable object were just copied to the cloned instance in a shallow copy manner.

That could easily cause instability. Maybe this particular scenario required deep copy of those references? We cannot tell. That is the serious issue, because ICloneable interface doesn’t give any concrete promises and then we cannot make concrete decisions down the stream.

Bottom line, ICloneable is almost of no use to both the feature provider and the feature consumer. We have to look for a more promising solution.

Replacing ICloneable with a Func Delegate

One reasonable replacement for the ICloneable interface is Func<T> delegate. This delegate type indicates a function returning a type T. Nothing more than that. Let’s see how the DoSomething method would look like when Func delegate is applied:

void DoSomething(Func<Person> deepCopy)
{
    Person clone = deepCopy();
    Send("name", clone.Name);
    Send("surname", clone.Surname);
}

This time, the client could enforce a concrete Person type. It was of great importance to the client, because it wanted to send name and surname over the wire. This time, the client can get hold of the concrete Person class instance and then access its concrete Name and Surname property getters.

On the calling side, we can easily wrap an object in the closure of a lambda:

Person person = new Person("Max", "Planck");
DoSomething(() => new Person(person));

As you can see, I was perfectly capable to produce a Func delegate that will create a deep copy of the Person object. Once again, I have relied on the copy constructor, which might not be the best solution in all cases. Again, we have that question – is it deep or shallow copy that the client is asking for.

Let’s look at the client then. The DoSomething method is expressing its needs through the argument name: deepCopy. Func delegate expected by the DoSomething method implies deep copy requirement. Not a hundred per cent satisfying solution, but better something than nothing. This solution is based on a convention.

Finally, there is this problem of deep vs. shallow copy in the Person class. Copy constructor is not the best solution when the client is the one who tells which kind of copy should be made. It is better to replace it with a static method with a name indicating the intention:

class Person
{
    public string Name { get; }
    public string Surname { get; }

    public Person(string name, string surname)
    {
        this.Name = name;
        this.Surname = surname;
    }

    public static Person DeepCopy(Person other)
    {
        return new Person(other.Name, other.Surname);
    }

}

This is all it takes to support copying convention both on the supplying and on the consuming end.

Summary

In this article we have tackled the question of usefulness of implementing the ICloneable interface. We have seen that this interface doesn’t bring much revenue back after we implement it.

Instead, it may be a better idea to replace it with the Func<T> delegate, where T is the concrete type the consumer needs after the copy is created. In this way, we can satisfy the client and then implement the supplying end appropriately, i.e. supplier will adapt to the desired interface of the consumer.

This solution is not the best possible, because there is still some uncertainty regarding whether deep or shallow copy has to be created. We have suggested that naming convention should be applied to clarify the situation.


If you wish to learn more, please watch my latest video courses

About

Zoran Horvat

Zoran Horvat is the Principal Consultant at Coding Helmet, speaker and author of 100+ articles, and independent trainer on .NET technology stack. He can often be found speaking at conferences and user groups, promoting object-oriented and functional development style and clean coding practices and techniques that improve longevity of complex business applications.

  1. Pluralsight
  2. Udemy
  3. Twitter
  4. YouTube
  5. LinkedIn
  6. GitHub