C#-实现ICloneable的正确方法

在类层次结构中实现Clone()的正确方法是什么? 假设我有一个抽象类DrawingObject。另一个抽象类RectangularObjectDrawingObject继承。然后有多个具体类,例如ShapeTextCircle等。所有具体类都从RectangularObject继承。我想在27578692750下实现275786927506194545,然后在27578692750上实现 ,在每个级别复制可用属性,并在下一个级别调用父级的Clone

但是,问题在于,由于前两个类是抽象的,因此无法在Clone()方法中创建它们的对象。 因此,我必须在每个具体类中复制属性复制过程。 或者,还有更好的方法?

dotNET asked 2020-02-21T21:12:09Z
6个解决方案
60 votes

您可以使用object的受保护方法MemberwiseClone轻松创建表面克隆。

例:

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

如果您不需要深层副本之类的任何内容,则无需在子类中执行任何操作。

MemberwiseClone方法通过创建新对象,然后将当前对象的非静态字段复制到新对象来创建浅表副本。 如果字段是值类型,则将执行该字段的逐位复制。 如果字段是引用类型,则引用被复制,但引用的对象不被复制; 因此,原始对象及其克隆引用相同的对象。

如果您需要克隆逻辑方面的更多知识,则可以添加一个虚拟方法来处理引用:

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

      protected virtual 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 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();
       }
   }
Johnny5 answered 2020-02-21T21:12:35Z
3 votes

为基类提供一个受保护且可覆盖的CreateClone()方法,该方法将创建当前类的新(空)实例。 然后让基类的Clone()方法调用该方法,以多态方式实例化一个新实例,该基类随后可以将其字段值复制到该实例。

派生的非抽象类可以覆盖CreateClone()方法以实例化适当的类,并且在引入继承的版本Clone()之后,引入新字段的所有派生类可以覆盖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; }
}

如果要跳过第一个覆盖步骤(至少在默认情况下是这样),则还可以假设使用默认构造函数签名(例如,无参数),并尝试使用带有反射的该构造函数签名实例化克隆实例。 这样,只有其构造函数与默认签名不匹配的类才必须覆盖CreateClone()

该默认CreateClone()实现的非常简单的版本如下所示:

protected virtual CloneableBase CreateClone()
{
    return (CloneableBase)Activator.CreateInstance(GetType());
}
O. R. Mapper answered 2020-02-21T21:13:10Z
3 votes

我相信我对@ johnny5的出色答案有所改进。 只需在所有类中定义副本构造函数,然后在基类中使用Clone方法中的反射来查找副本构造函数并执行它。 我认为这稍微干净一点,因为您不需要堆栈的句柄克隆覆盖,也不需要MemberwiseClone(),在很多情况下,MemberwiseClone()实在是太钝了。

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;
        }
    }

最后,让我说出赞成ICloneable的想法。 如果使用此接口,您将被样式策略所打败,因为.NET Framework设计指南说不实施它,因为引用该指南,“当使用实现带有ICloneable类型的对象时,您永远都不知道自己要做什么。 将会得到。这使该界面无用。” 这意味着您不知道要获取深副本还是浅副本。 嗯,这简直就是诡辩。 这是否意味着不应使用复制构造函数,因为“您永远不知道要得到什么?” 当然不是。 如果您不知道要得到什么,这只是类设计的问题,而不是接口的问题。

AQuirky answered 2020-02-21T21:13:37Z
1 votes

我认为,最清晰的方法是在MemoryStream中对BinaryFormatter应用二进制序列化。

建议在上面的方法中找到有关C#中的深度克隆的MSDN线程。

Ilya Tereschuk answered 2020-02-21T21:14:01Z
0 votes

至少您只让具体类处理克隆,抽象类具有protected复制构造函数。 现在,最重要的是,您希望能够获取一个DrawingObject变量并像这样克隆它:

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

您可能会考虑这种作弊行为,但是我会使用扩展方法和反射:

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);
    }
}

编辑1

如果您担心速度(每次都进行反射),则可以:a)将构造函数缓存在静态字典中。

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);
    }
}
ja72 answered 2020-02-21T21:14:35Z
-1 votes

这是我几年前写的一些示例代码的复制粘贴。

如今,我避免使用需要克隆支持的设计。 我发现大多数这样的设计都有些古怪。 取而代之的是,我大量使用了不可变的类,以避免一开始就需要克隆。

话虽如此,这是示例克隆模式:

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;
            }
        }
    }
}
Matthew Watson answered 2020-02-21T21:15:04Z
translate from https://stackoverflow.com:/questions/21116554/proper-way-to-implement-icloneable