Freigeben über


Why Not ICloneable?

The ICloneable interface has been around since the .NET 1.0 days. A number of people have criticized this interface because there is no intrinsic ability to indicate whether the clone is a shallow or deep copy. While that debate is an entirely different discussion in and of itself, I tend to believe that it's the implementer's responsibility to decide the depth of the cloning process. A simple XML comment on the method implementation can indicate whether the cloning process is deep or shallow.

Once generics where introduced in .NET 2.0, I often wondered why there was never a generic implementation for ICloneable<T>. At first glance, it seems impossible because methods cannot vary by return type alone. However, if we use a constrained type argument on the Clone method that restriction can be overcome.

public interface ICloneable<T>

{

TClone Clone<TClone>() where TClone : T;

}

This contract allows a generic Clone implementation where the cloned return type is compatible with the original type. This provides the benefit of a strongly-typed return type. We can also now create a generic, cloneable base class.

public class Cloneable<T> : ICloneable<T>, ICloneable

{

public virtual TClone Clone<TClone>() where TClone : T

{

return (TClone) this.MemberwiseClone();

}

object ICloneable.Clone()

{

var type = this.GetType();

var method = type.GetMethod( "Clone" ).MakeGenericMethod( type );

return method.Invoke( this, null );

}

}

This class provides a simple (and naïve) cloning strategy, but can be overridden and still retain the strongly-typed benefits. In addition, we can write a single implementation of ICloneable that uses Reflection to always invoke the current generic implementation. When we put it all together, we get the following:

public class Person : Cloneable<Person> {}
public class User : Person {}

var user = new User();
var clonedUser = user.Clone<User>();
var clonedPerson = user.Clone<Person>();

Unfortunately, there is one scenario where this strategy fails; a clone of a super type cannot be created for a subtype. This limitation is probably why such an interface was never added to the BCL. For example, the following will compile, but fails at runtime:

var person = new Person();
var clone = person.Clone<User>(); // fails because Person cannot cast down to User

In my opinion, this limitation is acceptable for two reasons. Although not guaranteed, it's unlikely that developers would write code in this manner. If you've written object-oriented code for any length of time, common sense tells you that a super type cannot be cast down to a subtype. Second, the runtime failure has same effect as writing code with an invalid cast. Consider the following:

var person = new Person();

var user = (User) person;

This code also compiles, but will fail at runtime. Both of these scenarios have logic defects that cannot be guarded against by the compiler. The benefits of a strongly-typed Clone implementation outweigh this single shortcoming in my opinion.

While this implementation certainly doesn't address the need for every cloning scenario, it does provide a single non-generic implementation, virtualized generic implementation, and the benefits of strong-typing.

GenericCloneable.zip