クラス階層にICloneable
を実装する適切な方法は何ですか?抽象クラスDrawingObject
があるとします。別の抽象クラスRectangularObject
はDrawingObject
を継承します。次に、Shape
、Text
、Circle
などのような複数の具象クラスがあり、それらはすべてRectangularObject
を継承します。 ICloneable
にDrawingObject
を実装し、それを階層に運び、各レベルで使用可能なプロパティをコピーし、次のレベルで親のClone
を呼び出します。
ただし問題は、最初の2つのクラスが抽象クラスであるため、Clone()
メソッドでオブジェクトを作成できないことです。したがって、各具体クラスでプロパティコピー手順を複製する必要があります。または、より良い方法がありますか?
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 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();
}
}
基本クラスに、現在のクラスの新しい(空の)インスタンスを作成する、保護されたオーバーライド可能な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());
}
_
@ johnny5の優れた回答よりも改善されていると思います。すべてのクラスでコピーコンストラクターを定義し、基本クラスでCloneメソッドのリフレクションを使用してコピーコンストラクターを見つけて実行します。ハンドルクローンオーバーライドのスタックを必要とせず、多くの状況で楽器を単純化しすぎる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で型を実装するオブジェクトを使用するとき、あなたが何を知っているかを知らないため、それを実装しない」と言われているため、スタイルポリスにbyられます。これにより、インターフェースが役に立たなくなります。」その意味するところは、ディープコピーを取得しているか浅いコピーを取得しているかということです。まあ、これは単にso弁です。これは、「何を取得するかわからない」ため、コピーコンストラクターを使用しないことを意味しますか?もちろん違います。何を取得するのかわからない場合、これはインターフェイスの問題ではなく、クラスの設計の問題です。
私の意見では、最も明確な方法は、BinaryFormatter
でMemoryStream
を使用したバイナリシリアル化を適用することです。
C#でのディープクローニングに関するMSDNスレッド があり、上記のアプローチが提案されています。
少なくとも、具体的なクラスのみがクローンを処理できるようにし、抽象クラスには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);
}
}