コピーコンストラクターとClone()


119

C#では、クラスに(ディープ)コピー機能を追加するための推奨される方法は何ですか?コピーコンストラクターICloneableを実装するのClone()か、それともメソッドから派生して実装するのか?

備考:関係のないものだと思ったので、括弧内に「ディープ」と書いた。どうやら他の人は同意しないので、コピーコンストラクター/オペレーター/関数が、どのコピーバリアントを実装するかを明確にする必要があるかどうか尋ねました。

回答:


91

から派生してはいけません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"しますが、これが十分に文書化されており、チーム内で十分に理解されていることを確認する必要があります。個人的には、フレームワークのガイドラインに従います。)


2
インターフェイスを使用する場合は、DeepClone(of T)()とDeepClone(of T)(dummy as T)のどちらもTを返しますか?後者の構文では、引数に基づいてTを推測できます。
スーパーキャット

@supercat:型を推測できるように、ダミーのパラメーターがあると言っていますか?それは私が思うオプションです。自動的に推論される型を取得するためだけにダミーパラメータを使用するのが好きかどうかはわかりません。おそらく私はあなたを誤解している。(たぶん、あなたが何を意味するのかわかるように、新しい回答にコードを投稿します)
Simon P Stevens

@supercat:型の推論を可能にするために、ダミーパラメータが正確に存在します。一部のコードでは、タイプにアクセスできない状態で何かを複製する必要がある場合があります(たとえば、フィールド、プロパティ、または別のクラスからの関数の戻りのため)。ダミーパラメータを使用すると、タイプを適切に推測できます。考えてみると、インターフェイスの要点はディープクローン可能なコレクションのようなものを作成することになるため、あまり役に立たないでしょう。その場合、型はコレクションのジェネリック型である必要があります。
スーパーキャット2010

2
質問!どのような状況で非ジェネリックバージョンが必要になりますか?だけのために私には、それは理にかなってIDeepCloneable<T>存在するあなたが独自の実装を行った場合、すなわち何Tを知っていますので...、SomeClass : IDeepCloneable<SomeClass> { ... }
カイルバラン

2
@カイルは、あなたがクローン可能なオブジェクトを取るメソッドを持っていたと言いますMyFunc(IDeepClonable data)、そしてそれはそれが特定のタイプだけでなくすべてのクローン可能なオブジェクトで機能することができました。または、クローン可能なコレクションがある場合。IEnumerable<IDeepClonable> lotsOfCloneables次に、同時に多くのオブジェクトのクローンを作成できます。ただし、そのようなものが必要ない場合は、一般的でないものは除外してください。
Simon P Stevens 14

33

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

1
DeepClone()オプションが最適であるかどうかはわかりませんが、私の意見では基本的なプログラミング言語の機能について存在する混乱している状況を強調しているので、私はあなたの答えをとても気に入っています。ユーザーがどのオプションを最もよく選択するかはユーザー次第だと思います。
Dimitri C.

11
ここでポイントを議論するつもりはありませんが、私の意見では、呼び出し側は、Clone()を呼び出すときに、深いまたは浅いことをあまり気にする必要はありません。無効な共有状態のないクローンを取得していることを知っている必要があります。たとえば、ディープクローンでは、すべての要素をディープクローンしたくない場合があります。Cloneの呼び出し元が気にする必要があるのは、オリジナルへの無効でサポートされていない参照がない新しいコピーを取得していることだけです。メソッド「DeepClone」の呼び出しは、呼び出し側に実装の詳細を伝えすぎているようです
zumalifeguard

1
静的メソッドによってコピーされるのではなく、それ自体を複製する方法を知っているオブジェクトインスタンスの何が問題になっていますか?これは、生体細胞では常に現実の世界で発生します。あなたがこれを読んでいる今、あなた自身の体の細胞は自分自身をクローンするのに忙しい。IMO、静的メソッドオプションはより扱いにくく、機能を非表示にする傾向があり、他のユーザーの利益のために「最も驚くべき」実装の使用から逸脱しています。
ケン・ベケット

8
@KenBeckett-クローンがオブジェクトに対して行われたものであると私が感じる理由は、オブジェクトが「1つのことを行い、それをうまく行う」必要があるためです。通常、それ自体のコピーを作成することは、クラスのコアコンピテンシーではありませんが、それに取り組む機能です。BankAccountのクローンを作成することは、あなたがしたいことかもしれませんが、それ自体のクローンを作成することは、銀行口座の機能ではありません。あなたの細胞の例は、生殖は細胞が正確に行うために進化したものであるため、広く有益ではありません。Cell.Cloneは優れたインスタンスメソッドですが、これは他のほとんどの場合には当てはまりません。
ジェフリーLホイットリッジ

23

クローンメソッドでは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);
}

8
+1ポリモーフィッククローニングに対処するため。クローニングの重要なアプリケーション。
samis

18

保護されたコピーコンストラクターを使用してclone()を実装する必要があるという大きな議論があります。

保護された(非パブリック)コピーコンストラクターを提供し、それをcloneメソッドから呼び出すことをお勧めします。これにより、オブジェクトを作成するタスクをクラス自体のインスタンスに委任できるため、拡張性が提供され、保護されたコピーコンストラクターを使用してオブジェクトを安全に作成できます。

したがって、これは「対」の質問ではありません。正しく行うには、コピーコンストラクタとクローンインターフェイスの両方が必要になる場合があります。

(ただし、推奨されるパブリックインターフェイスは、コンストラクタベースではなくClone()インターフェイスです。)

他の答えの明示的な深いまたは浅い議論に巻き込まれないでください。現実の世界では、ほとんどの場合、その中間にあるものであり、どちらにしても発信者の懸念事項ではありません。

Clone()コントラクトは、「最初のコントラクトを変更しても変更されません」です。コピーする必要があるグラフの量、またはそれを実現するために無限再帰を回避する方法は、呼び出し元には関係ありません。


「発信者の懸念であってはならない」。これ以上は同意できませんでしたが、ここでは、List <T> aList = new List <T>(aFullListOfT)が深いコピー(これが私が望むもの)を行うのか、浅いコピー(壊れる)を行うのかを理解しようとしています私のコード)、そして仕事を完了するために別の方法を実装する必要があるかどうか!
ThunderGr 2013年

3
リスト<T>は、クローンが意味をなすにはあまりにも一般的です(ハハ)。あなたの場合、それは確かにリストのコピーにすぎず、リストが指すオブジェクトではありません。新しいリストを操作しても最初のリストには影響しませんが、オブジェクトは同じであり、不変でない限り、2番目のセットのオブジェクトを変更すると、最初のセットのオブジェクトは変更されます。ライブラリにlist.Clone()オペレーションがあった場合、「最初の操作を行っても何も変更しない」のように、結果が完全なクローンになると期待する必要があります。含まれるオブジェクトにも適用されます。
DanO 2014年

1
List <T>は、その内容を適切に複製することについて、あなたよりも何も知りません。基礎となるオブジェクトが不変である場合は、準備は完了です。それ以外の場合、基になるオブジェクトにClone()メソッドがある場合は、それを使用する必要があります。List <T> aList = new List <T>(aFullListOfT.Select(t = t.Clone())
DanO

1
ハイブリッドアプローチの場合は+1。どちらのアプローチにも長所と短所がありますが、これには全体的な利点があるようです。
カイルバラン2014年

12

ICloneableの実装は、それが深いコピーであるか浅いコピーであるかが指定されていないため、お勧めできません。そのため、コンストラクターを使用するか、自分で何かを実装します。多分それをDeepCopy()と呼んでそれを本当に明白にするでしょう!


5
@グラント、コンストラクタはどのようにインテントをリレーするのですか?IOW、オブジェクトがコンストラクターでそれ自体を取得した場合、コピーは深いですか、それとも浅いですか?それ以外の場合は、DeepCopy()(またはその他)の提案に完全に同意します。
2010

7
コンストラクターはICloneableインターフェイスと同じくらい不明確であると私は主張します。ディープクローンを行うかどうかを知るには、APIのドキュメント/コードを読む必要があります。メソッドIDeepCloneable<T>とのインターフェースを定義するだけDeepClone()です。
ケントブーガート、2007

2
@ジョン-リアクタリングはまだ終わっていません!
Grant Crofton、2010

@ Marc、@ Kent-ええ、公平な点ですが、コンストラクタもおそらく良いアイデアではありません。
Grant Crofton、2010

3
不明なタイプのオブジェクトでiCloneableが使用された用途を見たことがありますか?インターフェースの要点は、タイプが不明なオブジェクトで使用できることです。それ以外の場合は、Cloneを問題の型を返す標準メソッドにすることもできます。
スーパーキャット'27 / 07/27

12

コピーコンストラクターと抽象クラスで問題が発生します。次のことを実行するとします。

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()の実装を決定したかを希望し始めます。


2
インターフェースベースのアプローチについての良い議論。
DanO 2013

4

ICloneableの問題は、意図と一貫性の両方です。それが深いコピーなのか浅いコピーなのかははっきりしません。そのため、たぶんいずれかの方法でのみ使用されることはありません。

パブリックコピーコンストラクターは、その点については明確ではありません。

とはいえ、私はあなたのために機能し、意図を中継する方法システムを紹介します(やや自己文書化)


3

コピーしようとしているオブジェクトが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
}

6
ディープクローニングを実装する手段としてシリアル化を使用することは、ディープクローンをctorまたはメソッドとして表面化する必要があるかどうかの問題とは無関係です。
ケントブーガート

1
別の有効な選択肢だと思います。彼がこれらの2つのディープコピー方法に限定されているとは思いませんでした。
Shaun Bowe、2007

5
@Kent Boogaart-OPが「C#で、クラスに(ディープ)コピー機能を追加するための推奨される方法は何か」という行で始まることを考えると、Shaunがさまざまな代替案を提供するのは十分公平だと思います。特に、クローン機能を実装するクラスが多数あるレガシーシナリオでは、このトリックが役立ちます。独自のクローンを直接実装するほど軽量ではありませんが、それでも有用です。人々が私の質問の代わりに「考えたことがあるか...」という選択肢を提供しなかったとしたら、私は何年にもわたって私ほど多くを学んだことはありませんでした。
Rob Levine

2

これは、問題のクラスのコピーセマンティクスに依存しています。これは、開発者として定義する必要があります。選択されたメソッドは通常、クラスの意図されたユースケースに基づいています。多分両方の方法を実装することは理にかなっています。しかし、どちらにも同様の欠点があります。どのコピー方式を実装するかは明確ではありません。これは、クラスのドキュメントに明確に記載されている必要があります。

私にとっては:

// 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();

より明確な意図の表明のように見えた。


0

クローン可能なオブジェクトには標準的なパターンがあるはずだと思いますが、正確なパターンが何であるかはわかりません。クローニングに関しては、3つのタイプのクラスがあるように思われます。

  1. ディープクローニングを明示的にサポートするもの
  2. メンバーごとのクローン作成がディープクローンとして機能するが、明示的なサポートを必要としないもの。
  3. 有用なディープクローンを作成できないもの、およびメンバーごとのクローン作成は悪い結果をもたらします。

私が知る限り、既存のオブジェクトと同じクラスの新しいオブジェクトを取得する唯一の方法は(少なくとも.net 2.0では)MemberwiseCloneを使用することです。良いパターンは、常に現在のタイプを返す「新しい」/「シャドウ」関数Cloneを持つことです。その定義は、常にMemberwiseCloneを呼び出してから、保護された仮想サブルーチンCleanupClone(originalObject)を呼び出すことです。CleanupCodeルーチンはbase.Cleanupcodeを呼び出して、ベースタイプのクローン作成のニーズを処理し、独自のクリーンアップを追加する必要があります。クローン作成ルーチンが元のオブジェクトを使用する必要がある場合は、タイプキャストする必要がありますが、それ以外の場合は、MemberwiseClone呼び出しでのみタイプキャストします。

残念ながら、タイプ(2)ではなく上記のタイプ(1)であった最低レベルのクラスは、その下位のタイプが複製の明示的なサポートを必要としないと想定するようにコーディングする必要があります。私はそれを回避する方法を本当に見ていません。

それでも、パターンを定義することは、何もしないよりはましだと思います。

ちなみに、基本型がiCloneableをサポートしていることはわかっているが、使用する関数の名前がわからない場合、基本型のiCloneable.Clone関数を参照する方法はありますか?


0

興味深い答えや議論をすべて読んだとしても、プロパティを正確にコピーする方法を自分自身に問いかけるかもしれません。それらすべてを明示的にコピーしますか、それともよりエレガントな方法がありますか?それがあなたの残りの質問である場合、これを見てください(StackOverflowで):

一般的な拡張メソッドを使用して、サードパーティのクラスのプロパティを「深く」クローンするにはどうすればよいですか?

CreateCopy()すべてのプロパティを含むオブジェクトの「ディープ」コピーを作成する拡張メソッドを実装する方法を説明します(プロパティごとに手動でプロパティをコピーする必要はありません)。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.