C#では、クラスに(ディープ)コピー機能を追加するための推奨される方法は何ですか?コピーコンストラクターICloneableを実装するのClone()か、それともメソッドから派生して実装するのか?
備考:関係のないものだと思ったので、括弧内に「ディープ」と書いた。どうやら他の人は同意しないので、コピーコンストラクター/オペレーター/関数が、どのコピーバリアントを実装するかを明確にする必要があるかどうか尋ねました。
C#では、クラスに(ディープ)コピー機能を追加するための推奨される方法は何ですか?コピーコンストラクターICloneableを実装するのClone()か、それともメソッドから派生して実装するのか?
備考:関係のないものだと思ったので、括弧内に「ディープ」と書いた。どうやら他の人は同意しないので、コピーコンストラクター/オペレーター/関数が、どのコピーバリアントを実装するかを明確にする必要があるかどうか尋ねました。
回答:
から派生してはいけませんICloneable。
その理由は、Microsoftが.netフレームワークを設計したときに、Clone()メソッドがICloneableディープクローンかシャロークローンのどちらであるかを指定しなかったため、呼び出しがオブジェクトのディープクローンまたはシャロークローンのどちらであるかを呼び出し元が知らないため、インターフェイスが意味的に壊れているためです。
代わりに、独自のIDeepCloneable(およびIShallowCloneable)インターフェイスをDeepClone()(およびShallowClone())メソッドで定義する必要があります。
2つのインターフェイスを定義できます。1つは厳密に型指定された複製をサポートするジェネリックパラメーターを持ち、もう1つは異なる型の複製可能なオブジェクトのコレクションを操作するときに弱い型付けの複製機能を維持しないインターフェースです。
public interface IDeepCloneable
{
object DeepClone();
}
public interface IDeepCloneable<T> : IDeepCloneable
{
T DeepClone();
}
次のように実装します。
public class SampleClass : IDeepCloneable<SampleClass>
{
public SampleClass DeepClone()
{
// Deep clone your object
return ...;
}
object IDeepCloneable.DeepClone()
{
return this.DeepClone();
}
}
一般的には、コピーコンストラクターではなく、説明されているインターフェイスを使用することを好みます。コピーコンストラクターはおそらくディープクローンであると想定されますが、IDeepClonableインターフェイスを使用する場合ほど明確な意図はありません。
これについては、.net フレームワーク設計ガイドラインとBrad Abramsのブログで説明されています。
(フレームワーク/ライブラリではなく)アプリケーションを作成している場合、チーム外の誰もコードを呼び出さないことを確認できるので、それほど問題ではなく、意味の意味を割り当てることができます.net ICloneableインターフェイスに "deepclone"しますが、これが十分に文書化されており、チーム内で十分に理解されていることを確認する必要があります。個人的には、フレームワークのガイドラインに従います。)
IDeepCloneable<T>存在するあなたが独自の実装を行った場合、すなわち何Tを知っていますので...、SomeClass : IDeepCloneable<SomeClass> { ... }
MyFunc(IDeepClonable data)、そしてそれはそれが特定のタイプだけでなくすべてのクローン可能なオブジェクトで機能することができました。または、クローン可能なコレクションがある場合。IEnumerable<IDeepClonable> lotsOfCloneables次に、同時に多くのオブジェクトのクローンを作成できます。ただし、そのようなものが必要ない場合は、一般的でないものは除外してください。
C#で、クラスに(ディープ)コピー機能を追加するための推奨される方法は何ですか?コピーコンストラクターを実装するか、ICloneableから派生させてClone()メソッドを実装する必要がありますか?
問題ICloneableは、他の人が述べたように、それが深いコピーであるか浅いコピーであるかを指定していないため、実際には使用できず、実際にはほとんど使用されないことです。またobject、多くのキャストが必要になるため、が返されます。(そして、あなたは質問でクラスを具体的に述べICloneableましたstructが、ボクシングを必要とします)
コピー作成者は、ICloneableの問題の1つにも悩まされています。コピーコンストラクターが深いコピーと浅いコピーのどちらを実行しているかは明らかではありません。
Account clonedAccount = new Account(currentAccount); // Deep or shallow?
DeepClone()メソッドを作成するのが最善です。このように、意図は完全に明確です。
これは、それが静的メソッドであるかインスタンスメソッドであるかという問題を提起します。
Account clonedAccount = currentAccount.DeepClone(); // instance method
または
Account clonedAccount = Account.DeepClone(currentAccount); // static method
複製はオブジェクトが行っていることではなく、オブジェクトに対して行われているように見えるため、静的バージョンを少し優先することがあります。どちらの場合も、継承階層の一部であるオブジェクトのクローンを作成するときに対処する必要がある問題があり、それらの問題をどのように解決するかによって、最終的には設計が推進される可能性があります。
class CheckingAccount : Account
{
CheckAuthorizationScheme checkAuthorizationScheme;
public override Account DeepClone()
{
CheckingAccount clone = new CheckingAccount();
DeepCloneFields(clone);
return clone;
}
protected override void DeepCloneFields(Account clone)
{
base.DeepCloneFields(clone);
((CheckingAccount)clone).checkAuthorizationScheme = this.checkAuthorizationScheme.DeepClone();
}
}
クローンメソッドではreadonly、代わりにコンストラクターを使用した場合に可能であったフィールドを作成できないため、クローンメソッドではなくコピーコンストラクターを使用することをお勧めします。
ポリモーフィッククローニングが必要な場合は、コピーコンストラクターへの呼び出しで実装する基本クラスにabstractor virtual Clone()メソッドを追加できます。
複数の種類のコピーが必要な場合(例:深い/浅い)は、コピーコンストラクターでパラメーターを使用して指定できますが、私の経験では、通常、深いコピーと浅いコピーの混合が必要です。
例:
public class BaseType {
readonly int mBaseField;
public BaseType(BaseType pSource) =>
mBaseField = pSource.mBaseField;
public virtual BaseType Clone() =>
new BaseType(this);
}
public class SubType : BaseType {
readonly int mSubField;
public SubType(SubType pSource)
: base(pSource) =>
mSubField = pSource.mSubField;
public override BaseType Clone() =>
new SubType(this);
}
保護されたコピーコンストラクターを使用してclone()を実装する必要があるという大きな議論があります。
保護された(非パブリック)コピーコンストラクターを提供し、それをcloneメソッドから呼び出すことをお勧めします。これにより、オブジェクトを作成するタスクをクラス自体のインスタンスに委任できるため、拡張性が提供され、保護されたコピーコンストラクターを使用してオブジェクトを安全に作成できます。
したがって、これは「対」の質問ではありません。正しく行うには、コピーコンストラクタとクローンインターフェイスの両方が必要になる場合があります。
(ただし、推奨されるパブリックインターフェイスは、コンストラクタベースではなくClone()インターフェイスです。)
他の答えの明示的な深いまたは浅い議論に巻き込まれないでください。現実の世界では、ほとんどの場合、その中間にあるものであり、どちらにしても発信者の懸念事項ではありません。
Clone()コントラクトは、「最初のコントラクトを変更しても変更されません」です。コピーする必要があるグラフの量、またはそれを実現するために無限再帰を回避する方法は、呼び出し元には関係ありません。
ICloneableの実装は、それが深いコピーであるか浅いコピーであるかが指定されていないため、お勧めできません。そのため、コンストラクターを使用するか、自分で何かを実装します。多分それをDeepCopy()と呼んでそれを本当に明白にするでしょう!
IDeepCloneable<T>とのインターフェースを定義するだけDeepClone()です。
コピーコンストラクターと抽象クラスで問題が発生します。次のことを実行するとします。
abstract class A
{
public A()
{
}
public A(A ToCopy)
{
X = ToCopy.X;
}
public int X;
}
class B : A
{
public B()
{
}
public B(B ToCopy) : base(ToCopy)
{
Y = ToCopy.Y;
}
public int Y;
}
class C : A
{
public C()
{
}
public C(C ToCopy)
: base(ToCopy)
{
Z = ToCopy.Z;
}
public int Z;
}
class Program
{
static void Main(string[] args)
{
List<A> list = new List<A>();
B b = new B();
b.X = 1;
b.Y = 2;
list.Add(b);
C c = new C();
c.X = 3;
c.Z = 4;
list.Add(c);
List<A> cloneList = new List<A>();
//Won't work
//foreach (A a in list)
// cloneList.Add(new A(a)); //Not this time batman!
//Works, but is nasty for anything less contrived than this example.
foreach (A a in list)
{
if(a is B)
cloneList.Add(new B((B)a));
if (a is C)
cloneList.Add(new C((C)a));
}
}
}
上記を実行した直後に、インターフェースを使用するか、DeepCopy()/ ICloneable.Clone()の実装を決定したかを希望し始めます。
コピーしようとしているオブジェクトがSerializableである場合は、それをシリアル化して逆シリアル化することで複製できます。そうすれば、クラスごとにコピーコンストラクタを作成する必要がなくなります。
現在コードにアクセスできませんが、このようなものです
public object DeepCopy(object source)
{
// Copy with Binary Serialization if the object supports it
// If not try copying with XML Serialization
// If not try copying with Data contract Serailizer, etc
}
これは、問題のクラスのコピーセマンティクスに依存しています。これは、開発者として定義する必要があります。選択されたメソッドは通常、クラスの意図されたユースケースに基づいています。多分両方の方法を実装することは理にかなっています。しかし、どちらにも同様の欠点があります。どのコピー方式を実装するかは明確ではありません。これは、クラスのドキュメントに明確に記載されている必要があります。
私にとっては:
// myobj is some transparent proxy object
var state = new ObjectState(myobj.State);
// do something
myobject = GetInstance();
var newState = new ObjectState(myobject.State);
if (!newState.Equals(state))
throw new Exception();
の代わりに:
// myobj is some transparent proxy object
var state = myobj.State.Clone();
// do something
myobject = GetInstance();
var newState = myobject.State.Clone();
if (!newState.Equals(state))
throw new Exception();
より明確な意図の表明のように見えた。
クローン可能なオブジェクトには標準的なパターンがあるはずだと思いますが、正確なパターンが何であるかはわかりません。クローニングに関しては、3つのタイプのクラスがあるように思われます。
私が知る限り、既存のオブジェクトと同じクラスの新しいオブジェクトを取得する唯一の方法は(少なくとも.net 2.0では)MemberwiseCloneを使用することです。良いパターンは、常に現在のタイプを返す「新しい」/「シャドウ」関数Cloneを持つことです。その定義は、常にMemberwiseCloneを呼び出してから、保護された仮想サブルーチンCleanupClone(originalObject)を呼び出すことです。CleanupCodeルーチンはbase.Cleanupcodeを呼び出して、ベースタイプのクローン作成のニーズを処理し、独自のクリーンアップを追加する必要があります。クローン作成ルーチンが元のオブジェクトを使用する必要がある場合は、タイプキャストする必要がありますが、それ以外の場合は、MemberwiseClone呼び出しでのみタイプキャストします。
残念ながら、タイプ(2)ではなく上記のタイプ(1)であった最低レベルのクラスは、その下位のタイプが複製の明示的なサポートを必要としないと想定するようにコーディングする必要があります。私はそれを回避する方法を本当に見ていません。
それでも、パターンを定義することは、何もしないよりはましだと思います。
ちなみに、基本型がiCloneableをサポートしていることはわかっているが、使用する関数の名前がわからない場合、基本型のiCloneable.Clone関数を参照する方法はありますか?
興味深い答えや議論をすべて読んだとしても、プロパティを正確にコピーする方法を自分自身に問いかけるかもしれません。それらすべてを明示的にコピーしますか、それともよりエレガントな方法がありますか?それがあなたの残りの質問である場合、これを見てください(StackOverflowで):
一般的な拡張メソッドを使用して、サードパーティのクラスのプロパティを「深く」クローンするにはどうすればよいですか?
CreateCopy()すべてのプロパティを含むオブジェクトの「ディープ」コピーを作成する拡張メソッドを実装する方法を説明します(プロパティごとに手動でプロパティをコピーする必要はありません)。