65

ICloneableクラス階層で実装する適切な方法は何ですか? 抽象クラスがあるとしDrawingObjectます。から別の抽象クラスRectangularObjectが継承されDrawingObjectます。Shape次に、Text、などの複数の具象クラスがあり、Circleそれらはすべて から継承されRectangularObjectます。実装ICloneableしてDrawingObjectから階層を下に運び、各レベルで使用可能なプロパティをコピーCloneし、次のレベルで親のプロパティを呼び出します。

ただし問題は、最初の 2 つのクラスが抽象クラスであるため、メソッドでそれらのオブジェクトを作成できないことClone()です。したがって、各具象クラスでプロパティのコピー手順を複製する必要があります。それとももっと良い方法がありますか?

4

7 に答える 7

84

objectの protected メソッド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 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();
       }
   }
于 2014-01-14T16:54:20.157 に答える
5

基本クラス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());
}
于 2014-01-14T15:05:16.090 に答える
3

新しい参照でディープ クローン オブジェクトを作成し、最も予期しない場所でのオブジェクトの変更を回避するには、Serialize/Deserialize を使用します。

複製できるものを完全に制御できます (ignore 属性を使用)。System.Text.Json と 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();
于 2021-03-01T19:55:26.120 に答える
1

BinaryFormatter私の意見では、最も明確な方法はin でバイナリ シリアライゼーションを適用することMemoryStreamです。

上記のアプローチが提案されているC# でのディープ クローン作成に関する MSDN スレッドがあります。

于 2014-01-14T15:00:14.177 に答える
-1

これは、私が何年も前に書いたサンプル コードのコピー アンド ペーストです。

最近では、クローンのサポートを必要とするデザインを避けるようにしています。そのようなデザインのほとんどは、やや不安定であることがわかりました。代わりに、不変クラスを多用して、そもそもクローン作成の必要性を回避しています。

そうは言っても、サンプルのクローン作成パターンは次のとおりです。

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;
            }
        }
    }
}
于 2014-01-14T15:13:29.313 に答える