Proper way to implement ICloneable
Asked Answered
W

7

79

What is the proper way of implementing ICloneable in a class hierarchy? Say I have an abstract class DrawingObject. Another abstract class RectangularObject inherits from DrawingObject. Then there are multiple concrete classes like Shape, Text, Circle etc. that all inherit from RectangularObject. I want to implement ICloneable on DrawingObject and then carry it down the hierarchy, copying available properties at each level and calling parent's Clone at the next level.

The problem however is that since the first two classes are abstract, I cannot create their objects in the Clone() method. Thus I must duplicate the property-copying procedure in each concrete class. Or is there a better way?

Wershba answered 14/1, 2014 at 14:53 Comment(8)
Why are you implementing ICloneable? From the MSDN "Because callers of Clone cannot depend on the method performing a predictable cloning operation, we recommend that ICloneable not be implemented in public APIs." Do you have a good reason to implement the ICloneable interface?Artur
@ScottChamberlain: That may be true for public APIs, but I'm implementing it in my own code. I have direct access to all the classes involved. I must just trying to think of a proper OOP way.Wershba
@ElliotTereschuk: I thought of doing that, but it rather smells like a non-OOP (anti-OOP in fact) approach.Wershba
@Wershba Hovewer, its simplicity with deep hierarchical cloning looks like nice "dirty hack"Repudiation
Make EVERYTHING immutable, and then you don't need to clone, just pass the reference around. ;)Exact
@MatthewWatson: Clever. Don't know why MS didn't do that with the framework itself. :)Wershba
@Wershba they did in 4.5Artur
Passing a reference around is not always appropriate. In my case, I'm using Clone() to make copies of SqlConnections in a multithreaded application. Since SqlConnections are not threadsafe, and locking them interferes with them being managed properly, references aren't useful. It's always a matter of the almighty use-case.Clemmy
C
103

You can easily create a superficial clone with object's protected method MemberwiseClone.

Example:

   public abstract class AbstractCloneable : ICloneable
   {
      public object Clone()
      {
         return this.MemberwiseClone();
      }
   }

If you don't need anything like a deep copy, you will not have to do anything in the child classes.

The MemberwiseClone method creates a shallow copy by creating a new object, and then copying the nonstatic fields of the current object to the new object. If a field is a value type, a bit-by-bit copy of the field is performed. If a field is a reference type, the reference is copied but the referred object is not; therefore, the original object and its clone refer to the same object.

If you need more intelligence in the cloning logic, you can add a virtual method to handle references :

   public abstract class AbstractCloneable : ICloneable
   {
      public object Clone()
      {
         var clone = (AbstractCloneable) this.MemberwiseClone();
         HandleCloned(clone);
         return clone;
      }

      protected virtual void HandleCloned(AbstractCloneable clone)
      {
         //Nothing particular in the base class, but maybe useful for children.
         //Not abstract so children may not implement this if they don't need to.
      }
   }


   public class ConcreteCloneable : AbstractCloneable
   {
       protected override void HandleCloned(AbstractCloneable clone)
       {
           //Get whathever magic a base class could have implemented.
           base.HandleCloned(clone);

           //Clone is of the current type.
           ConcreteCloneable obj = (ConcreteCloneable) clone;

           //Here you have a superficial copy of "this". You can do whathever 
           //specific task you need to do.
           //e.g.:
           obj.SomeReferencedProperty = this.SomeReferencedProperty.Clone();
       }
   }
Coralyn answered 14/1, 2014 at 16:54 Comment(9)
Of course, you can always call base.HandleCloned to call the parent classes' logic. I think it's the solution that involve the less code writing. MemberwiseClone handles the magic of object creation.Coralyn
Outside of the squirreled coding of HandleCloned(...), this is probably the most concise example and answer to the OP stated context.Donor
This will cause a StackOverflowException with circular references, right?Kissiah
@Kissiah no, there's no circurlar references. Where do you see one ?Coralyn
@Coralyn I'm not claiming there are circular references, just asking if it will cause a StackOverflowException if it encounters circular references.Kissiah
I cant see a case where it would append. If you call this.HandleClone(this) it may do some funky things depending of the implementation, but the method is protected and there's no reason to do something like this.Coralyn
@Coralyn what is the return type of the HandleCloned method?Damages
@RamilAliyev It would be void since the method act on the parameter, it does not have to return a value. I'll correct it.Coralyn
@Coralyn You did edit your answer, before your method's is not void methodDamages
S
5

Give your base class a protected and overridable CreateClone() method that creates a new (empty) instance of the current class. Then have the Clone() method of the base class call that method to polymorphically instantiate a new instance, which the base class can then copy its field values to.

Derived non-abstract classes can override the CreateClone() method to instantiate the appropriate class, and all derived classes that introduce new fields can override Clone() to copy their additional field values into the new instance after invoking the inherited version of Clone().

public CloneableBase : ICloneable
{
    protected abstract CloneableBase CreateClone();

    public virtual object Clone()
    {
        CloneableBase clone = CreateClone();
        clone.MyFirstProperty = this.MyFirstProperty;
        return clone;
    }

    public int MyFirstProperty { get; set; }
}

public class CloneableChild : CloneableBase
{
    protected override CloneableBase CreateClone()
    {
        return new CloneableChild();
    }

    public override object Clone()
    {
        CloneableChild clone = (CloneableChild)base.Clone();
        clone.MySecondProperty = this.MySecondProperty;
        return clone;
    }

    public int MySecondProperty { get; set; }
}

If you want to skip the first overriding step (at least in the default case), you can also suppose a default constructor signature (e.g. parameterless) and try to instantiate a clone instance using that constructor signature with reflection. Like this, only classes whose constructors do not match the default signature will have to override CreateClone().

A very simple version of that default CreateClone() implementation could look like this:

protected virtual CloneableBase CreateClone()
{
    return (CloneableBase)Activator.CreateInstance(GetType());
}
Sadiras answered 14/1, 2014 at 15:5 Comment(3)
Wait a minute, the base class is abstract. How do I create an empty object of it in the CreateClone() method?Wershba
@dotNET: Either, the CreateClone() method is abstract in the base class and every class has to override it. Or, the CreateClone() method uses reflection to instantiate whatever is returned by GetType() and classes for which that will fail have to override CreateClone(), others can rely on the default implementation.Sadiras
@dotNET: I have added some example code to illustrate how it works.Sadiras
S
5

In order to create deep clone object with new references and avoid mutations on your objects in the most unexpected places, use Serialize/Deserialize.

It will allow full control of what can be cloned (using ignore attribute). Here some example with both System.Text.Json and Newtonsoft.

// System.Text.Json
public object Clone()
{
    // setup
    var json = JsonSerializer.Serialize(this);

    // get
    return JsonSerializer.Deserialize<MyType>(json);
}

// Newtonsoft
public object Clone()
{
    // setup
    var json = JsonConvert.SerializeObject(this);

    // get
    return JsonConvert.DeserializeObject<MyType>(json);
}

// Usage
MyType clonedMyType = myType.Clone();
Satiny answered 1/3, 2021 at 19:55 Comment(0)
A
4

I believe I have an improvement over @johnny5's excellent answer. Simply define copy constructors in all classes and in the base class use reflection in the Clone method to find the copy constructor and execute it. I think this is slightly cleaner as you don't need a stack of handle clone overrides and you don't need MemberwiseClone() which is simply too blunt an instrument in many situations.

public abstract class AbstractCloneable : ICloneable
    {
        public int BaseValue { get; set; }
        protected AbstractCloneable()
        {
            BaseValue = 1;
        }
        protected AbstractCloneable(AbstractCloneable d)
        {
            BaseValue = d.BaseValue;
        }

        public object Clone()
        {
            var clone = ObjectSupport.CloneFromCopyConstructor(this);
            if(clone == null)throw new ApplicationException("Hey Dude, you didn't define a copy constructor");
            return clone;
        }

    }


    public class ConcreteCloneable : AbstractCloneable
    {
        public int DerivedValue { get; set; }
        public ConcreteCloneable()
        {
            DerivedValue = 2;
        }

        public ConcreteCloneable(ConcreteCloneable d)
            : base(d)
        {
            DerivedValue = d.DerivedValue;
        }
    }

    public class ObjectSupport
    {
        public static object CloneFromCopyConstructor(System.Object d)
        {
            if (d != null)
            {
                Type t = d.GetType();
                foreach (ConstructorInfo ci in t.GetConstructors())
                {
                    ParameterInfo[] pi = ci.GetParameters();
                    if (pi.Length == 1 && pi[0].ParameterType == t)
                    {
                        return ci.Invoke(new object[] { d });
                    }
                }
            }

            return null;
        }
    }

Finally let me speak out in favor of ICloneable. If you use this interface you will get beat up by the style police because the .NET Framework Design Guidelines say not to implement it because, to quote the guidelines, "when using an object that implements a type with ICloneable, you never know what you are going to get. This makes the interface useless." The implication is that you don't know whether you are getting a deep or shallow copy. Well this is simply sophistry. Does this imply that copy constructors should never be used because "you never know what you are going to get?" Of course not. If you don't know what you are going to get, this is a simply a problem with the design of the class, not the interface.

Apogeotropism answered 8/7, 2018 at 15:31 Comment(0)
R
0

On my opinion, the clearest way is to apply binary serialization with BinaryFormatter in MemoryStream.

There is MSDN thread about deep cloning in C# where the approach above is suggested.

Repudiation answered 14/1, 2014 at 15:0 Comment(4)
While this is helpful in some cases, the downside is that you may have less control about what gets cloned. Maybe you do not want to clone the whole reachable object graph, but actually keep references to some (but not all) "central objects" intact. e.g. a drawing element should probably clone its "sub-items", but not its owner document. In practice, I have found neither strictly shallow nor strictly deep cloning to be desirable, usually it's something in between.Sadiras
@O.R.Mapper The main goal of such method is that its routine needs 5 strings of code actually. Custom cloning is performed rather with mentioning every individual field and (or) propertyRepudiation
Agree with @O.R.Mapper here.Wershba
Since 2020 this approach should not be followed as MS considers BinaryFormatter dangerous.Putrescible
S
0

At a minimum you let only concrete classes handle cloning, and abstract classes have protected copy constructors. Now on top of this you want to be able to take a variable of DrawingObject and clone it like this:

class Program
{
    static void Main(string[] args)
    {
        DrawingObject obj1=new Circle(Color.Black, 10);
        DrawingObject obj2=obj1.Clone();
    }
}

You might consider this cheating, but I would use extension methods and reflection:

public abstract class DrawingObject
{
    public abstract void Draw();
    public Color Color { get; set; }
    protected DrawingObject(DrawingObject other)
    {
        this.Color=other.Color;
    }
    protected DrawingObject(Color color) { this.Color=color; }
}

public abstract class RectangularObject : DrawingObject
{
    public int Width { get; set; }
    public int Height { get; set; }
    protected RectangularObject(RectangularObject other)
        : base(other)
    {
        Height=other.Height;
        Width=other.Width;
    }
    protected RectangularObject(Color color, int width, int height)
        : base(color)
    {
        this.Width=width;
        this.Height=height;
    }
}

public class Circle : RectangularObject, ICloneable
{
    public int Diameter { get; set; }
    public override void Draw()
    {
    }
    public Circle(Circle other)
        : base(other)
    {
        this.Diameter=other.Diameter;
    }
    public Circle(Color color, int diameter)
        : base(color, diameter, diameter)
    {
        Diameter=diameter;
    }

    #region ICloneable Members
    public Circle Clone() { return new Circle(this); }
    object ICloneable.Clone()
    {
        return Clone();
    }
    #endregion

}

public class Square : RectangularObject, ICloneable
{
    public int Side { get; set; }
    public override void Draw()
    {
    }
    public Square(Square other)
        : base(other)
    {
        this.Side=other.Side;
    }
    public Square(Color color, int side)
        : base(color, side, side)
    {
        this.Side=side;
    }

    #region ICloneable Members
    public Square Clone() { return new Square(this); }
    object ICloneable.Clone()
    {
        return Clone();
    }
    #endregion

}

public static class Factory
{
    public static T Clone<T>(this T other) where T : DrawingObject
    {
        Type t = other.GetType();
        ConstructorInfo ctor=t.GetConstructor(new Type[] { t });
        if (ctor!=null)
        {
            ctor.Invoke(new object[] { other });
        }
        return default(T);
    }
}

Edit 1

If you are concearned about speed (doing a reflection every time) you can a) Cache the constructor in a static dictionary.

public static class Factory
{
    public static T Clone<T>(this T other) where T : DrawingObject
    {
        return Dynamic<T>.CopyCtor(other);
    }
}

public static class Dynamic<T> where T : DrawingObject
{
    static Dictionary<Type, Func<T, T>> cache = new Dictionary<Type,Func<T,T>>();

    public static T CopyCtor(T other)
    {
        Type t=other.GetType();
        if (!cache.ContainsKey(t))
        {
            var ctor=t.GetConstructor(new Type[] { t });
            cache.Add(t, (x) => ctor.Invoke(new object[] { x }) as T);
        }
        return cache[t](other);
    }
}
Sulfamerazine answered 19/4, 2015 at 18:43 Comment(1)
At the Factory level, this implementation makes sense. But the OP context was about inheritance and Clone-ability. Albeit correct, if in the perspective of Factory's, but not in the perspective of inheritance.Donor
E
-1

Here's a copy-paste of some sample code I had lying around that I wrote years ago.

These days, I avoid having designs that require Clone support; I found most such designs to be somewhat flaky. Instead, I make extensive use of immutable classes to avoid the need for cloning in the first place.

Having said that, here's the sample cloning pattern:

using System;
using System.IO;
using System.Diagnostics;

/*

This code demonstrates a cloning pattern that you can use for class hierarchies.

The abstract base class specifies an abstract Clone() method which must be implemented by all derived classes.
Every class except the abstract base class must have a protected copy constructor. 

This protected copy constructor will:

(1) call the base class' copy constructor, and 
(2) set any new fields introduced in the derived class.

This code also demonstrates an implementation of Equals() and CopyFrom().

*/

namespace CloningPattern
{
    //—————————————————————————————————————————————————————————————————————————————————————————————————————————————————————

    static class Program
    {
        static void Main()
        {
            Derived2 test = new Derived2()
            {
                IntValue = 1,
                StringValue = "s",
                DoubleValue = 2,
                ShortValue = 3
            };

            Derived2 copy = Clone(test);
            Console.WriteLine(copy);
        }

        static Derived2 Clone(AbstractBase item)
        {
            AbstractBase abstractBase = (AbstractBase) item.Clone();
            Derived2 result = abstractBase as Derived2;
            Debug.Assert(result != null);
            return result;
        }
    }

    //—————————————————————————————————————————————————————————————————————————————————————————————————————————————————————

    public abstract class AbstractBase: ICloneable
    {
        // Sample data field.

        public int IntValue { get; set; }

        // Canonical way of providing a Clone() operation
        // (except that this is abstract rather than virtual, since this class
        // is itself abstract).

        public abstract object Clone();

        // Default constructor.

        protected AbstractBase(){}

        // Copy constructor.

        protected AbstractBase(AbstractBase other)
        {
            if (other == null)
            {
                throw new ArgumentNullException("other");
            }

            this.copyFrom(other);
        }

        // Copy from another instance over the top of an already existing instance.

        public virtual void CopyFrom(AbstractBase other)
        {
            if (other == null)
            {
                throw new ArgumentNullException("other");
            }

            this.copyFrom(other);
        }

        // Equality check.

        public override bool Equals(object obj)
        {
            if (obj == null)
            {
                return false;
            }

            if (object.ReferenceEquals(this, obj))
            {
                return true;
            }

            if (this.GetType() != obj.GetType())
            {
                return false;
            }

            AbstractBase other = (AbstractBase)obj;

            return (this.IntValue == other.IntValue);
        }

        // Get hash code.

        public override int GetHashCode()
        {
            return this.IntValue.GetHashCode();
        }

        // ToString() for debug purposes.

        public override string ToString()
        {
            return "IntValue = " + IntValue;
        }

        // Implement copying fields in a private non-virtual method, called from more than one place.

        private void copyFrom(AbstractBase other)  // 'other' cannot be null, so no check for nullness is made.
        {
            this.IntValue = other.IntValue;
        }
    }

    //—————————————————————————————————————————————————————————————————————————————————————————————————————————————————————

    public abstract class AbstractDerived: AbstractBase
    {
        // Sample data field.

        public short ShortValue{ get; set; }

        // Default constructor.

        protected AbstractDerived(){}

        // Copy constructor.

        protected AbstractDerived(AbstractDerived other): base(other)
        {
            this.copyFrom(other);
        }

        // Copy from another instance over the top of an already existing instance.

        public override void CopyFrom(AbstractBase other)
        {
            base.CopyFrom(other);
            this.copyFrom(other as AbstractDerived);
        }

        // Comparison.

        public override bool Equals(object obj)
        {
            if (object.ReferenceEquals(this, obj))
            {
                return true;
            }

            if (!base.Equals(obj))
            {
                return false;
            }

            AbstractDerived other = (AbstractDerived)obj;  // This must succeed because if the types are different, base.Equals() returns false.

            return (this.IntValue == other.IntValue);
        }

        // Get hash code.

        public override int GetHashCode()
        {
            // "Standard" way of combining hash codes from subfields.

            int hash = 17;

            hash = hash * 23 + base.GetHashCode();
            hash = hash * 23 + this.ShortValue.GetHashCode();

            return hash;
        }

        // ToString() for debug purposes.

        public override string ToString()
        {
            return base.ToString() + ", ShortValue = " + ShortValue;
        }

        // This abstract class doesn't need to implement Clone() because no instances of it
        // can ever be created, on account of it being abstract and all that.
        // If you COULD, it would look like this (but you can't so this won't compile):

        // public override object Clone()
        // {
        //     return new AbstractDerived(this);
        // }

        // Implement copying fields in a private non-virtual method, called from more than one place.

        private void copyFrom(AbstractDerived other)  // Other could be null, so check for nullness.
        {
            if (other != null)
            {
                this.ShortValue = other.ShortValue;
            }
        }
    }

    //—————————————————————————————————————————————————————————————————————————————————————————————————————————————————————

    public class Derived1: AbstractDerived
    {
        // Must declare a default constructor.

        public Derived1(){}

        // Sample data field.

        public string StringValue{ get; set; }

        // Implement Clone() by simply using this class' copy constructor.

        public override object Clone()
        {
            return new Derived1(this);
        }

        // Copy from another instance over the top of an already existing instance.

        public override void CopyFrom(AbstractBase other)
        {
            base.CopyFrom(other);
            this.copyFrom(other as Derived1);
        }

        // Equality check.

        public override bool Equals(object obj)
        {
            if (object.ReferenceEquals(this, obj))
            {
                return true;
            }

            if (!base.Equals(obj))
            {
                return false;
            }

            Derived1 other = (Derived1)obj;  // This must succeed because if the types are different, base.Equals() returns false.

            return (this.StringValue == other.StringValue);
        }

        // Get hash code.

        public override int GetHashCode()
        {
            // "Standard" way of combining hash codes from subfields.

            int hash = 17;

            hash = hash * 23 + base.GetHashCode();
            hash = hash * 23 + this.StringValue.GetHashCode();

            return hash;
        }

        // ToString() for debug purposes.

        public override string ToString()
        {
            return base.ToString() + ", StringValue = " + StringValue;
        }

        // Protected copy constructor. Used to implement Clone().
        // Also called by a derived class' copy constructor.

        protected Derived1(Derived1 other): base(other)
        {
            this.copyFrom(other);
        }

        // Implement copying fields in a private non-virtual method, called from more than one place.

        private void copyFrom(Derived1 other)  // Other could be null, so check for nullness.
        {
            if (other != null)
            {
                this.StringValue = other.StringValue;
            }
        }
    }

    //—————————————————————————————————————————————————————————————————————————————————————————————————————————————————————

    public class Derived2: Derived1
    {
        // Must declare a default constructor.

        public Derived2(){}

        // Sample data field.

        public double DoubleValue{ get; set; }

        // Implement Clone() by simply using this class' copy constructor.

        public override object Clone()
        {
            return new Derived2(this);
        }

        // Copy from another instance over the top of an already existing instance.

        public override void CopyFrom(AbstractBase other)
        {
            base.CopyFrom(other);
            this.copyFrom(other as Derived2);
        }

        // Equality check.

        public override bool Equals(object obj)
        {
            if (object.ReferenceEquals(this, obj))
            {
                return true;
            }

            if (!base.Equals(obj))
            {
                return false;
            }

            Derived2 other = (Derived2)obj;  // This must succeed because if the types are different, base.Equals() returns false.

            return (this.DoubleValue == other.DoubleValue);
        }

        // Get hash code.

        public override int GetHashCode()
        {
            // "Standard" way of combining hash codes from subfields.

            int hash = 17;

            hash = hash * 23 + base.GetHashCode();
            hash = hash * 23 + this.DoubleValue.GetHashCode();

            return hash;
        }

        // ToString() for debug purposes.

        public override string ToString()
        {
            return base.ToString() + ", DoubleValue = " + DoubleValue;
        }

        // Protected copy constructor. Used to implement Clone().
        // Also called by a derived class' copy constructor.

        protected Derived2(Derived2 other): base(other)
        {
            // Canonical implementation: use ":base(other)" to copy all
            // the base fields (which recursively applies all the way to the ultimate base)
            // and then explicitly copy any of this class' fields here:

            this.copyFrom(other);
        }

        // Implement copying fields in a private non-virtual method, called from more than one place.

        private void copyFrom(Derived2 other)  // Other could be null, so check for nullness.
        {
            if (other != null)
            {
                this.DoubleValue = other.DoubleValue;
            }
        }
    }
}
Exact answered 14/1, 2014 at 15:13 Comment(3)
This example implementation never implements the Clone method...You make the AbstractBase inherit ICloneable, define the Clone method as abstract but then never actually implement it in the Child classes. Also, all the excess stuff is "Mudding" up the example with things like Equals, GetHashCode, and ToString. Please keep your examples to the OP contextual questionDonor
@GoldBishop: This example implementation never implements the Clone method Yes it does. Did you try compiling it? Try it - you'll see that it compiles and runs. What do you think the public override object Clone() does in the derived classes? Also, I think it's important to consider object equality when implementing class hierarchies.Exact
Well with all the non-relevant code mixed in with the extremely long code snippet, it is hard to see until you spend 5 minutes navigating through the inheritance tree to find the Clone implementation. I would suggest you keep the code snippets short-sweet-and-to-the-point. DotNet Fiddle: dotnetfiddle.net/FftNr5Donor

© 2022 - 2024 — McMap. All rights reserved.