ディープクローンオブジェクト


2226

私は次のようなことをしたいです:

MyObject myObj = GetMyObj(); // Create and fill a new object
MyObject newObj = myObj.Clone();

次に、元のオブジェクトに反映されていない新しいオブジェクトに変更を加えます。

私はこの機能を必要としないことが多いので、必要に応じて、新しいオブジェクトを作成して各プロパティを個別にコピーすることにしましたが、常に、より優れたまたはよりエレガントな処理方法があると感じています。状況。

オブジェクトを複製またはディープコピーして、元のオブジェクトに変更が反映されることなく、複製されたオブジェクトを変更するにはどうすればよいですか?


81
「オブジェクトをコピーするのがなぜひどいことなのですか?」agiledeveloper.com/articles/cloning072002.htm
Pedro77

stackoverflow.com/questions/8025890/…別のソリューション...
Felix K.

18
AutoMapperをご覧ください
Daniel Little

3
あなたの解決策ははるかに複雑です、私はそれを読むのを失ってしまいました...へへへ。DeepCloneインターフェイスを使用しています。パブリックインターフェイスIDeepCloneable <T> {T DeepClone(); }
Pedro77 2013

1
@ Pedro77-興味深いことに、その記事ではclone、クラスにメソッドを作成して、渡された内部のプライベートコンストラクターを呼び出すように言っていますthis。したがって、コピーはひどいものです(sic)が、注意深くコピーすること(そして記事を読む価値があることは間違いありません)はそうではありません。; ^)
ルフィン

回答:


1715

標準的な方法はICloneableインターフェースを実装することですが(ここで説明されているので、私は逆流しません)、コードプロジェクトで見つけたディープクローンオブジェクトのコピー機を次に示しますがあります。

他の場所で述べたように、オブジェクトがシリアル化可能である必要があります。

using System;
using System.IO;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;

/// <summary>
/// Reference Article http://www.codeproject.com/KB/tips/SerializedObjectCloner.aspx
/// Provides a method for performing a deep copy of an object.
/// Binary Serialization is used to perform the copy.
/// </summary>
public static class ObjectCopier
{
    /// <summary>
    /// Perform a deep Copy of the object.
    /// </summary>
    /// <typeparam name="T">The type of object being copied.</typeparam>
    /// <param name="source">The object instance to copy.</param>
    /// <returns>The copied object.</returns>
    public static T Clone<T>(T source)
    {
        if (!typeof(T).IsSerializable)
        {
            throw new ArgumentException("The type must be serializable.", nameof(source));
        }

        // Don't serialize a null object, simply return the default for that object
        if (Object.ReferenceEquals(source, null))
        {
            return default(T);
        }

        IFormatter formatter = new BinaryFormatter();
        Stream stream = new MemoryStream();
        using (stream)
        {
            formatter.Serialize(stream, source);
            stream.Seek(0, SeekOrigin.Begin);
            return (T)formatter.Deserialize(stream);
        }
    }
}

これは、オブジェクトをシリアル化してから、新しいオブジェクトに逆シリアル化するという考え方です。利点は、オブジェクトが複雑になりすぎたときに、すべてのクローンを作成することについて心配する必要がないことです。

そして、拡張メソッドを使用して(最初に参照されたソースからも):

C#3.0の新しい拡張メソッドを使用したい場合は、メソッドを次のシグネチャに変更します。

public static T Clone<T>(this T source)
{
   //...
}

これで、メソッド呼び出しは単にになりobjectBeingCloned.Clone();ます。

編集(2015年1月10日)これを再検討すると思いましたが、最近(Newtonsoft)Jsonを使用してこれを行うようになったので、軽くなり、[Serializable]タグのオーバーヘッドが回避されます。(注意: @atconwayは、プライベートメンバーはJSONメソッドを使用して複製されないことをコメントで指摘しています)

/// <summary>
/// Perform a deep Copy of the object, using Json as a serialisation method. NOTE: Private members are not cloned using this method.
/// </summary>
/// <typeparam name="T">The type of object being copied.</typeparam>
/// <param name="source">The object instance to copy.</param>
/// <returns>The copied object.</returns>
public static T CloneJson<T>(this T source)
{            
    // Don't serialize a null object, simply return the default for that object
    if (Object.ReferenceEquals(source, null))
    {
        return default(T);
    }

    // initialize inner objects individually
    // for example in default constructor some list property initialized with some values,
    // but in 'source' these items are cleaned -
    // without ObjectCreationHandling.Replace default constructor values will be added to result
    var deserializeSettings = new JsonSerializerSettings {ObjectCreationHandling = ObjectCreationHandling.Replace};

    return JsonConvert.DeserializeObject<T>(JsonConvert.SerializeObject(source), deserializeSettings);
}

24
stackoverflow.com/questions/78536/cloning-objects-in-c/...は、上記のコードへのリンクを有し【および参照二つの他のそのような実装では、私の文脈においてより適切であり、その一方]
ルーベンBartelink

102
シリアライゼーション/デシリアライゼーションには、不要な大きなオーバーヘッドが伴います。C#のICloneableインターフェイスと.MemberWise()クローンメソッドを参照してください。
3Dave、2010年

18
@David、当然ですが、オブジェクトが軽量で、それを使用したときのパフォーマンスヒットが要件に対して高すぎない場合は、便利なヒントです。大量のデータをループで集中的に使用したことはありませんが、パフォーマンスに関する懸念は1つもありません。
johnc 2010年

16
@Amir:実際には、no:typeof(T).IsSerializableタイプが[Serializable]属性でマークされている場合もtrue です。ISerializableインターフェースを実装する必要はありません。
Daniel Gehriger、2011年

11
この方法は便利で、何度も使ったことがありますが、Medium Trustとはまったく互換性がないので、互換性が必要なコードを作成する場合は注意してください。BinaryFormatterはプライベートフィールドにアクセスするため、部分信頼環境のデフォルトのアクセス権セットでは機能しません。別のシリアライザーを試すこともできますが、着信オブジェクトがプライベートフィールドに依存している場合、クローンが完全ではない可能性があることを呼び出し元が知っていることを確認してください。
Alex Norcliffe、2011年

298

私はほとんどがプリミティブとリストの非常にシンプルなオブジェクトのクローンを欲しがっていました。オブジェクトがそのままJSONシリアライズ可能である場合、このメソッドがうまくいきます。これには、クローンされたクラスのインターフェースの変更や実装は必要ありません。JSON.NETのようなJSONシリアライザーだけです。

public static T Clone<T>(T source)
{
    var serialized = JsonConvert.SerializeObject(source);
    return JsonConvert.DeserializeObject<T>(serialized);
}

また、この拡張メソッドを使用できます

public static class SystemExtension
{
    public static T Clone<T>(this T source)
    {
        var serialized = JsonConvert.SerializeObject(source);
        return JsonConvert.DeserializeObject<T>(serialized);
    }
}

13
solutiojnはBinaryFormatterソリューションよりもさらに高速です。NET
シリアライゼーションの

3
これをありがとう。C#用のMongoDBドライバーに同梱されているBSONシリアライザーを使用して、基本的に同じことを行うことができました。
マーク・エワー2014年

3
これは私にとって最良の方法ですが、使用しますNewtonsoft.Json.JsonConvertが同じです
ピエール

1
これが機能するためには、すでに説明したように、複製するオブジェクトをシリアライズ可能にする必要があります。これは、たとえば、循環依存関係がない可能性があることも意味します
radomeit

2
実装はほとんどのプログラミング言語に適用できるため、これが最良のソリューションだと思います。
mr5

178

ICloneableを使用しない理由は、ジェネリックインターフェイスがないためではありません。 それを使わない理由はそれが曖昧だからです。浅いコピーと深いコピーのどちらを取得するかは明確ではありません。それは実装者次第です。

はい、MemberwiseClone浅いコピーを作成しますが、その逆MemberwiseCloneはそうではありませんClone。おそらくDeepClone存在しないでしょう。ICloneableインターフェイスを介してオブジェクトを使用する場合、基になるオブジェクトがどのような種類の複製を実行するかはわかりません。(また、XMLコメントでは明確になりません。オブジェクトのCloneメソッドのコメントではなく、インターフェースのコメントが取得されるためです。)

私がいつもやっていることは、単純にCopy自分がやりたいことを正確に行う方法を作ることです。


ICloneableがなぜ曖昧であると考えられているのか、私にはよくわかりません。Dictionary(Of T、U)のような型を考えると、ICloneable.Cloneは、新しいディクショナリを同じTとUを含む独立したディクショナリにするために必要な深くて浅いコピーのレベルを実行する必要があると予想します(構造体の内容、および/またはオブジェクト参照)オリジナルとして。あいまいさはどこにありますか?確かに、「Self」メソッドを含むISelf(Of T)を継承したジェネリックICloneable(Of T)の方がはるかに優れていますが、深いvs浅いクローニングのあいまいさはわかりません。
スーパーキャット2011年

31
あなたの例は問題を説明しています。Dictionary <string、Customer>があるとします。複製されたディクショナリには、オリジナルと同じ Customerオブジェクト、またはそれらのCustomerオブジェクトのコピーを含める必要がありますか?どちらの場合にも合理的な使用例があります。しかし、ICloneableは、どれを取得するかを明確にしていません。それが役に立たない理由です。
Ryan Lundy

@Kyralessa Microsoft MSDNの記事では、深いコピーと浅いコピーのどちらを要求しているかがわからないという、まさにこの問題が実際に述べられています。
クラッシュ

重複したstackoverflow.com/questions/129389/…からの回答は、再帰的なMembershipCloneに基づくコピー拡張について説明しています
Michael Freidgeim

123

ここにリンクされている多くのオプションとこの問題の可能な解決策についてよく読んだ後、すべてのオプションはIan Pのリンクでかなりうまくまとめられていると思います(他のすべてのオプションはそれらのバリエーションです)。Pedro77質問コメントへのリンク

したがって、これら2つの参照の関連部分をここにコピーします。そうすれば、次のことが可能になります。

Cシャープでオブジェクトのクローンを作成するための最善の方法!

まず第一に、これらはすべて私たちのオプションです:

高速ツリーによる高速ディープコピー記事に は、シリアル化、リフレクション、および式ツリーによるクローン作成のパフォーマンス比較もあります。

私が選ぶ理由 ICloneable理由(つまり、手動)

Venkat Subramaniam氏(ここに冗長リンクがあります)がその理由を詳しく説明しています

彼の記事はすべて、PersonBrainCityの 3つのオブジェクトを使用して、ほとんどの場合に適用できるようにする例を中心にしています。。自分の頭脳を持つが同じ都市を持つ人をクローンしたい。上記の他の方法で問題が発生するか、記事を読むことができます。

これは彼の結論を少し変更したバージョンです。

指定によるオブジェクトのコピー Newクラス名を多くの場合、拡張できないコードが生成されます。これを実現するには、プロトタイプパターンのアプリケーションであるクローンを使用するのがより良い方法です。ただし、C#(およびJava)で提供されているようにcloneを使用すると、問題が発生する可能性があります。保護された(非パブリック)コピーコンストラクターを提供し、それをcloneメソッドから呼び出すことをお勧めします。これにより、オブジェクトを作成するタスクをクラス自体のインスタンスに委任できるため、拡張性が提供され、保護されたコピーコンストラクターを使用してオブジェクトを安全に作成できます。

うまくいけば、この実装は物事を明確にすることができます:

public class Person : ICloneable
{
    private final Brain brain; // brain is final since I do not want 
                // any transplant on it once created!
    private int age;
    public Person(Brain aBrain, int theAge)
    {
        brain = aBrain; 
        age = theAge;
    }
    protected Person(Person another)
    {
        Brain refBrain = null;
        try
        {
            refBrain = (Brain) another.brain.clone();
            // You can set the brain in the constructor
        }
        catch(CloneNotSupportedException e) {}
        brain = refBrain;
        age = another.age;
    }
    public String toString()
    {
        return "This is person with " + brain;
        // Not meant to sound rude as it reads!
    }
    public Object clone()
    {
        return new Person(this);
    }
    
}

次に、クラスをPersonから派生させることを検討してください。

public class SkilledPerson extends Person
{
    private String theSkills;
    public SkilledPerson(Brain aBrain, int theAge, String skills)
    {
        super(aBrain, theAge);
        theSkills = skills;
    }
    protected SkilledPerson(SkilledPerson another)
    {
        super(another);
        theSkills = another.theSkills;
    }

    public Object clone()
    {
        return new SkilledPerson(this);
    }
    public String toString()
    {
        return "SkilledPerson: " + super.toString();
    }
}

次のコードを実行してみてください:

public class User
{
    public static void play(Person p)
    {
        Person another = (Person) p.clone();
        System.out.println(p);
        System.out.println(another);
    }
    public static void main(String[] args)
    {
        Person sam = new Person(new Brain(), 1);
        play(sam);
        SkilledPerson bob = new SkilledPerson(new SmarterBrain(), 1, "Writer");
        play(bob);
    }
}

生成される出力は次のようになります。

This is person with Brain@1fcc69
This is person with Brain@253498
SkilledPerson: This is person with SmarterBrain@1fef6f
SkilledPerson: This is person with SmarterBrain@209f4e

オブジェクトの数のカウントを保持している場合、ここで実装されているクローンはオブジェクトの数の正しいカウントを保持することに注意してください。


6
MSではICloneable、パブリックメンバーには使用しないことをお勧めします。「Cloneの呼び出し元は予測可能な複製操作を実行するメソッドに依存できないため、ICloneableをパブリックAPIに実装しないことをお勧めします。」msdn.microsoft.com/en-us/library/…ただし、リンクされた記事でVenkat Subramaniamによって与えられた説明に基づいて、ICloneableオブジェクトの作成者が深いコピーと浅いコピーのどちらのプロパティを理解するか(つまり、深いコピーの脳、浅いコピーの都市)
BateTech

まず、私はこのトピック(パブリックAPI)のエキスパートとはかけ離れています。MSの発言は非常に理にかなっていると私は一度思います。そして、そのAPI のユーザーがそのような深い理解を持っていると想定するのは安全ではないと思います。したがって、誰が使用するかが本当に問題にならない場合にのみ、パブリックAPIに実装するのが理にかなっています。ある種のUMLを非常に明確にして各プロパティを明確に区別することが役立つと思います。しかし、もっと経験のある人から連絡をもらいたいです。:P
cregox 2015年

CGbR Clone Generatorを使用して、手動でコードを記述しなくても同様の結果を得ることができます。
トキサントロン2016年

中級言語の実装は有用です
Michael Freidgeim

C#には決勝戦はありません
Konrad、

84

私はクローンよりコピーコンストラクタを好む。意図はより明確です。


5
.Netにはコピーコンストラクターがありません。
ポップカタリン

48
確かにそうです:new MyObject(objToCloneFrom)クローンするオブジェクトをパラメータとして受け取るctorを宣言するだけです。
ニック

30
同じことではありません。すべてのクラスに手動で追加する必要があり、ディープコピーを保証しているかどうかさえわかりません。
Dave Van den Eynde

15
コピートラクターの場合は+1。また、オブジェクトのタイプごとにclone()関数を手動で作成する必要があります。クラス階層が数レベル深くなると、これでうまくいきます。
アンドリューグラント

3
ただし、コピーコンストラクターでは階層が失われます。agiledeveloper.com/articles/cloning072002.htm
ウィル

42

すべてのパブリックプロパティをコピーする単純な拡張メソッド。どのオブジェクトでも機能し、クラスがである必要ありません[Serializable]。他のアクセスレベルに拡張できます。

public static void CopyTo( this object S, object T )
{
    foreach( var pS in S.GetType().GetProperties() )
    {
        foreach( var pT in T.GetType().GetProperties() )
        {
            if( pT.Name != pS.Name ) continue;
            ( pT.GetSetMethod() ).Invoke( T, new object[] 
            { pS.GetGetMethod().Invoke( S, null ) } );
        }
    };
}

15
残念ながら、これには欠陥があります。これは、objectOne.MyProperty = objectTwo.MyPropertyを呼び出すことと同じです(つまり、参照をコピーするだけです)。プロパティの値は複製されません。
Alex Norcliffe、2011年

1
Alex Norcliffeへ:質問の著者は、クローンではなく「各プロパティのコピー」について尋ねました。ほとんどの場合、プロパティを正確に複製する必要はありません。
Konstantin Salavatov 2012年

1
私はこの方法を使用することを考えていますが、再帰を伴います。したがって、プロパティの値が参照の場合、新しいオブジェクトを作成して、CopyToを再度呼び出します。使用するすべてのクラスにパラメーターなしのコンストラクターが必要であるという問題が1つだけあります。誰かがすでにこれを試しましたか?これが実際にDataRowやDataTableなどの.netクラスを含むプロパティで機能するかどうかも疑問に思いますか?
Koryu 2013

33

CloneExtensionsライブラリプロジェクトを作成しました。式ツリーランタイムコードのコンパイルによって生成された単純な割り当て操作を使用して、高速でディープクローンを実行します。

それの使い方?

フィールドまたはプロパティ間の割り当ての調子で独自のメソッドCloneまたはCopyメソッドを記述する代わりに、プログラムは式ツリーを使用して自分で行います。GetClone<T>()拡張メソッドとしてマークされたメソッドを使用すると、インスタンスでそれを呼び出すだけです。

var newInstance = source.GetClone();

enum sourcenewInstance使用して、コピー元を選択できますCloningFlags

var newInstance 
    = source.GetClone(CloningFlags.Properties | CloningFlags.CollectionItems);

何をクローンできますか?

  • プリミティブ(int、uint、byte、double、charなど)、既知の不変タイプ(DateTime、TimeSpan、String)およびデリゲート(Action、Funcなど)
  • ヌル可能
  • T []配列
  • 汎用クラスと構造体を含む、カスタムクラスと構造体。

次のクラス/構造体メンバーは内部的に複製されます:

  • 読み取り専用フィールドではなく、パブリックフィールドの値
  • getアクセサーとsetアクセサーの両方を持つパブリックプロパティの値
  • ICollectionを実装する型のコレクション項目

どれくらい速いですか?

ソリューションはリフレクションよりも高速です。これは、メンバー情報を1回だけ収集する必要があるGetClone<T>ため、特定のタイプで初めて使用される前にTです。

また、同じタイプのインスタンスを2つ以上複製すると、シリアル化ベースのソリューションよりも高速になりますT

もっと...

ドキュメントで生成された式の詳細を読む

の式デバッグリストの例List<int>

.Lambda #Lambda1<System.Func`4[System.Collections.Generic.List`1[System.Int32],CloneExtensions.CloningFlags,System.Collections.Generic.IDictionary`2[System.Type,System.Func`2[System.Object,System.Object]],System.Collections.Generic.List`1[System.Int32]]>(
    System.Collections.Generic.List`1[System.Int32] $source,
    CloneExtensions.CloningFlags $flags,
    System.Collections.Generic.IDictionary`2[System.Type,System.Func`2[System.Object,System.Object]] $initializers) {
    .Block(System.Collections.Generic.List`1[System.Int32] $target) {
        .If ($source == null) {
            .Return #Label1 { null }
        } .Else {
            .Default(System.Void)
        };
        .If (
            .Call $initializers.ContainsKey(.Constant<System.Type>(System.Collections.Generic.List`1[System.Int32]))
        ) {
            $target = (System.Collections.Generic.List`1[System.Int32]).Call ($initializers.Item[.Constant<System.Type>(System.Collections.Generic.List`1[System.Int32])]
            ).Invoke((System.Object)$source)
        } .Else {
            $target = .New System.Collections.Generic.List`1[System.Int32]()
        };
        .If (
            ((System.Byte)$flags & (System.Byte).Constant<CloneExtensions.CloningFlags>(Fields)) == (System.Byte).Constant<CloneExtensions.CloningFlags>(Fields)
        ) {
            .Default(System.Void)
        } .Else {
            .Default(System.Void)
        };
        .If (
            ((System.Byte)$flags & (System.Byte).Constant<CloneExtensions.CloningFlags>(Properties)) == (System.Byte).Constant<CloneExtensions.CloningFlags>(Properties)
        ) {
            .Block() {
                $target.Capacity = .Call CloneExtensions.CloneFactory.GetClone(
                    $source.Capacity,
                    $flags,
                    $initializers)
            }
        } .Else {
            .Default(System.Void)
        };
        .If (
            ((System.Byte)$flags & (System.Byte).Constant<CloneExtensions.CloningFlags>(CollectionItems)) == (System.Byte).Constant<CloneExtensions.CloningFlags>(CollectionItems)
        ) {
            .Block(
                System.Collections.Generic.IEnumerator`1[System.Int32] $var1,
                System.Collections.Generic.ICollection`1[System.Int32] $var2) {
                $var1 = (System.Collections.Generic.IEnumerator`1[System.Int32]).Call $source.GetEnumerator();
                $var2 = (System.Collections.Generic.ICollection`1[System.Int32])$target;
                .Loop  {
                    .If (.Call $var1.MoveNext() != False) {
                        .Call $var2.Add(.Call CloneExtensions.CloneFactory.GetClone(
                                $var1.Current,
                                $flags,


                         $initializers))
                } .Else {
                    .Break #Label2 { }
                }
            }
            .LabelTarget #Label2:
        }
    } .Else {
        .Default(System.Void)
    };
    .Label
        $target
    .LabelTarget #Label1:
}

}

次のc#コードのように同じ意味は何ですか:

(source, flags, initializers) =>
{
    if(source == null)
        return null;

    if(initializers.ContainsKey(typeof(List<int>))
        target = (List<int>)initializers[typeof(List<int>)].Invoke((object)source);
    else
        target = new List<int>();

    if((flags & CloningFlags.Properties) == CloningFlags.Properties)
    {
        target.Capacity = target.Capacity.GetClone(flags, initializers);
    }

    if((flags & CloningFlags.CollectionItems) == CloningFlags.CollectionItems)
    {
        var targetCollection = (ICollection<int>)target;
        foreach(var item in (ICollection<int>)source)
        {
            targetCollection.Add(item.Clone(flags, initializers));
        }
    }

    return target;
}

それはあなたがあなた自身のClone方法をどのように書くのと全く似ていませんList<int>か?


2
NuGetでこれが発生する可能性は何ですか?それは最善の解決策のようです。NCloneと比較してどうですか?
クラッシュ

この回答はもっと賛成すべきだと思います。ICloneableを手動で実装するのは面倒でエラーが発生しやすく、パフォーマンスが重要で短期間に数千のオブジェクトをコピーする必要がある場合、リフレクションまたはシリアル化の使用は遅くなります。
ナイトコーダー、2015

まったくそうではありません。リフレクションについては間違っています。これを適切にキャッシュするだけです。下記の私の答えをチェックstackoverflow.com/a/34368738/4711853
ローマBorodov

31

さて、SilverlightでICloneableを使用する際に問題が発生しましたが、セラリゼーションのアイデアが好きでした。XMLをセラリゼーションできるので、次のようにしました。

static public class SerializeHelper
{
    //Michael White, Holly Springs Consulting, 2009
    //michael@hollyspringsconsulting.com
    public static T DeserializeXML<T>(string xmlData) where T:new()
    {
        if (string.IsNullOrEmpty(xmlData))
            return default(T);

        TextReader tr = new StringReader(xmlData);
        T DocItms = new T();
        XmlSerializer xms = new XmlSerializer(DocItms.GetType());
        DocItms = (T)xms.Deserialize(tr);

        return DocItms == null ? default(T) : DocItms;
    }

    public static string SeralizeObjectToXML<T>(T xmlObject)
    {
        StringBuilder sbTR = new StringBuilder();
        XmlSerializer xmsTR = new XmlSerializer(xmlObject.GetType());
        XmlWriterSettings xwsTR = new XmlWriterSettings();

        XmlWriter xmwTR = XmlWriter.Create(sbTR, xwsTR);
        xmsTR.Serialize(xmwTR,xmlObject);

        return sbTR.ToString();
    }

    public static T CloneObject<T>(T objClone) where T:new()
    {
        string GetString = SerializeHelper.SeralizeObjectToXML<T>(objClone);
        return SerializeHelper.DeserializeXML<T>(GetString);
    }
}

31

ValueInjecterAutomapperなどのサードパーティ製アプリケーションをすでに使用している場合は、次のようなことができます。

MyObject oldObj; // The existing object to clone

MyObject newObj = new MyObject();
newObj.InjectFrom(oldObj); // Using ValueInjecter syntax

この方法を使用すると、あなたが実装する必要はありませんISerializableICloneableあなたのオブジェクトに。これはMVC / MVVMパターンと共通なので、このような単純なツールが作成されました。

GitHubのValueInjecterディープクローニングサンプルを参照してください。


25

最高のような拡張メソッドを実装することです

public static T DeepClone<T>(this T originalObject)
{ /* the cloning code */ }

そしてそれをソリューションのどこでも使用する

var copy = anyObject.DeepClone();

次の3つの実装を使用できます。

  1. シリアル化(最短コード)
  2. 反射によって - 5倍速く
  3. 式の木に - 20倍高速化

すべてのリンクされたメソッドはうまく機能しており、徹底的にテストされています。


codeproject.com/Articles/1111658/…を投稿した式ツリーを使用してコードを複製すると、セキュリティ例外を伴う.Netフレームワークの新しいバージョンで失敗します。操作はランタイムを不安定にする可能性があります。これは、基本的に不正な式ツリーによる例外です。これは実行時にFuncを生成するために使用されます。実際に解決策があるかどうかを確認してください。実際、私は深い階層を持つ複雑なオブジェクトでのみ問題が発生し、単純なオブジェクトは簡単にコピーされます
Mrinal Kamboj

1
ExpressionTreeの実装は非常に良いようです。循環参照やプライベートメンバーでも機能します。属性は必要ありません。私が見つけた最良の答え。
N73k

最良の答え、非常にうまくいきました、あなたは私の日を救いました
Adel Mourad

23

簡単に言えば、ICloneableインターフェイスを継承してから、.clone関数を実装します。クローンはメンバーごとのコピーを行い、それを必要とするすべてのメンバーに対してディープコピーを実行してから、結果のオブジェクトを返す必要があります。これは再帰的な操作です(クローンを作成するクラスのすべてのメンバーが値型またはICloneableを実装していること、およびそれらのメンバーが値型またはICloneableを実装していることなどが必要です)。

ICloneableを使用したクローン作成の詳細については、こちらの記事をご覧ください

長い答えは「それが依存」です。他の人が述べたように、ICloneableはジェネリックではサポートされておらず、循環クラス参照について特別な考慮が必要であり、実際には一部の人が「間違い」と見なしていますには.NET Frameworkのとています。シリアル化の方法は、オブジェクトがシリアル化可能かどうかに依存します。シリアル化可能ではない場合があり、ユーザーが制御できない場合があります。コミュニティには、「ベスト」プラクティスについての議論がまだたくさんあります。実際には、ICloneableが最初にそうであると解釈されたようなすべての状況で、すべてのベストプラクティスに適したソリューションはありません。

その他のいくつかのオプションについては、この開発者のコ​​ーナーの記事を参照してください(Ianの功績)。


1
ICloneableには汎用インターフェイスがないため、そのインターフェイスを使用することはお勧めしません。
Karg

あなたのソリューションは循環参照を処理する必要があるまで機能し、それから物事が複雑になり始めるので、ディープシリアル化を使用してディープクローニングを実装することをお勧めします。
ポップカタリン

残念ながら、すべてのオブジェクトが直列化可能であるとは限らないため、そのメソッドを常に使用できるわけではありません。イアンのリンクは、これまでで最も包括的な答えです。
ザックバーリンゲーム、

19
  1. 基本的に、ICloneableインターフェイスを実装してから、オブジェクト構造のコピーを実現する必要があります。
  2. それがすべてのメンバーのディープコピーである場合は、すべての子もクローン可能であることを保証する必要があります(選択したソリューションには関係ありません)。
  3. たとえば、ORMオブジェクトをコピーする場合、ほとんどのフレームワークがセッションにアタッチされたオブジェクトを1つだけ許可し、このオブジェクトのクローンを作成してはならない場合や、可能であれば、注意する必要がある場合など、このプロセス中にいくつかの制限に注意する必要がある場合があります。これらのオブジェクトのセッションアタッチについて。

乾杯。


4
ICloneableには汎用インターフェイスがないため、そのインターフェイスを使用することはお勧めしません。
Karg

シンプルで簡潔な答えが最適です。
DavidGuaita

17

編集:プロジェクトは中止されました

未知のタイプへの真のクローンが必要な場合は、 fastcloneをご覧ください

これは、式ベースのクローン作成がバイナリシリアル化よりも約10倍速く機能し、完全なオブジェクトグラフの整合性を維持します。

つまり、階層内の同じオブジェクトを複数回参照した場合、クローンには単一のインスタンスが参照されます。

クローンされるオブジェクトに対するインターフェース、属性、またはその他の変更は必要ありません。


これはかなり便利なようです
LuckyLikey

システム全体、特にクローズドシステムよりも、1つのコードスナップショットから作業を開始する方が簡単です。ワンショットですべての問題を解決できるライブラリはないということは非常に理解できます。いくつかの緩和を行う必要があります。
TarmoPikaro 2015

1
私はあなたの解決策を試しました、そしてそれはうまくいくようです、ありがとう!この回答はもっと賛成すべきだと思います。ICloneableを手動で実装するのは面倒でエラーが発生しやすく、パフォーマンスが重要で短期間に数千のオブジェクトをコピーする必要がある場合、リフレクションまたはシリアル化の使用は遅くなります。
ナイトコーダー、2015

試してみましたが、うまくいきませんでした。MemberAccess例外をスローします。
マイケルブラウン

新しいバージョンの.NETでは動作せず、廃止されました
Michael Sander '19

14

物事をシンプルに保ち、他の人が述べたようにAutoMapperを使用します。これは、あるオブジェクトを別のオブジェクトにマップする単純な小さなライブラリです...オブジェクトを同じタイプの別のオブジェクトにコピーするには、必要なのは3行のコードだけです。

MyType source = new MyType();
Mapper.CreateMap<MyType, MyType>();
MyType target = Mapper.Map<MyType, MyType>(source);

これで、ターゲットオブジェクトがソースオブジェクトのコピーになりました。単純ではないですか?ソリューションのあらゆる場所で使用する拡張メソッドを作成します。

public static T Copy<T>(this T source)
{
    T copy = default(T);
    Mapper.CreateMap<T, T>();
    copy = Mapper.Map<T, T>(source);
    return copy;
}

拡張メソッドは次のように使用できます。

MyType copy = source.Copy();

これには注意してください。パフォーマンスが非常に低下します。私は最終的にjohnc回答に切り替えましたが、これはこの回答と同じくらい短く、パフォーマンスが大幅に向上しています。
アゴリラ2017

1
これは浅いコピーのみを行います。
N73k

11

.NETを克服するためにこれを思いついた手動でディープコピーのList <T>を行う必要が欠点を。

私はこれを使います:

static public IEnumerable<SpotPlacement> CloneList(List<SpotPlacement> spotPlacements)
{
    foreach (SpotPlacement sp in spotPlacements)
    {
        yield return (SpotPlacement)sp.Clone();
    }
}

そして別の場所で:

public object Clone()
{
    OrderItem newOrderItem = new OrderItem();
    ...
    newOrderItem._exactPlacements.AddRange(SpotPlacement.CloneList(_exactPlacements));
    ...
    return newOrderItem;
}

これを行うonelinerを考え出そうとしましたが、無名メソッドブロック内でyieldが機能しないため、それは不可能です。

さらによいことに、汎用のList <T>クローンを使用します。

class Utility<T> where T : ICloneable
{
    static public IEnumerable<T> CloneList(List<T> tl)
    {
        foreach (T t in tl)
        {
            yield return (T)t.Clone();
        }
    }
}

10

Q.なぜこの回答を選ぶのですか?

  • .NETで可能な最速の速度が必要な場合は、この回答を選択してください。
  • 本当に簡単な複製方法が必要な場合は、この回答を無視してください。

つまり、修正が必要なパフォーマンスのボトルネックがあり、プロファイラーでそれを証明できる場合を除いて、別の答えを使用してください

他の方法より10倍高速

ディープクローンを実行する次の方法は次のとおりです。

  • シリアライゼーション/デシリアライゼーションを伴うものよりも10倍高速。
  • .NETで可能な理論上の最大速度にかなり近いです。

そして方法...

究極のスピードのために、ネストされたMemberwiseCloneを使用してディープコピーを実行できます。値の構造体をコピーするのとほぼ同じ速度で、(a)リフレクションまたは(b)シリアライゼーション(このページの他の回答で説明されている)よりもはるかに高速です。

ことを注意場合は、使用深いコピーのためのネストされたMemberwiseCloneを、手動でクラスのネストされた各レベルのためShallowCopyを実装する必要があり、すべてのShallowCopy方法は、完全なクローンを作成すると述べた呼び出しDeepCopy。これは簡単です。合計で数行だけです。以下のデモコードを参照してください。

これは、100,000クローンの相対的なパフォーマンスの違いを示すコードの出力です。

  • ネストされた構造体のネストされたMemberwiseCloneの1.08秒
  • ネストされたクラスでのネストされたMemberwiseCloneの4.77秒
  • シリアライゼーション/デシリアライゼーションには39.93秒

ネストされたMemberwiseCloneをクラスで構造体をコピーするのとほぼ同じ速さで使用し、構造体をコピーすることは、.NETが理論的に可能な最大速度にかなり近いです。

Demo 1 of shallow and deep copy, using classes and MemberwiseClone:
  Create Bob
    Bob.Age=30, Bob.Purchase.Description=Lamborghini
  Clone Bob >> BobsSon
  Adjust BobsSon details
    BobsSon.Age=2, BobsSon.Purchase.Description=Toy car
  Proof of deep copy: If BobsSon is a true clone, then adjusting BobsSon details will not affect Bob:
    Bob.Age=30, Bob.Purchase.Description=Lamborghini
  Elapsed time: 00:00:04.7795670,30000000

Demo 2 of shallow and deep copy, using structs and value copying:
  Create Bob
    Bob.Age=30, Bob.Purchase.Description=Lamborghini
  Clone Bob >> BobsSon
  Adjust BobsSon details:
    BobsSon.Age=2, BobsSon.Purchase.Description=Toy car
  Proof of deep copy: If BobsSon is a true clone, then adjusting BobsSon details will not affect Bob:
    Bob.Age=30, Bob.Purchase.Description=Lamborghini
  Elapsed time: 00:00:01.0875454,30000000

Demo 3 of deep copy, using class and serialize/deserialize:
  Elapsed time: 00:00:39.9339425,30000000

MemberwiseCopyを使用してディープコピーを行う方法を理解するために、上記の時間を生成するために使用されたデモプロジェクトを次に示します。

// Nested MemberwiseClone example. 
// Added to demo how to deep copy a reference class.
[Serializable] // Not required if using MemberwiseClone, only used for speed comparison using serialization.
public class Person
{
    public Person(int age, string description)
    {
        this.Age = age;
        this.Purchase.Description = description;
    }
    [Serializable] // Not required if using MemberwiseClone
    public class PurchaseType
    {
        public string Description;
        public PurchaseType ShallowCopy()
        {
            return (PurchaseType)this.MemberwiseClone();
        }
    }
    public PurchaseType Purchase = new PurchaseType();
    public int Age;
    // Add this if using nested MemberwiseClone.
    // This is a class, which is a reference type, so cloning is more difficult.
    public Person ShallowCopy()
    {
        return (Person)this.MemberwiseClone();
    }
    // Add this if using nested MemberwiseClone.
    // This is a class, which is a reference type, so cloning is more difficult.
    public Person DeepCopy()
    {
            // Clone the root ...
        Person other = (Person) this.MemberwiseClone();
            // ... then clone the nested class.
        other.Purchase = this.Purchase.ShallowCopy();
        return other;
    }
}
// Added to demo how to copy a value struct (this is easy - a deep copy happens by default)
public struct PersonStruct
{
    public PersonStruct(int age, string description)
    {
        this.Age = age;
        this.Purchase.Description = description;
    }
    public struct PurchaseType
    {
        public string Description;
    }
    public PurchaseType Purchase;
    public int Age;
    // This is a struct, which is a value type, so everything is a clone by default.
    public PersonStruct ShallowCopy()
    {
        return (PersonStruct)this;
    }
    // This is a struct, which is a value type, so everything is a clone by default.
    public PersonStruct DeepCopy()
    {
        return (PersonStruct)this;
    }
}
// Added only for a speed comparison.
public class MyDeepCopy
{
    public static T DeepCopy<T>(T obj)
    {
        object result = null;
        using (var ms = new MemoryStream())
        {
            var formatter = new BinaryFormatter();
            formatter.Serialize(ms, obj);
            ms.Position = 0;
            result = (T)formatter.Deserialize(ms);
            ms.Close();
        }
        return (T)result;
    }
}

次に、メインからデモを呼び出します。

void MyMain(string[] args)
{
    {
        Console.Write("Demo 1 of shallow and deep copy, using classes and MemberwiseCopy:\n");
        var Bob = new Person(30, "Lamborghini");
        Console.Write("  Create Bob\n");
        Console.Write("    Bob.Age={0}, Bob.Purchase.Description={1}\n", Bob.Age, Bob.Purchase.Description);
        Console.Write("  Clone Bob >> BobsSon\n");
        var BobsSon = Bob.DeepCopy();
        Console.Write("  Adjust BobsSon details\n");
        BobsSon.Age = 2;
        BobsSon.Purchase.Description = "Toy car";
        Console.Write("    BobsSon.Age={0}, BobsSon.Purchase.Description={1}\n", BobsSon.Age, BobsSon.Purchase.Description);
        Console.Write("  Proof of deep copy: If BobsSon is a true clone, then adjusting BobsSon details will not affect Bob:\n");
        Console.Write("    Bob.Age={0}, Bob.Purchase.Description={1}\n", Bob.Age, Bob.Purchase.Description);
        Debug.Assert(Bob.Age == 30);
        Debug.Assert(Bob.Purchase.Description == "Lamborghini");
        var sw = new Stopwatch();
        sw.Start();
        int total = 0;
        for (int i = 0; i < 100000; i++)
        {
            var n = Bob.DeepCopy();
            total += n.Age;
        }
        Console.Write("  Elapsed time: {0},{1}\n\n", sw.Elapsed, total);
    }
    {               
        Console.Write("Demo 2 of shallow and deep copy, using structs:\n");
        var Bob = new PersonStruct(30, "Lamborghini");
        Console.Write("  Create Bob\n");
        Console.Write("    Bob.Age={0}, Bob.Purchase.Description={1}\n", Bob.Age, Bob.Purchase.Description);
        Console.Write("  Clone Bob >> BobsSon\n");
        var BobsSon = Bob.DeepCopy();
        Console.Write("  Adjust BobsSon details:\n");
        BobsSon.Age = 2;
        BobsSon.Purchase.Description = "Toy car";
        Console.Write("    BobsSon.Age={0}, BobsSon.Purchase.Description={1}\n", BobsSon.Age, BobsSon.Purchase.Description);
        Console.Write("  Proof of deep copy: If BobsSon is a true clone, then adjusting BobsSon details will not affect Bob:\n");
        Console.Write("    Bob.Age={0}, Bob.Purchase.Description={1}\n", Bob.Age, Bob.Purchase.Description);                
        Debug.Assert(Bob.Age == 30);
        Debug.Assert(Bob.Purchase.Description == "Lamborghini");
        var sw = new Stopwatch();
        sw.Start();
        int total = 0;
        for (int i = 0; i < 100000; i++)
        {
            var n = Bob.DeepCopy();
            total += n.Age;
        }
        Console.Write("  Elapsed time: {0},{1}\n\n", sw.Elapsed, total);
    }
    {
        Console.Write("Demo 3 of deep copy, using class and serialize/deserialize:\n");
        int total = 0;
        var sw = new Stopwatch();
        sw.Start();
        var Bob = new Person(30, "Lamborghini");
        for (int i = 0; i < 100000; i++)
        {
            var BobsSon = MyDeepCopy.DeepCopy<Person>(Bob);
            total += BobsSon.Age;
        }
        Console.Write("  Elapsed time: {0},{1}\n", sw.Elapsed, total);
    }
    Console.ReadKey();
}

ここでも、ディープコピーにNested MemberwiseCloneを使用する場合は、クラスのネストされた各レベルのShallowCopyを実装し、完全なクローンを作成するために上記のすべてのShallowCopyメソッドを呼び出すDeepCopyを実装する必要があります。これは簡単です。合計で数行だけです。上記のデモコードを参照してください。

値タイプと参照タイプ

オブジェクトの複製に関しては、「構造体」と「クラス」の間に大きな違いがあることに注意してください。

  • struct」がある場合、それは値タイプなので、コピーするだけでコンテンツが複製されます(ただし、この投稿の手法を使用しない限り、浅い複製しか作成されません)。
  • クラス」がある場合、それは参照型なので、コピーする場合は、ポインターをコピーするだけです。真のクローンを作成するには、より創造的になり、値の型と参照の型の違いを使用して、メモリ内に元のオブジェクトの別のコピーを作成する必要があります。

参照値型と参照型の間の違いを

デバッグに役立つチェックサム

  • オブジェクトのクローンを誤って作成すると、ピン留めが非常に困難なバグが発生する可能性があります。プロダクションコードでは、チェックサムを実装して、オブジェクトが適切に複製されており、オブジェクトへの別の参照によって破損していないことを再確認する傾向があります。このチェックサムは、リリースモードでオフにできます。
  • この方法は非常に便利です。多くの場合、オブジェクト全体ではなく、オブジェクトの一部のみを複製したいとします。

他の多くのスレッドから多くのスレッドを分離するのに本当に役立つ

このコードの優れた使用例の1つは、入れ子になったクラスまたは構造体のクローンをキューに入れて、プロデューサー/コンシューマーパターンを実装することです。

  • 1つ(または複数)のスレッドが所有するクラスを変更し、このクラスの完全なコピーを ConcurrentQueue
  • 次に、これらのクラスのコピーを引き出して処理する1つ(または複数)のスレッドがあります。

これは実際には非常にうまく機能し、1つ以上のスレッド(コンシューマー)から多くのスレッド(プロデューサー)を切り離すことができます。

そして、この方法も非常に高速です。ネストされた構造体を使用すると、ネストされたクラスのシリアライズ/デシリアライズより35倍速く、マシンで利用可能なすべてのスレッドを利用できます。

更新

明らかに、ExpressMapperは、上記のような手動コーディングよりも高速ではありません。プロファイラーとの比較を確認する必要があるかもしれません。


構造体をコピーする場合、浅いコピーを取得しますが、ディープコピーには特定の実装が必要になる場合があります。
Lasse V. Karlsen、2015

@ラッセ・V・カールセン。はい、あなたは完全に正しいです、私はこれをより明確にするために答えを更新しました。このメソッドは、構造体クラスの詳細なコピーを作成するために使用できます。含まれているサンプルデモコードを実行して、その方法を示すことができます。ネストされた構造体のディープクローンの例と、ネストされたクラスのディープクローンの別の例があります。
Contango 2015

9

一般に、ICloneableインターフェイスを実装し、Cloneを自分で実装します。C#オブジェクトには組み込みのMemberwiseCloneメソッドがあり、浅いコピーを実行して、すべてのプリミティブを手助けできます。

ディープコピーの場合、自動的に行う方法を知る方法はありません。


ICloneableには汎用インターフェイスがないため、そのインターフェイスを使用することはお勧めしません。
Karg

8

また、リフレクションを介して実装されているのを見てきました。基本的には、オブジェクトのメンバーを反復処理して、それらを新しいオブジェクトに適切にコピーするメソッドがありました。参照型またはコレクションに達したとき、それ自体が再帰的に呼び出されたと思います。リフレクションは高価ですが、かなりうまくいきました。


8

以下はディープコピーの実装です:

public static object CloneObject(object opSource)
{
    //grab the type and create a new instance of that type
    Type opSourceType = opSource.GetType();
    object opTarget = CreateInstanceOfType(opSourceType);

    //grab the properties
    PropertyInfo[] opPropertyInfo = opSourceType.GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);

    //iterate over the properties and if it has a 'set' method assign it from the source TO the target
    foreach (PropertyInfo item in opPropertyInfo)
    {
        if (item.CanWrite)
        {
            //value types can simply be 'set'
            if (item.PropertyType.IsValueType || item.PropertyType.IsEnum || item.PropertyType.Equals(typeof(System.String)))
            {
                item.SetValue(opTarget, item.GetValue(opSource, null), null);
            }
            //object/complex types need to recursively call this method until the end of the tree is reached
            else
            {
                object opPropertyValue = item.GetValue(opSource, null);
                if (opPropertyValue == null)
                {
                    item.SetValue(opTarget, null, null);
                }
                else
                {
                    item.SetValue(opTarget, CloneObject(opPropertyValue), null);
                }
            }
        }
    }
    //return the new item
    return opTarget;
}

2
参照型のプロパティを認識しないため、これはメンバーごとのクローンのように見えます
sll

1
盲目的に高速なパフォーマンスが必要な場合は、この実装を選択しないでください。リフレクションを使用するため、それほど高速ではありません。逆に、「時期尚早の最適化は悪いことです」ので、プロファイラーを実行するまでパフォーマンスの面は無視してください。
Contango 2011

1
CreateInstanceOfTypeが定義されていませんか?
MonsterMMORPG、2015年

それは整数で失敗します:「非静的メソッドにはターゲットが必要です。」
Mr.B

8

さまざまなプロジェクトですべての要件を満たすクローンを見つけることができなかったため、コードをクローンの要件を満たすように調整するのではなく、構成してさまざまなコード構造に適応できるディープクローナーを作成しました。クローンを作成するコードに注釈を追加するか、デフォルトの動作を維持するためにコードをそのままにしておくことで、これを実現します。リフレクション、タイプキャッシュを使用し、fasterflectに基づいています。クローニングプロセスは、大量のデータと高いオブジェクト階層に対して非常に高速です(他のリフレクション/シリアル化ベースのアルゴリズムと比較して)。

https://github.com/kalisohn/CloneBehave

nugetパッケージとしても利用可能:https ://www.nuget.org/packages/Clone.Behave/1.0.0

例:次のコードはアドレスをdeepCloneしますが、_currentJobフィールドの浅いコピーのみを実行します。

public class Person 
{
  [DeepClone(DeepCloneBehavior.Shallow)]
  private Job _currentJob;      

  public string Name { get; set; }

  public Job CurrentJob 
  { 
    get{ return _currentJob; }
    set{ _currentJob = value; }
  }

  public Person Manager { get; set; }
}

public class Address 
{      
  public Person PersonLivingHere { get; set; }
}

Address adr = new Address();
adr.PersonLivingHere = new Person("John");
adr.PersonLivingHere.BestFriend = new Person("James");
adr.PersonLivingHere.CurrentJob = new Job("Programmer");

Address adrClone = adr.Clone();

//RESULT
adr.PersonLivingHere == adrClone.PersonLivingHere //false
adr.PersonLivingHere.Manager == adrClone.PersonLivingHere.Manager //false
adr.PersonLivingHere.CurrentJob == adrClone.PersonLivingHere.CurrentJob //true
adr.PersonLivingHere.CurrentJob.AnyProperty == adrClone.PersonLivingHere.CurrentJob.AnyProperty //true

7

コードジェネレーター

手動による実装のシリアル化からリフレクションまで、多くのアイデアを見てきました。CGbRコードジェネレーターを使用して、まったく異なるアプローチを提案したいと思います。クローンの生成方法は、メモリとCPUの効率がよく、標準のDataContractSerializerの300倍高速です。

必要なのは、部分的なクラス定義だけでICloneable、残りはジェネレーターが行います。

public partial class Root : ICloneable
{
    public Root(int number)
    {
        _number = number;
    }
    private int _number;

    public Partial[] Partials { get; set; }

    public IList<ulong> Numbers { get; set; }

    public object Clone()
    {
        return Clone(true);
    }

    private Root()
    {
    }
} 

public partial class Root
{
    public Root Clone(bool deep)
    {
        var copy = new Root();
        // All value types can be simply copied
        copy._number = _number; 
        if (deep)
        {
            // In a deep clone the references are cloned 
            var tempPartials = new Partial[Partials.Length];
            for (var i = 0; i < Partials.Length; i++)
            {
                var value = Partials[i];
                value = value.Clone(true);
                tempPartials[i] = value;
            }
            copy.Partials = tempPartials;
            var tempNumbers = new List<ulong>(Numbers.Count);
            for (var i = 0; i < Numbers.Count; i++)
            {
                var value = Numbers[i];
                tempNumbers.Add(value);
            }
            copy.Numbers = tempNumbers;
        }
        else
        {
            // In a shallow clone only references are copied
            copy.Partials = Partials; 
            copy.Numbers = Numbers; 
        }
        return copy;
    }
}

注:最新バージョンではnullチェックが多くなっていますが、理解を深めるために省略しています。


6

私はそのようなCopyconstructorsが好きです:

    public AnyObject(AnyObject anyObject)
    {
        foreach (var property in typeof(AnyObject).GetProperties())
        {
            property.SetValue(this, property.GetValue(anyObject));
        }
        foreach (var field in typeof(AnyObject).GetFields())
        {
            field.SetValue(this, field.GetValue(anyObject));
        }
    }

コピーするものが他にある場合は、それらを追加します


6

この方法で問題が解決しました:

private static MyObj DeepCopy(MyObj source)
        {

            var DeserializeSettings = new JsonSerializerSettings { ObjectCreationHandling = ObjectCreationHandling.Replace };

            return JsonConvert.DeserializeObject<MyObj >(JsonConvert.SerializeObject(source), DeserializeSettings);

        }

次のように使用します。 MyObj a = DeepCopy(b);


6

ここでは、シリアル化/デシリアライゼーションを中継することなく、私にとってはうまく機能する高速で簡単なソリューションを紹介します。

public class MyClass
{
    public virtual MyClass DeepClone()
    {
        var returnObj = (MyClass)MemberwiseClone();
        var type = returnObj.GetType();
        var fieldInfoArray = type.GetRuntimeFields().ToArray();

        foreach (var fieldInfo in fieldInfoArray)
        {
            object sourceFieldValue = fieldInfo.GetValue(this);
            if (!(sourceFieldValue is MyClass))
            {
                continue;
            }

            var sourceObj = (MyClass)sourceFieldValue;
            var clonedObj = sourceObj.DeepClone();
            fieldInfo.SetValue(returnObj, clonedObj);
        }
        return returnObj;
    }
}

編集:必要

    using System.Linq;
    using System.Reflection;

それは私がそれを使用した方法です

public MyClass Clone(MyClass theObjectIneededToClone)
{
    MyClass clonedObj = theObjectIneededToClone.DeepClone();
}

5

次の手順を実行します:

  • 定義ISelf<T>読み取り専用とSelf戻ること性T、およびICloneable<out T>から導出する、ISelf<T>および方法を含みますT Clone()
  • 次に、渡された型へのキャストをCloneBase実装する型を定義します。 protected virtual generic VirtualCloneMemberwiseClone
  • 各派生型はVirtualClone、基本のcloneメソッドを呼び出して実装し、親VirtualCloneメソッドがまだ処理していない派生型の側面を適切に複製するために必要なことをすべて実行する必要があります。

継承の汎用性を最大限にするには、パブリッククローン機能を公開するクラスをにする必要sealedがありますが、基本クラスから派生しますが、それ以外の点では、クローンがないことを除いて同一です。明示的なクローン可能タイプの変数を渡すのではなく、タイプのパラメーターを使用しますICloneable<theNonCloneableType>。これにより、のクローン可能な派生物がのクローン可能な派生物で機能Fooすることを期待するルーチンが可能になります が、のクローンDerivedFooできない派生物の作成も可能になりFooます。


5

これはやってみてもいいと思います。

MyObject myObj = GetMyObj(); // Create and fill a new object
MyObject newObj = new MyObject(myObj); //DeepClone it

4

「[Serializable]」と「[DataContract]」の両方で動作する、承認された回答のバージョンを作成しました。私が書いてから久しぶりですが、覚えているなら[DataContract]には別のシリアライザが必要でした。

System、System.IO、System.Runtime.Serialization、System.Runtime.Serialization.Formatters.Binary、System.Xmlが必要です。

public static class ObjectCopier
{

    /// <summary>
    /// Perform a deep Copy of an object that is marked with '[Serializable]' or '[DataContract]'
    /// </summary>
    /// <typeparam name="T">The type of object being copied.</typeparam>
    /// <param name="source">The object instance to copy.</param>
    /// <returns>The copied object.</returns>
    public static T Clone<T>(T source)
    {
        if (typeof(T).IsSerializable == true)
        {
            return CloneUsingSerializable<T>(source);
        }

        if (IsDataContract(typeof(T)) == true)
        {
            return CloneUsingDataContracts<T>(source);
        }

        throw new ArgumentException("The type must be Serializable or use DataContracts.", "source");
    }


    /// <summary>
    /// Perform a deep Copy of an object that is marked with '[Serializable]'
    /// </summary>
    /// <remarks>
    /// Found on http://stackoverflow.com/questions/78536/cloning-objects-in-c-sharp
    /// Uses code found on CodeProject, which allows free use in third party apps
    /// - http://www.codeproject.com/KB/tips/SerializedObjectCloner.aspx
    /// </remarks>
    /// <typeparam name="T">The type of object being copied.</typeparam>
    /// <param name="source">The object instance to copy.</param>
    /// <returns>The copied object.</returns>
    public static T CloneUsingSerializable<T>(T source)
    {
        if (!typeof(T).IsSerializable)
        {
            throw new ArgumentException("The type must be serializable.", "source");
        }

        // Don't serialize a null object, simply return the default for that object
        if (Object.ReferenceEquals(source, null))
        {
            return default(T);
        }

        IFormatter formatter = new BinaryFormatter();
        Stream stream = new MemoryStream();
        using (stream)
        {
            formatter.Serialize(stream, source);
            stream.Seek(0, SeekOrigin.Begin);
            return (T)formatter.Deserialize(stream);
        }
    }


    /// <summary>
    /// Perform a deep Copy of an object that is marked with '[DataContract]'
    /// </summary>
    /// <typeparam name="T">The type of object being copied.</typeparam>
    /// <param name="source">The object instance to copy.</param>
    /// <returns>The copied object.</returns>
    public static T CloneUsingDataContracts<T>(T source)
    {
        if (IsDataContract(typeof(T)) == false)
        {
            throw new ArgumentException("The type must be a data contract.", "source");
        }

        // ** Don't serialize a null object, simply return the default for that object
        if (Object.ReferenceEquals(source, null))
        {
            return default(T);
        }

        DataContractSerializer dcs = new DataContractSerializer(typeof(T));
        using(Stream stream = new MemoryStream())
        {
            using (XmlDictionaryWriter writer = XmlDictionaryWriter.CreateBinaryWriter(stream))
            {
                dcs.WriteObject(writer, source);
                writer.Flush();
                stream.Seek(0, SeekOrigin.Begin);
                using (XmlDictionaryReader reader = XmlDictionaryReader.CreateBinaryReader(stream, XmlDictionaryReaderQuotas.Max))
                {
                    return (T)dcs.ReadObject(reader);
                }
            }
        }
    }


    /// <summary>
    /// Helper function to check if a class is a [DataContract]
    /// </summary>
    /// <param name="type">The type of the object to check.</param>
    /// <returns>Boolean flag indicating if the class is a DataContract (true) or not (false) </returns>
    public static bool IsDataContract(Type type)
    {
        object[] attributes = type.GetCustomAttributes(typeof(DataContractAttribute), false);
        return attributes.Length == 1;
    }

} 

4

わかりました、この投稿にはリフレクションの明らかな例がいくつかありますが、正しくキャッシュし始めるまで、リフレクションは通常遅いです。

適切にキャッシュする場合は、1000000オブジェクトを4,6秒でディープクローンします(Watcherで測定)。

static readonly Dictionary<Type, PropertyInfo[]> ProperyList = new Dictionary<Type, PropertyInfo[]>();

キャッシュされたプロパティを取得するか、ディクショナリに新規を追加してそれらを単純に使用するよりも

foreach (var prop in propList)
{
        var value = prop.GetValue(source, null);   
        prop.SetValue(copyInstance, value, null);
}

別の答えで私の投稿の完全なコードチェック

https://stackoverflow.com/a/34365709/4711853


2
呼び出しprop.GetValue(...)はまだ反映されており、キャッシュできません。式ツリーではコンパイルされたので、非常に高速です
Tseng

4

この質問に対する回答のほとんどすべてが不十分であるか、私の状況では明らかに機能しないため、リフレクションで完全に実装され、ここですべてのニーズを解決するAnyCloneを作成しました。複雑な構造の複雑なシナリオでシリアル化を機能させることができなかったためIClonable、理想的とは言えません。実際、必要もありません。

標準では、属性が使用してサポートされ、無視します[IgnoreDataMember][NonSerialized]。複雑なコレクション、セッターなしのプロパティ、読み取り専用フィールドなどをサポートします。

私が行ったのと同じ問題に遭遇した他の誰かの助けになることを願っています。


4

免責事項:私は言及されたパッケージの作者です。

2019年のこの質問に対するトップの回答が、まだシリアル化またはリフレクションを使用していることに驚きました。

シリアル化は制限があり(属性、特定のコンストラクターなどが必要)、非常に遅い

BinaryFormatterSerializable属性が必要ですJsonConverterパラメータなしのコンストラクタまたは属性を必要とし、ハンドルどちらも非常によくフィールドだけやインターフェイスを読み、両方が必要以上に10-30x遅いです。

式ツリー

代わりに、式ツリーまたはReflection.Emitを使用できますを使用して複製コードを1回だけ生成し、その後、遅いリフレクションまたはシリアル化の代わりにそのコンパイル済みコードを使用できます。

私自身で問題に遭遇し、満足のいく解決策が見当たらないので、私はそれだけですべてのタイプで機能し、カスタム作成コードとほぼ同じ速さのパッケージを作成することにしました。

GitHubでプロジェクトを見つけることができます:https : //github.com/marcelltoth/ObjectCloner

使用法

NuGetからインストールできます。ObjectClonerパッケージを取得して、次のように使用します。

var clone = ObjectCloner.DeepClone(original);

または、拡張機能でオブジェクトタイプを汚染することを気にしない場合はObjectCloner.Extensions、次のように記述してください。

var clone = original.DeepClone();

パフォーマンス

クラス階層のクローン作成の単純なベンチマークは、Reflectionを使用する場合よりも約3倍、Newtonsoft.Jsonのシリアル化よりも約12倍、強く推奨されてBinaryFormatterいるよりも約36倍速いパフォーマンスを示しました。

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