質問

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

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

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

この機能が必要になることはあまりないので、必要になったときは、新しいオブジェクトを作成して各プロパティを個別にコピーすることに頼っていましたが、もっと良い、またはよりエレガントな処理方法があるのではないかという気持ちがいつも残ります。状況。

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

役に立ちましたか?

解決

標準的な方法では、 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.", "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);
}

他のヒント

私は、ほとんどがプリミティブとリストからなる非常に単純なオブジェクトのクローンを作成したいと考えていました。オブジェクトがすぐに 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);
    }
}

使用しない理由 Iローン可能ない 汎用インターフェイスがないからです。 使わない理由は曖昧だから. 。浅いコピーを取得しているのか深いコピーを取得しているのかは明らかではありません。それは実装者次第です。

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

私が普段やっているのは、単純に Copy 私が望んでいることを正確に実行するメソッド。

ここにリンクされている多くのオプションと、この問題に対する考えられる解決策についてよく読んだ後、私は次のように考えています。 すべてのオプションは次の場所に非常によくまとめられています イアン・Pさんのリンク (他のすべてのオプションはそれらのバリエーションです)、最良の解決策は次のように提供されます。 ペドロ77さんのリンク 質問のコメントに。

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

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

何よりもまず、これらはすべて私たちのオプションです。

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

私が選ぶ理由 Iローン可能 (すなわち、手動)

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

彼の記事はすべて、次の 3 つのオブジェクトを使用して、ほとんどの場合に適用できる例を中心にしています。 , そして . 。私たちは、同じ都市を持つ独自の脳を持つ人間のクローンを作りたいと考えています。上記の他の方法で発生する可能性のあるすべての問題をイメージするか、記事を読むことができます。

これは彼の結論を少し修正したものです。

指定してオブジェクトをコピーする New クラス名が後に続くと、拡張不可能なコードが生成されることがよくあります。これを実現するには、プロトタイプ パターンのアプリケーションであるクローンを使用する方が良い方法です。ただし、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

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

私はクローンよりコピー コンストラクターの方が好きです。意図がより明確になります。

すべてのパブリック プロパティをコピーする簡単な拡張メソッド。あらゆるオブジェクトに対応し、 ではない クラスが次であることを要求する [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 ) } );
        }
    };
}

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

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

それの使い方?

自分で書く代わりに 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)およびDelegates(アクション、FUNCなどを含む)
  • Null可能
  • T[] 配列
  • カスタム クラスと構造体 (ジェネリック クラスと構造体を含む)。

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

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

どのくらい速いですか?

メンバー情報を収集する前に 1 回だけ収集する必要があるため、この解決策はリフレクションよりも高速です。 GetClone<T> 指定された型に対して初めて使用されます 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>?

すでに次のようなサードパーティのアプリケーションを使用している場合 バリューインジェクター または オートマッパー, 、次のようなことができます。

MyObject oldObj; // The existing object to clone

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

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

見る CodePlex の valueinjecter ディープ クローン ソリューション.

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

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

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

これを見てください 開発者コーナーの記事 さらにいくつかのオプションがあります (Ian の功績です)。

最善の方法は、 拡張メソッド のように

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

そして、それをソリューション内の任意の場所で使用します。

var copy = anyObject.DeepClone();

次の 3 つの実装が可能です。

  1. 連載別 (最短のコード)
  2. 反射によって - 5倍高速
  3. 式ツリーによる - 20倍高速

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

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

乾杯。

未知の型への真のクローン作成が必要な場合は、以下を参照してください。ファストクローン.

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

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

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

物事をシンプルにして使用する オートマッパー 他の人が述べたように、これはあるオブジェクトを別のオブジェクトにマップするための単純な小さなライブラリです...オブジェクトを同じタイプの別のオブジェクトにコピーするには、次の 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;
}

拡張メソッドを使用すると、3 行が 1 行になります。

MyType copy = source.Copy();

を克服するためにこれを思いつきました 。ネット 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;
}

これを行うワンライナーを考え出そうとしましたが、匿名メソッドブロック内で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();
        }
    }
}

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

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

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

他の方法よりも 10 倍高速

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

  • シリアル化/逆シリアル化を伴うものよりも 10 倍高速です。
  • .NET が可能な理論上の最大速度にかなり近い速度です。

そしてその方法は…

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

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

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

  • ネストされた構造体のネストされた MemberwiseClone の場合は 1.08 秒
  • ネストされたクラスのネストされた MemberwiseClone の場合は 4.77 秒
  • シリアル化/逆シリアル化に 39.93 秒

クラスで Nested 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();
}

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

値の型と参照の種類

オブジェクトのクローン作成に関しては、「構造体「と」クラス":

  • 「」をお持ちの場合は、構造体"、 それは 値の型 したがって、それをコピーするだけで、内容が複製されます (ただし、この投稿のテクニックを使用しない限り、浅い複製しか作成されません)。
  • 「」をお持ちの場合は、クラス"、 それは 参照型, したがって、これをコピーする場合は、そのポインタをコピーするだけです。真のクローンを作成するには、より創造的になり、 値型と参照型の違い これにより、元のオブジェクトの別のコピーがメモリ内に作成されます。

見る 値型と参照型の違い.

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

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

多くのスレッドを他の多くのスレッドから分離するのに非常に役立ちます

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

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

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

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

アップデート

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

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

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

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

ディープコピーの実装は次のとおりです。

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

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

https://github.com/kalisohn/CloneBehave

nuget パッケージとしても使用できます。https://www.nuget.org/packages/Clone.Behave/1.0.0

例えば:次のコードはアドレスをディープクローンしますが、_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

この方法で私の問題は解決されました。

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

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

他にもコピーするものがある場合は追加してください

コードジェネレーター

私たちは、シリアル化から手動実装、リフレクションに至るまで、多くのアイデアを見てきました。私は、 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 チェックが含まれていますが、理解を容易にするために省略しました。

ここでは、シリアル化/逆シリアル化を中継せずにうまく機能した、迅速かつ簡単なソリューションを紹介します。

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

次の手順を実行します:

  • を定義します ISelf<T> 読み取り専用で Self 返されるプロパティ T, 、 そして ICloneable<out T>, 、から派生します ISelf<T> そしてメソッドが含まれています T Clone().
  • 次に、 CloneBase を実装する型 protected virtual generic VirtualClone 鋳造 MemberwiseClone 渡された型に。
  • 各派生型は実装する必要があります VirtualClone 基本の clone メソッドを呼び出して、親の VirtualClone メソッドがまだ処理していない派生型の側面を適切に複製するために必要なことをすべて実行します。

継承の汎用性を最大限に高めるには、パブリック クローン作成機能を公開するクラスを次のようにする必要があります。 sealed, ですが、クローン作成がないことを除けば同一の基本クラスから派生しています。明示的にクローン可能なタイプの変数を渡すのではなく、次のタイプのパラメータを受け取ります。 ICloneable<theNonCloneableType>. 。これにより、クローン化可能な派生物を期待するルーチンが可能になります。 Foo クローン化可能な派生物を操作するには DerivedFoo, 、ただし、クローン不可能な派生物の作成も許可します。 Foo.

これを試してみるとよいと思います。

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

「[Serializable]」と「[DataContract]」の両方で動作する、受け入れられた回答のバージョンを作成しました。書いてからしばらく経ちましたが、私の記憶が正しければ、[DataContract] には別のシリアライザーが必要でした。

必要 システム、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;
    }

} 

クラス オブジェクトのクローンを作成するには、Object.MemberwiseClone メソッドを使用できます。

この関数をクラスに追加するだけです。

public class yourClass
{
    // ...
    // ...

    public yourClass DeepCopy()
    {
        yourClass othercopy = (yourClass)this.MemberwiseClone();
        return othercopy;
    }
}

次に、ディープ独立コピーを実行するには、DeepCopy メソッドを呼び出すだけです。

yourClass newLine = oldLine.DeepCopy();

お役に立てれば。

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

適切にキャッシュすれば、4.6 秒で 1000000 個のオブジェクトのディープ クローンが作成されます (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

オブジェクトツリーがシリアル化可能な場合は、次のようなものを使用することもできます

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

このソリューションは非常に簡単ですが、他のソリューションほどパフォーマンスが優れていないことに注意してください。

また、クラスが大きくなっても、複製されたフィールドだけが残り、シリアル化されるようにしてください。

ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top