2473

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

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

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

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

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

4

54 に答える 54

1849

1 つのアプローチはICloneableインターフェイスを実装することです (ここで説明されているので、逆戻りはしません) が、これは私が少し前にThe Code Projectで見つけてコードに組み込んだ、優れたディープ クローン オブジェクト コピーです。他の場所で述べたように、オブジェクトをシリアライズ可能にする必要があります。

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 via serialization.
    /// </summary>
    /// <typeparam name="T">The type of object being copied.</typeparam>
    /// <param name="source">The object instance to copy.</param>
    /// <returns>A deep copy of the 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 (ReferenceEquals(source, null)) return default;

        using var Stream stream = new MemoryStream();
        IFormatter formatter = new BinaryFormatter();
        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 日) 私はこれ再訪したいと思いました。( NB @atconway はコメントで、プライベート メンバーは JSON メソッドを使用して複製されないことを指摘しています)

/// <summary>
/// Perform a deep Copy of the object, using Json as a serialization 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 (ReferenceEquals(source, null)) return default;

    // 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);
}
于 2008-09-17T00:18:28.830 に答える
190

ICloneableを使用しない理由は、汎用インターフェイスがないからではありません。 使用しない理由は、あいまいだからです。浅いコピーを取得しているか、深いコピーを取得しているかは明確ではありません。それは実装者次第です。

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

私が通常行っていることは、Copyまさに私が望むことを行うメソッドを作成することです。

于 2008-09-17T01:12:18.327 に答える
146

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

したがって、これら2つのリファレンスの関連部分をここにコピーします。そうすれば、次のことができます。

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

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

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

ICloneableを選択する理由(つまり手動で)

Venkat Subramaniam氏(ここに冗長リンク)は、その理由を詳細に説明しています

彼のすべての記事は、 PersonBrainCityの3つのオブジェクトを使用して、ほとんどの場合に適用できるようにする例を囲んでいます。私たちは、自分の頭脳を持ちながら同じ都市を持つ人のクローンを作りたいと思っています。上記の他の方法のいずれかがもたらす可能性のあるすべての問題を想像するか、記事を読むことができます。

これは彼の結論の私のわずかに修正されたバージョンです:

クラス名の後に指定してオブジェクトをコピーするNewと、拡張できないコードが生成されることがよくあります。プロトタイプパターンのアプリケーションであるcloneを使用することは、これを実現するためのより良い方法です。ただし、C#(およびJava)で提供されているクローンを使用することも非常に問題になる可能性があります。保護された(非公開の)コピーコンストラクターを提供し、それを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

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

于 2012-09-26T20:18:05.100 に答える
95

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

于 2008-09-17T00:13:01.947 に答える
46

すべてのパブリック プロパティをコピーする単純な拡張メソッド。任意のオブジェクトに対して機能し、クラスが である必要はありません[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 ) } );
        }
    };
}
于 2011-03-16T11:38:17.713 に答える
37

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

MyObject oldObj; // The existing object to clone

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

このメソッドを使用すると、オブジェクトに実装ISerializableする必要はありません。ICloneableこれは MVC/MVVM パターンに共通するものなので、このような単純なツールが作成されています。

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

于 2012-10-15T17:55:40.633 に答える
37

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

それの使い方?

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

var newInstance = source.GetClone();

列挙型sourcenewInstance使用して、何からコピーするかを選択できます。CloningFlags

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

クローンできるものは何ですか?

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

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

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

どれくらい速いですか?

GetClone<T>メンバー情報は、指定された type に対して初めて使用される前に一度だけ収集する必要があるため、ソリューションはリフレクションよりも高速Tです。

また、同じタイプのインスタンスを結合して複数のクローンを作成すると、シリアライゼーション ベースのソリューションよりも高速になります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>か?

于 2013-12-24T22:56:12.867 に答える
35

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);
    }
}
于 2009-12-02T17:39:54.037 に答える
26

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

ICloneable を使用したクローン作成の詳細については、この記事を参照してください。

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

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

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

乾杯。

于 2008-09-17T00:11:54.290 に答える
20

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

未知の型への真の複製が必要な場合は、 fastcloneをご覧ください。

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

つまり、階層内の同じオブジェクトを複数回参照すると、クローンも参照される単一のインスタンスを持つことになります。

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

于 2015-02-16T11:30:51.103 に答える
13

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

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

于 2008-09-17T00:09:02.177 に答える
12

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

私はこれを使用します:

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

これを行うワンライナーを考え出そうとしましたが、匿名メソッドブロック内で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();
        }
    }
}
于 2009-09-30T09:51:49.673 に答える
11

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

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

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

他の方法よりも 10 倍高速

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

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

そして方法は...

究極の速度を得るには、Nested 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;
    }
}

次に、main からデモを呼び出します。

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

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

値型と参照型

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

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

値型と参照型の違いを参照してください。

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

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

多くのスレッドを他の多くのスレッドから分離するのに非常に便利

このコードの優れた使用例の 1 つは、ネストされたクラスまたは構造体のクローンをキューにフィードして、生産者/消費者パターンを実装することです。

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

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

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

アップデート

どうやら、ExpressMapper は、上記のようなハンド コーディングよりも高速ではないにしても、同じくらい高速です。それらがプロファイラーとどのように比較されるかを確認する必要があるかもしれません。

于 2015-07-04T17:24:47.000 に答える
9

さまざまなプロジェクトですべての要件を満たすクローナーを見つけることができなかったため、クローナーの要件を満たすようにコードを適応させるのではなく、さまざまなコード構造に構成および適応できるディープ クローナーを作成しました。これは、複製されるコードに注釈を追加するか、コードをそのままにしてデフォルトの動作にすることで実現されます。リフレクション、型キャッシュを使用し、 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
于 2016-01-25T17:45:56.083 に答える
9

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

于 2010-10-19T13:01:19.507 に答える
9

ディープ コピーの実装を次に示します。

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;
}
于 2011-09-06T07:38:32.540 に答える
8

コードジェネレーター

シリアライゼーションからリフレクションまで、手動実装よりも多くのアイデアを見てきましたが、CGbR Code Generatorを使用したまったく異なるアプローチを提案したいと思います。generate clone メソッドはメモリと 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 チェックがありますが、理解を深めるために省略しました。

于 2016-06-09T20:56:50.757 に答える
7

私はそのような 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));
        }
    }

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

于 2015-03-06T13:48:06.413 に答える
7

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

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

于 2016-04-12T13:43:36.730 に答える
7

ここでは、シリアライゼーション/デシリアライゼーションを中継することなく、迅速かつ簡単に解決できるソリューションを紹介します。

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();
}
于 2016-07-29T13:44:32.687 に答える
6

次の手順を実行します:

  • メソッドから派生し、メソッド を含む 、およびを返すISelf<T>読み取り専用プロパティを持つ を定義します。SelfTICloneable<out T>ISelf<T>T Clone()
  • 次に、渡された型への キャストをCloneBase実装する型を定義します。protected virtual generic VirtualCloneMemberwiseClone
  • 各派生型は、基本クローン メソッドを呼び出して実装VirtualCloneし、親の VirtualClone メソッドがまだ処理していない派生型の側面を適切に複製するために必要なことは何でも実行する必要があります。

継承の多様性を最大限に高めるには、パブリック クローニング機能を公開するクラスは である必要がありますがsealed、クローニングがないことを除いて他の点では同一の基本クラスから派生します。明示的な複製可能な型の変数を渡すのではなく、 type のパラメーターを取りますICloneable<theNonCloneableType>Fooこれにより、 の複製可能な派生物が の複製可能な派生物で動作することを期待するルーチンが許可さ れますが、 の複製不可能な派生DerivedFoo物の作成も許可されFooます。

于 2011-12-07T21:24:25.410 に答える
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

于 2015-12-19T08:17:57.830 に答える
4

これを試すことができると思います。

MyObject myObj = GetMyObj(); // Create and fill a new object
MyObject newObj = new MyObject(myObj); //DeepClone it
于 2016-08-19T16:47:21.490 に答える
3

オブジェクトツリーがシリアライズ可能な場合、次のようなものも使用できます

static public MyClass Clone(MyClass myClass)
{
    MyClass clone;
    XmlSerializer ser = new XmlSerializer(typeof(MyClass), _xmlAttributeOverrides);
    using (var ms = new MemoryStream())
    {
        ser.Serialize(ms, myClass);
        ms.Position = 0;
        clone = (MyClass)ser.Deserialize(ms);
    }
    return clone;
}

このソリューションは非常に簡単ですが、他のソリューションほどパフォーマンスが高くないことが通知されます。

また、クラスが大きくなった場合でも、クローンされたフィールドのみが存在し、シリアル化されることを確認してください。

于 2015-04-20T13:51:42.553 に答える
1

基本的に自動コピーコンストラクターを呼び出す必要があるメソッド内で再キャストするのはどうですか

T t = new T();
T t2 = (T)t;  //eh something like that

        List<myclass> cloneum;
        public void SomeFuncB(ref List<myclass> _mylist)
        {
            cloneum = new List<myclass>();
            cloneum = (List < myclass >) _mylist;
            cloneum.Add(new myclass(3));
            _mylist = new List<myclass>();
        }

私にはうまくいくようです

于 2014-04-13T12:53:53.060 に答える
1

Marc Gravells protobuf-net をシリアライザーとして使用する場合、受け入れられた回答には若干の変更が必要です。これは、コピーするオブジェクトに属性が付けられ[Serializable]ないため、シリアライズできず、Clone メソッドが例外をスローするためです。
protobuf-net で動作するように変更しました。

public static T Clone<T>(this T source)
{
    if(Attribute.GetCustomAttribute(typeof(T), typeof(ProtoBuf.ProtoContractAttribute))
           == null)
    {
        throw new ArgumentException("Type has no ProtoContract!", "source");
    }

    if(Object.ReferenceEquals(source, null))
    {
        return default(T);
    }

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

これは、[ProtoContract]属性の存在をチェックし、protobufs 独自のフォーマッターを使用してオブジェクトをシリアル化します。

于 2015-08-22T11:36:20.867 に答える