webdevqa.jp.net

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

私は何かをしたいのですが。

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

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

この機能はあまり必要ないので、必要に応じて新しいオブジェクトを作成して各プロパティを個別にコピーすることにしました。状況。

変更を元のオブジェクトに反映させずに複製されたオブジェクトを変更できるように、オブジェクトを複製またはディープコピーする方法を教えてください。

2028
NakedBrunch

標準的なプラクティスは ICloneable インターフェイス( ここ で説明されているため、逆流しません)を実装することですが、ここで見つけたNice deep cloneオブジェクトコピー機は コードプロジェクト 少し前に、それを私たちのものに組み込みました。

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

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();になります。

EDIT(2015年1月10日)これを再考すると思いますが、最近これを行うために(Newtonsoft)Jsonを使用し始めたので、 はずです より軽く、[Serializable]タグのオーバーヘッドを回避します。 (NB@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);
}
1628
johnc

私は主にプリミティブとリストの非常に単純なオブジェクトのクローンを欲していました。あなたのオブジェクトがそのまま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);
    }
}
241
craastad

ICloneable を使用しない理由は、 not ですが、それには汎用インターフェースがないからです。 使わないのは曖昧だから 。あなたが浅いコピーを取っているのか、それとも深いコピーを取っているのかは明らかではありません。それは実装者次第です。

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

私が普段やっていることは、Copyメソッドを単に作ることです。

162
Ryan Lundy

ここにリンクされている多くのオプションとこの問題の可能な解決策について多くを読んだ後、 すべてのオプションはIan P 's link (all他のオプションはそれらのバリエーションです)、最良の解決策は Pedro77 's link の質問コメントで提供されます。

そこで、ここでこれらの2つの参照の関連部分をコピーします。そうすれば、次のことができます。

Cシャープでオブジェクトを複製するための最善の方法!

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

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

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

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

彼のすべての記事は、3つのオブジェクトを使用して、ほとんどの場合に適用できるようにしようとする例を取り巻いています:PersonBrainおよびCity。私たちは、同じ都市でありながら独自の頭脳を持つ人物をクローンしたいと考えています。上記の他の方法のいずれかが記事をもたらすまたは読むことができるすべての問題を想像することができます。

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

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 [email protected]
This is person with [email protected]
SkilledPerson: This is person with [email protected]
SkilledPerson: This is person with [email protected]

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

106
cregox

私はクローンよりコピーコンストラクタを好む。意図が明確になりました。

77
Nick

すべてのパブリックプロパティをコピーする簡単な拡張方法任意のオブジェクトに対して機能し、 は必要ありません - クラスを[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 ) } );
        }
    };
}
38

私はSilverlightでICloneableを使用するのに問題がありましたが、Seralizationのアイデアが気に入ったので、XMLをSeralizeできます。

static public class SerializeHelper
{
    //Michael White, Holly Springs Consulting, 2009
    //[email protected]
    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);
    }
}
30
Michael White

CloneExtensionslibraryprojectを作成しました。 Expression Treeランタイムコードのコンパイルによって生成された単純な代入操作を使用して、高速でディープクローンを実行します。

使い方は?

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

var newInstance = source.GetClone();

sourceenumを使用して、newInstancename__からCloningFlagsname__に何をコピーするかを選択できます。

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

クローンできるもの

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

以下のクラス/構造体メンバは内部的に複製されています。

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

どれくらい速いですか?

与えられた型Tname__に対してGetClone<T>が初めて使用されるまでは、メンバー情報を一度だけ収集すればよいため、解決策はリフレクションよりも高速です。

クローンを作成して同じ型のインスタンスを結合する場合は、シリアル化ベースのソリューションよりも高速です(Tname__)。

そしてもっと...

documentation で生成された式についてもっと読んでください。

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

List<int>のためにあなたがあなた自身のClonename__メソッドを書く方法と全く同じではないですか?

27
MarcinJuraszek

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

MyObject oldObj; // The existing object to clone

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

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

CodePlexのvalueinjecterディープクローニングソリューションを参照してください

26
Michael Cox

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

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

long という答えは「それが依存する」ということです。他の人が述べたように、ICloneableは総称ではサポートされておらず、循環クラス参照については特別な考慮が必要であり、.NET Frameworkでは "間違い" として実際に見られています。直列化の方法は、オブジェクトが直列化可能かどうかによって異なります。オブジェクトは直列化できないため、制御できません。コミュニティではまだ「ベスト」プラクティスである多くの議論があります。実際には、ICloneableがもともとあると解釈されていたような、すべての状況に対応するすべてのベストプラクティスに適したものではありません。

その他のオプションについては、this Developer's Cornerの記事 を参照してください(Ianの功績による)。

20
Zach Burlingame

最善の方法は拡張メソッドを実装することです。

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

そしてそれを次のようにして解の中のどこにでも使ってください。

var copy = anyObject.DeepClone();

次の3つの実装があります。

  1. シリアライゼーションによる (最短のコード)
  2. 反射による - 5倍速
  3. 表現木による - 20倍速

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

19
frakon
  1. 基本的にはICloneableインターフェースを実装してからオブジェクト構造のコピーを実現する必要があります。
  2. それがすべてのメンバーの深いコピーであるならば、あなたはすべての子供たちが同様にクローンを作ることができることを保証する必要があります(あなたが選んだ解決策には関係ありません)。
  3. たとえば、ほとんどのフレームワークでセッションにアタッチされているオブジェクトは1つしか許可されておらず、このオブジェクトのクローンを作成してはいけない、または可能であれば注意が必要な場合など。これらのオブジェクトのセッションアタッチについて.

乾杯。

16
dimarzionist

未知の型への本当のクローン作成が欲しいなら、 fastclone を見てください。

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

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

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

15
Michael Sander

物事をシンプルに保ち、 AutoMapper を使ってください。他の人が述べたように、あるオブジェクトを別のオブジェクトにマッピングするのは簡単な小さなライブラリです。

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();
11
Stacked

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

これを行う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)シリアライゼーションよりもはるかに速いですこのページで).

ifディープコピーにNested MemberwiseCloneを使用する場合は、クラス内のネストされた各レベルにShallowCopyを手動で実装する必要があります。)これは簡単です。合計数行だけで、下のデモコードを見てください。

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

  • 入れ子になった構造体の入れ子になったMemberwiseCloneで1.08秒
  • ネストしたクラスのNested 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();
}

繰り返しますが、ifディープコピーにNested MemberwiseCloneを使用する場合は、クラス内のネストされた各レベルにShallowCopyを手動で実装する必要があります。 clone。これは簡単です:合計でほんの数行で、上のデモコードを見てください。

値型と参照型

オブジェクトのクローン作成に関しては、 "struct"と "class")の間には大きな違いがあります。

  • あなたが "struct"を持っているなら、それはvalue typeです)あなたはそれをコピーすることができます、そして内容はクローンされます(しかし、この記事のテクニックを使わない限りそれは浅いクローンを作るでしょう).
  • "class"がある場合、それは参照型です。コピーする場合は、ポインタをコピーするだけです。真のクローンを作成するには、もっとクリエイティブになる必要があります。そして、 値型と参照型の違い を使用して、メモリ内に元のオブジェクトの別のコピーを作成します。

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

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

  • オブジェクトを誤って複製すると、ピンを突き止めるのが非常に困難なバグにつながる可能性があります。プロダクションコードでは、オブジェクトが正しくクローンされていることと、それを参照することによって破損していないことを二重チェックするチェックサムを実装する傾向があります。このチェックサムは、解放モードではオフにすることができます。
  • 私はこの方法が非常に便利だと思います。多くの場合、オブジェクト全体を複製するのではなく、オブジェクトの一部だけを複製したいのです。

他の多くのスレッドから多くのスレッドを切り離すために本当に役立ちます

このコードの優れたユースケースの1つは、プロデューサ/コンシューマパターンを実装するために、ネストしたクラスまたは構造体のクローンをキューに入れることです。

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

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

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

更新

明らかに、ExpressMapperは上記のようなハンドコーディングよりも速くはないにしても同じくらい速いです。私は彼らがプロファイラーと比較する方法を見なければならないかもしれません。

7
Contango

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

7
xr280xr

さまざまなプロジェクトで自分のすべての要件を満たすクローンを見つけることができなかったので、自分のコードをクローン要件に合うように調整するのではなく、さまざまなコード構造に合わせて構成および適応できる深いクローンを作成しました。これは、複製されるコードにアノテーションを追加することによって達成されるか、またはデフォルトの動作を維持するためにコードをそのままにします。リフレクション、タイプキャッシュを使い、 fastflect に基づいています。クローン作成プロセスは、膨大な量のデータと高いオブジェクト階層(他のリフレクション/シリアライゼーションベースのアルゴリズムと比較して)に対して非常に高速です。

https://github.com/kalisohn/CloneBehave

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

たとえば、次のコードはAddressを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
kalisohn

一般に、あなたはICloneableインターフェースを実装し、Cloneを自分で実装します。 C#オブジェクトには、浅いコピーを実行するMemberwiseCloneメソッドが組み込まれています。

ディープコピーの場合は、自動的に実行する方法を知ることはできません。

7
HappyDude

これがディープコピーの実装です。

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

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

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
JerryGoyal

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

もっとコピーするものがあればそれらを追加してください

5
LuckyLikey

コードジェネレーター

手作業による実装からリフレクションまでのシリアライゼーションからたくさんのアイデアを見てきました、そして CGbR Code Generator を使って全く異なるアプローチを提案したいです。クローン生成方法は、メモリと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チェックがありますが、理解を深めるために省略しました。

5
Toxantron

ここでは速くて簡単な解決策が私のためにシリアル化/デシリアライゼーションに頼らずにうまくいきました。

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
Daniele D.

私はあなたがこれを試すことができると思います。

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

次の手順を実行します:

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

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

4
supercat

私は、 '[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;
    }

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

この解決策は非常に簡単ですが、他の解決策ほど高性能ではないことに注意してください。

また、Classが大きくなっても、クローン化されたフィールドのみが残っていることを確認してください。これも直列化されます。

3
LuckyLikey

クラスオブジェクトを複製するには、Object.MemberwiseCloneメソッドを使います。

この関数をあなたのクラスに追加するだけです:

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

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

その後、ディープ独立コピーを実行するには、DeepCopyメソッドを呼び出します。

yourClass newLine = oldLine.DeepCopy();

お役に立てれば。

3
Chtiwi Malek

さて、この記事にはリフレクションを使った明白な例がいくつかありますが、それを適切にキャッシュし始めるまでは通常リフレクションは遅くなります。

正しくキャッシュした場合、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

3
Roma Borodov

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

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

私がそれが私がしたのと同じ問題に出くわした他の誰かに役立つことを願っています。

2
Michael Brown

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自身のフォーマッタを使ってオブジェクトをシリアル化します。

1
Basti M

"not ISerializable "タイプもサポートするC#拡張。

 public static class AppExtensions
 {                                                                      
       public static T DeepClone<T>(this T a)
       {
           using (var stream = new MemoryStream())
           {
               var serializer = new System.Xml.Serialization.XmlSerializer(typeof(T));

               serializer.Serialize(stream, a);
               stream.Position = 0;
               return (T)serializer.Deserialize(stream);
           }
       }                                                                    
 }

使用法

       var obj2 = obj1.DeepClone()
1
Sameera R.

特にクラス階層が重い場合は、IClonableインターフェイスにどれだけの労力を費やすことができるかは信じられません。また、MemberwiseCloneはどういうわけか奇妙に動作します - それは構造の通常のリスト型の種類でさえ正確に複製しません。

そしてもちろん、シリアル化のための最も興味深いジレンマは逆参照をシリアル化することです。親子関係があるクラス階層この場合、バイナリシリアライザが役に立つことを疑います。 (それは再帰的なループとスタックオーバーフローで終わります)。

私はどういうわけかここで提案された解決策が好き: どのようにあなたは.NETでオブジェクトのディープコピーをするのですか(特にC#)?

しかし - それはリストをサポートしていなかった、そのサポートを追加し、また再子育てを考慮に入れた。そのフィールドまたはプロパティに「parent」という名前を付けるようにしたルールのみをペアレント化する場合、DeepCloneでは無視されます。あなたは後方参照のためのあなた自身の規則を決めたいと思うかもしれません - ツリー階層のためにそれは "左/右"であるかもしれません、等...

これがテストコードを含む全体のコードスニペットです:

using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Text;

namespace TestDeepClone
{
    class Program
    {
        static void Main(string[] args)
        {
            A a = new A();
            a.name = "main_A";
            a.b_list.Add(new B(a) { name = "b1" });
            a.b_list.Add(new B(a) { name = "b2" });

            A a2 = (A)a.DeepClone();
            a2.name = "second_A";

            // Perform re-parenting manually after deep copy.
            foreach( var b in a2.b_list )
                b.parent = a2;


            Debug.WriteLine("ok");

        }
    }

    public class A
    {
        public String name = "one";
        public List<String> list = new List<string>();
        public List<String> null_list;
        public List<B> b_list = new List<B>();
        private int private_pleaseCopyMeAsWell = 5;

        public override string ToString()
        {
            return "A(" + name + ")";
        }
    }

    public class B
    {
        public B() { }
        public B(A _parent) { parent = _parent; }
        public A parent;
        public String name = "two";
    }


    public static class ReflectionEx
    {
        public static Type GetUnderlyingType(this MemberInfo member)
        {
            Type type;
            switch (member.MemberType)
            {
                case MemberTypes.Field:
                    type = ((FieldInfo)member).FieldType;
                    break;
                case MemberTypes.Property:
                    type = ((PropertyInfo)member).PropertyType;
                    break;
                case MemberTypes.Event:
                    type = ((EventInfo)member).EventHandlerType;
                    break;
                default:
                    throw new ArgumentException("member must be if type FieldInfo, PropertyInfo or EventInfo", "member");
            }
            return Nullable.GetUnderlyingType(type) ?? type;
        }

        /// <summary>
        /// Gets fields and properties into one array.
        /// Order of properties / fields will be preserved in order of appearance in class / struct. (MetadataToken is used for sorting such cases)
        /// </summary>
        /// <param name="type">Type from which to get</param>
        /// <returns>array of fields and properties</returns>
        public static MemberInfo[] GetFieldsAndProperties(this Type type)
        {
            List<MemberInfo> fps = new List<MemberInfo>();
            fps.AddRange(type.GetFields());
            fps.AddRange(type.GetProperties());
            fps = fps.OrderBy(x => x.MetadataToken).ToList();
            return fps.ToArray();
        }

        public static object GetValue(this MemberInfo member, object target)
        {
            if (member is PropertyInfo)
            {
                return (member as PropertyInfo).GetValue(target, null);
            }
            else if (member is FieldInfo)
            {
                return (member as FieldInfo).GetValue(target);
            }
            else
            {
                throw new Exception("member must be either PropertyInfo or FieldInfo");
            }
        }

        public static void SetValue(this MemberInfo member, object target, object value)
        {
            if (member is PropertyInfo)
            {
                (member as PropertyInfo).SetValue(target, value, null);
            }
            else if (member is FieldInfo)
            {
                (member as FieldInfo).SetValue(target, value);
            }
            else
            {
                throw new Exception("destinationMember must be either PropertyInfo or FieldInfo");
            }
        }

        /// <summary>
        /// Deep clones specific object.
        /// Analogue can be found here: https://stackoverflow.com/questions/129389/how-do-you-do-a-deep-copy-an-object-in-net-c-specifically
        /// This is now improved version (list support added)
        /// </summary>
        /// <param name="obj">object to be cloned</param>
        /// <returns>full copy of object.</returns>
        public static object DeepClone(this object obj)
        {
            if (obj == null)
                return null;

            Type type = obj.GetType();

            if (obj is IList)
            {
                IList list = ((IList)obj);
                IList newlist = (IList)Activator.CreateInstance(obj.GetType(), list.Count);

                foreach (object elem in list)
                    newlist.Add(DeepClone(elem));

                return newlist;
            } //if

            if (type.IsValueType || type == typeof(string))
            {
                return obj;
            }
            else if (type.IsArray)
            {
                Type elementType = Type.GetType(type.FullName.Replace("[]", string.Empty));
                var array = obj as Array;
                Array copied = Array.CreateInstance(elementType, array.Length);

                for (int i = 0; i < array.Length; i++)
                    copied.SetValue(DeepClone(array.GetValue(i)), i);

                return Convert.ChangeType(copied, obj.GetType());
            }
            else if (type.IsClass)
            {
                object toret = Activator.CreateInstance(obj.GetType());

                MemberInfo[] fields = type.GetFieldsAndProperties();
                foreach (MemberInfo field in fields)
                {
                    // Don't clone parent back-reference classes. (Using special kind of naming 'parent' 
                    // to indicate child's parent class.
                    if (field.Name == "parent")
                    {
                        continue;
                    }

                    object fieldValue = field.GetValue(obj);

                    if (fieldValue == null)
                        continue;

                    field.SetValue(toret, DeepClone(fieldValue));
                }

                return toret;
            }
            else
            {
                // Don't know that type, don't know how to clone it.
                if (Debugger.IsAttached)
                    Debugger.Break();

                return null;
            }
        } //DeepClone
    }

}
1
TarmoPikaro

まだ別のJSON.NETの答え。このバージョンはISerializableを実装していないクラスで動作します。

public static class Cloner
{
    public static T Clone<T>(T source)
    {
        if (ReferenceEquals(source, null))
            return default(T);

        var settings = new JsonSerializerSettings { ContractResolver = new ContractResolver() };

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

    class ContractResolver : DefaultContractResolver
    {
        protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
        {
            var props = type.GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)
                .Select(p => base.CreateProperty(p, memberSerialization))
                .Union(type.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)
                    .Select(f => base.CreateProperty(f, memberSerialization)))
                .ToList();
            props.ForEach(p => { p.Writable = true; p.Readable = true; });
            return props;
        }
    }
}
1
Matthew Watson

マッパーはディープコピーを実行します。あなたのオブジェクトの各メンバは、新しいオブジェクトを作成し、そのすべての値を割り当てます。それは各非プリミティブ内部メンバーに再帰的に働きます。

私はあなたに最速の、現在活発に開発されているもののうちの1つを提案します。 UltraMapperをお勧めします https://github.com/maurosampietro/UltraMapper

Nugetパッケージ: https://www.nuget.org/packages/UltraMapper/ /

1
Mauro Sampietro

ディープクローニングとは、状態をコピーすることです。 .netの場合statefieldsを意味します。

階層があるとしましょう:

static class RandomHelper
{
    private static readonly Random random = new Random();

    public static int Next(int maxValue) => random.Next(maxValue);
}

class A
{
    private readonly int random = RandomHelper.Next(100);

    public override string ToString() => $"{typeof(A).Name}.{nameof(random)} = {random}";
}

class B : A
{
    private readonly int random = RandomHelper.Next(100);

    public override string ToString() => $"{typeof(B).Name}.{nameof(random)} = {random} {base.ToString()}";
}

class C : B
{
    private readonly int random = RandomHelper.Next(100);

    public override string ToString() => $"{typeof(C).Name}.{nameof(random)} = {random} {base.ToString()}";
}

クローンを作成できます:

static class DeepCloneExtension
{
    // consider instance fields, both public and non-public
    private static readonly BindingFlags bindingFlags =
        BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance;

    public static T DeepClone<T>(this T obj) where T : new()
    {
        var type = obj.GetType();
        var result = (T)Activator.CreateInstance(type);

        do
            // copy all fields
            foreach (var field in type.GetFields(bindingFlags))
                field.SetValue(result, field.GetValue(obj));
        // for every level of hierarchy
        while ((type = type.BaseType) != typeof(object));

        return result;
    }
}

Demo1

Console.WriteLine(new C());
Console.WriteLine(new C());

var c = new C();
Console.WriteLine($"{Environment.NewLine}Image: {c}{Environment.NewLine}");

Console.WriteLine(new C());
Console.WriteLine(new C());

Console.WriteLine($"{Environment.NewLine}Clone: {c.DeepClone()}{Environment.NewLine}");

Console.WriteLine(new C());
Console.WriteLine(new C());

結果:

C.random = 92 B.random = 66 A.random = 71
C.random = 36 B.random = 64 A.random = 17

Image: C.random = 96 B.random = 18 A.random = 46

C.random = 60 B.random = 7 A.random = 37
C.random = 78 B.random = 11 A.random = 18

Clone: C.random = 96 B.random = 18 A.random = 46

C.random = 33 B.random = 63 A.random = 38
C.random = 4 B.random = 5 A.random = 79

すべての新しいオブジェクトにはrandomフィールドのランダム値がありますが、cloneimageと完全に一致します。

Demo2

class D
{
    public event EventHandler Event;
    public void RaiseEvent() => Event?.Invoke(this, EventArgs.Empty);
}

// ...

var image = new D();
Console.WriteLine($"Created obj #{image.GetHashCode()}");

image.Event += (sender, e) => Console.WriteLine($"Event from obj #{sender.GetHashCode()}");
Console.WriteLine($"Subscribed to event of obj #{image.GetHashCode()}");

image.RaiseEvent();
image.RaiseEvent();

var clone = image.DeepClone();
Console.WriteLine($"obj #{image.GetHashCode()} cloned to obj #{clone.GetHashCode()}");

clone.RaiseEvent();
image.RaiseEvent();

結果:

Created obj #46104728
Subscribed to event of obj #46104728
Event from obj #46104728
Event from obj #46104728
obj #46104728 cloned to obj #12289376
Event from obj #12289376
Event from obj #46104728

イベントバッキングフィールドもコピーされ、クライアントもクローンのイベントにサブスクライブされることに注意してください。

1
Ted Mucuzany

一般的なアプローチはすべて技術的に有効ですが、実際のディープコピーは実際にはほとんど必要ないため、自分でメモを追加したいと思いました。実際のビジネスアプリケーションで一般的なディープコピーを使用することは強くお勧めします。オブジェクトがコピーされてから明示的に変更されている場所では、見逃しがちです。

実際の状況では、データアクセスフレームワークとの連携だけでなく、実際にはコピーされたビジネスオブジェクトが100%同じになることはめったにないため、コピープロセスをできるだけ細かく制御したい場合もあります。オブジェクト参照を識別するためにORMによって使用されるreferenceIdの例を考えると、フルディープコピーでもこのidがコピーされるため、データストアに送信するとすぐにオブジェクトは異なります。とにかくコピーした後でこのプロパティを手動で変更しなければならず、オブジェクトが変更された場合は、一般的なディープコピーを使用するすべての場所でそれを調整する必要があります。

ICloneableを使って@cregox回答を拡張すると、実際には何がディープコピーになりますか?それは、元のオブジェクトと同じですが、別のメモリスペースを占有する、ヒープ上に新しく割り当てられたオブジェクトです。一般的なクローン機能を使用するのではなく、単に新しいオブジェクトを作成しないのですか。

私は自分のドメインオブジェクトに静的ファクトリメソッドの考え方を個人的に使用しています。

例:

    public class Client
    {
        public string Name { get; set; }

        protected Client()
        {
        }

        public static Client Clone(Client copiedClient)
        {
            return new Client
            {
                Name = copiedClient.Name
            };
        }
    }

    public class Shop
    {
        public string Name { get; set; }

        public string Address { get; set; }

        public ICollection<Client> Clients { get; set; }

        public static Shop Clone(Shop copiedShop, string newAddress, ICollection<Client> clients)
        {
            var copiedClients = new List<Client>();
            foreach (var client in copiedShop.Clients)
            {
                copiedClients.Add(Client.Clone(client));
            }

            return new Shop
            {
                Name = copiedShop.Name,
                Address = newAddress,
                Clients = copiedClients
            };
        }
    }

コピープロセスを完全に制御しながら、オブジェクトのインスタンス化を構成する方法を誰かが探しているのであれば、それは私が個人的に非常に成功している解決策です。保護されたコンストラクタもそれを可能にします、他の開発者はオブジェクトの内部に構築ロジックをカプセル化するオブジェクトインスタンス化のきちんとした単一点を与えるファクトリメソッドを使用することを強いられます。メソッドをオーバーロードして、必要に応じて異なる場所にいくつかのクローンロジックを作成することもできます。

0

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

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

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

BinaryFormatterにはSerializable属性が必要です、JsonConverterにはパラメーターなしのコンストラクターまたは属性が必要です。読み取り専用フィールドまたはインターフェースをうまく処理できず、両方とも必要以上に10-30倍遅いです。

式ツリー

代わりに、Expression Treesまたは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倍速いパフォーマンスを示しました。

0
Marcell Toth

System.Text.Jsonを使用:

https://devblogs.Microsoft.com/dotnet/try-the-new-system-text-json-apis/

public static T DeepCopy<T>(this T source)
{
    return source == null ? default : JsonSerializer.Parse<T>(JsonSerializer.ToString(source));
}

新しいAPIはSpan<T>を使用しています。これは高速であるはずであり、いくつかのベンチマークを行うのは良いことです。

注:デフォルトでコレクション値を置き換えるため、Json.NETのようなObjectCreationHandling.Replaceは必要ありません。すべてが新しい公式APIに置き換えられるため、Json.NETについては忘れてください。

これがプライベートフィールドで機能するかどうかはわかりません。

0
Konrad

私はそれを行うための新しい方法を見つけましたそれはエミットです。

Emitを使ってILをアプリに追加して実行できます。しかし、私は自分の答えを書くためにこれを完璧にしたいのなら、それが良い方法だとは思わない。

エミットは 公式文書 および ガイド を見ることができます

あなたはコードを読むためにいくらかのILを学ぶべきです。プロパティをクラスにコピーできるコードを書きます。

public static class Clone
{        
    // ReSharper disable once InconsistentNaming
    public static void CloneObjectWithIL<T>(T source, T los)
    {
        //see http://lindexi.oschina.io/lindexi/post/C-%E4%BD%BF%E7%94%A8Emit%E6%B7%B1%E5%85%8B%E9%9A%86/
        if (CachedIl.ContainsKey(typeof(T)))
        {
            ((Action<T, T>) CachedIl[typeof(T)])(source, los);
            return;
        }
        var dynamicMethod = new DynamicMethod("Clone", null, new[] { typeof(T), typeof(T) });
        ILGenerator generator = dynamicMethod.GetILGenerator();

        foreach (var temp in typeof(T).GetProperties().Where(temp => temp.CanRead && temp.CanWrite))
        {
            //do not copy static that will except
            if (temp.GetAccessors(true)[0].IsStatic)
            {
                continue;
            }

            generator.Emit(OpCodes.Ldarg_1);// los
            generator.Emit(OpCodes.Ldarg_0);// s
            generator.Emit(OpCodes.Callvirt, temp.GetMethod);
            generator.Emit(OpCodes.Callvirt, temp.SetMethod);
        }
        generator.Emit(OpCodes.Ret);
        var clone = (Action<T, T>) dynamicMethod.CreateDelegate(typeof(Action<T, T>));
        CachedIl[typeof(T)] = clone;
        clone(source, los);
    }

    private static Dictionary<Type, Delegate> CachedIl { set; get; } = new Dictionary<Type, Delegate>();
}

コードはディープコピーにすることができますが、プロパティをコピーすることはできます。もしあなたがそれをディープコピーにしたいのであれば、ILのためにそれを変更することができます。

0
lindexi

クローニングを解決するための迅速、簡単、効果的なNugetパッケージ

すべての答えを読んだ後、私は誰もこの素晴らしいパッケージに言及しなかったことに驚きました

https://github.com/force-net/DeepCloner

READMEについて少し詳しく説明しますが、ここで私たちが仕事でそれを選んだ理由を示します。

免責事項-要件:

  • 。NET 4.0以降または.NET Standard 1.3(.NET Core)
  • 完全信頼許可セットまたはリフレクション許可(MemberAccess)が必要です
  • 深いコピーまたは浅いコピーが可能
  • ディープクローニングでは、すべてのオブジェクトグラフが維持されます。
  • 結果のクローン作成が非常に高速であるため、ランタイムでコード生成を使用します
  • 内部構造によってコピーされたオブジェクト、呼び出されたメソッドまたはアクターなし
  • 何らかの方法でクラスをマークする必要はありません(Serializable-attributeやインターフェイスの実装など)
  • クローン作成のオブジェクトタイプを指定する必要はありません。オブジェクトは、インターフェイスまたは抽象オブジェクトとしてキャストできます(たとえば、intの配列を抽象配列またはIEnumerableとして複製できます。nullでもエラーなしで複製できます)
  • クローンされたオブジェクトには、自分がクローンであることを判別する機能がありません(非常に特殊なメソッドを除く)

使い方は簡単です:

  var deepClone = new { Id = 1, Name = "222" }.DeepClone();
  var shallowClone = new { Id = 1, Name = "222" }.ShallowClone();
0
alexlomba87

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

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

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

0
will_m

これにより、オブジェクトの読み書き可能なすべてのプロパティが別のオブジェクトにコピーされます。

 public class PropertyCopy<TSource, TTarget> 
                        where TSource: class, new()
                        where TTarget: class, new()
        {
            public static TTarget Copy(TSource src, TTarget trg, params string[] properties)
            {
                if (src==null) return trg;
                if (trg == null) trg = new TTarget();
                var fulllist = src.GetType().GetProperties().Where(c => c.CanWrite && c.CanRead).ToList();
                if (properties != null && properties.Count() > 0)
                    fulllist = fulllist.Where(c => properties.Contains(c.Name)).ToList();
                if (fulllist == null || fulllist.Count() == 0) return trg;

                fulllist.ForEach(c =>
                    {
                        c.SetValue(trg, c.GetValue(src));
                    });

                return trg;
            }
        }

そしてこれはあなたがそれをどのように使うかです:

 var cloned = Utils.PropertyCopy<TKTicket, TKTicket>.Copy(_tmp, dbsave,
                                                            "Creation",
                                                            "Description",
                                                            "IdTicketStatus",
                                                            "IdUserCreated",
                                                            "IdUserInCharge",
                                                            "IdUserRequested",
                                                            "IsUniqueTicketGenerated",
                                                            "LastEdit",
                                                            "Subject",
                                                            "UniqeTicketRequestId",
                                                            "Visibility");

またはすべてをコピーする:

var cloned = Utils.PropertyCopy<TKTicket, TKTicket>.Copy(_tmp, dbsave);
0
Ylli Prifti