it-swarm-eu.dev

Hluboké klonování objektů

Chci udělat něco jako:

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

Poté proveďte změny nového objektu, které se v původním objektu neodrazí.

Tuto funkci často nepotřebuju, takže když je to nutné, uchýlil jsem se k vytvoření nového objektu a pak zkopírovat každou vlastnost individuálně, ale vždycky mě nechává pocit, že existuje lepší nebo elegantnější způsob manipulace situace.

Jak mohu klonovat nebo hluboko kopírovat objekt tak, aby mohl být klonovaný objekt upraven, aniž by se v původním objektu projevily jakékoli změny?

2028
NakedBrunch

Zatímco standardní praxí je implementovat rozhraní ICloneable (popsané zde , takže se nebudu opakovat), zde je pěkný kopírovací stroj s hlubokým klonem, který jsem našel na Kód projektu před chvílí a začlenil ji do našich věcí.

Jak je uvedeno jinde, vyžaduje, aby vaše objekty byly serializovatelné.

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

Myšlenka je, že serializuje váš objekt a pak ho deserializuje do nového objektu. Výhodou je, že se nemusíte starat o klonování všeho, když je objekt příliš složitý.

A s využitím metod rozšíření (také z původně odkazovaného zdroje):

V případě, že dáváte přednost novému { extension method of C # 3.0, změňte metodu na následující podpis:

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

Nyní se volání metody jednoduše stane objectBeingCloned.Clone();.

EDITOVAT(10. ledna 2015) Myslel jsem, že bych se k tomu znovu vrátil, abych zmínil, že jsem nedávno začal používat (Newtonsoft) Json, aby to udělal, to mělo by být lehčí a vyhnout se režii značek [Serializable]. (NB@atconway poukázal v komentářích, že soukromí členové nejsou klonováni metodou 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);
}
1592
johnc

Chtěl jsem cloner pro velmi jednoduché objekty většinou primitivů a seznamů. Pokud je váš objekt mimo krabici JSON serializovatelný, pak tato metoda udělá trik. To nevyžaduje žádnou úpravu ani implementaci rozhraní na klonované třídě, jen JSON serializátor jako JSON.NET.

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

Tuto metodu rozšíření můžete také použít

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

Důvod, proč nepoužívat ICloneable is not protože nemá obecné rozhraní. Důvodem nepoužívání je, že je to vágní . Neznamená to, zda máte malou nebo hlubokou kopii; to je na realizátorovi.

Ano, MemberwiseClone vytváří malou kopii, ale opak MemberwiseClone není Clone; bylo by to možná DeepClone, které neexistuje. Když použijete objekt přes jeho ICloneable rozhraní, nemůžete vědět, který druh klonování podkladového objektu provádí. (A komentáře XML to neosvětlí, protože komentáře rozhraní získáte spíše než komentáře k metodě Clone objektu.)

Obvykle dělám Copy metodu, která dělá přesně to, co chci.

162
Ryan Lundy

Věřím, že po mnohem četném čtení mnoha možností, které jsou zde spojeny, a možných řešení této problematiky všechny možnosti jsou shrnuty velmi dobře na odkazu Ian P (všechny ostatní možnosti jsou variace těchto variant ) a nejlepší řešení poskytuje odkaz Pedro77 na komentáře k otázkám.

Tak jsem jen zkopírovat relevantní části těchto 2 odkazů zde. Tímto způsobem můžeme mít:

To nejlepší, co udělat pro klonování objektů v ostré!

V první řadě jsou to všechny naše možnosti:

článek Fast Deep Copy by Expression Trees má také výkonnostní srovnání klonování pomocí Serializace, reflexe a výrazových stromů.

Proč volím ICloneable (tj. Ručně)

pan Venkat Subramaniam (nadbytečný odkaz zde) vysvětluje podrobně proč .

Všechny jeho články krouží kolem příkladu, který se snaží být použitelný pro většinu případů, s použitím 3 objektů: Osoba, Brain a Město. Chceme klonovat osobu, která bude mít svůj vlastní mozek, ale stejné město. Můžete buď obrázek všechny problémy kterékoli z výše uvedených metod může přinést nebo přečíst článek.

Toto je moje mírně upravená verze jeho závěru:

Kopírování objektu zadáním New následovaného názvem třídy často vede k kódu, který není rozšiřitelný. Použití klonů, aplikace prototypového vzoru, je lepší způsob, jak toho dosáhnout. Použití klonu, jak je uvedeno v C # (a Java), však může být také problematické. Je lepší poskytnout konstruktor chráněných (neveřejných) kopií a vyvolat to z metody klonování. To nám dává možnost delegovat úkol vytvořit objekt na instanci samotné třídy, čímž se zajistí rozšiřitelnost a také bezpečné vytváření objektů pomocí konstruktoru chráněných kopií.

Doufejme, že tato implementace může objasnit:

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

Uvažujme, že mít třídu odvozenou od osoby.

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

Můžete zkusit spustit následující kód:

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

Výsledkem bude:

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]

Všimněte si, že pokud zachováme počet objektů, klon, který je zde implementován, bude udržovat správný počet objektů.

102
cregox

Dávám přednost konstruktoru kopií klonům. Záměr je jasnější.

77
Nick

Jednoduchá metoda rozšíření pro kopírování všech veřejných vlastností. Pracuje pro všechny objekty a nemá vyžadovat třídu [Serializable]. Lze rozšířit o další úroveň přístupu.

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

Měl jsem problémy s použitím IClonable v Silverlightu, ale líbila se mi myšlenka seralizace, mohu seralizovat XML, takže jsem to udělal:

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

Právě jsem vytvořilCloneExtensions knihovnaproject. Provádí rychlý, hluboký klon pomocí jednoduchých operací přiřazení generovaných kompilací kódu runtime stromu Expression Tree.

Jak ji používat?

Namísto psaní vlastních metod Clone nebo Copy s tónem přiřazení mezi poli a vlastnostmi program udělejte sami pro sebe pomocí stromu výrazů. GetClone<T>() metoda označená jako metoda rozšíření vám umožňuje jednoduše volat ji ve vaší instanci:

var newInstance = source.GetClone();

Můžete vybrat, co má být zkopírováno z source do newInstance pomocí CloningFlags enum:

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

Co lze klonovat?

  • Primitivní (int, uint, byte, double, char, atd.), Známé neměnné typy (DateTime, TimeSpan, String) a delegáti (včetně Akce, Func, atd.)
  • Neplatný
  • T [] pole
  • Vlastní třídy a struktury, včetně generických tříd a struktur.

Následující členové třídy/struct jsou klonováni interně:

  • Hodnoty veřejných, ne čtených polí
  • Hodnoty veřejných vlastností s přístupovými i nastavovacími prvky
  • Sbírka položek pro typy provádějící ICollection

Jak rychle to je?

Řešení je rychlejší než reflexe, protože informace o členech musí být shromážděny pouze jednou, dříve než je GetClone<T> poprvé použit pro daný typ T.

Je také rychlejší než řešení založené na serializaci, když klonujete více než pár instancí stejného typu T.

a další ...

Přečtěte si více o generovaných výrazech v documentation .

Ukázka ladění výpisu výrazu 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:
}

}

co má stejný význam jako kód 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;
}

Není to tak, jak byste psali vlastní metodu Clone pro List<int>?

27
MarcinJuraszek

Pokud již používáte aplikaci třetí strany jako ValueInjecter nebo Automapper , můžete něco takového udělat:

MyObject oldObj; // The existing object to clone

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

Pomocí této metody nemusíte na svých objektech implementovat ISerializable nebo ICloneable. Toto je běžné u vzoru MVC/MVVM, takže jednoduché nástroje, jako je tento, byly vytvořeny.

viz řešení pro klonování hlubokých hodnot na CodePlex .

26
Michael Cox

Krátká odpověď je, že zdědíte z rozhraní ICloneable a pak implementujete funkci .clone. Klon by měl udělat členskou kopii a provést hlubokou kopii na každém členu, který to vyžaduje, a poté vrátit výsledný objekt. Jedná se o rekurzivní operaci (vyžaduje, aby všichni členové třídy, kterou chcete klonovat, jsou buď hodnotové typy, nebo implementujte ICloneable a aby jejich členové byli buď hodnotové typy nebo implementovali ICloneable, atd.).

Pro podrobnější vysvětlení klonování pomocí IClonable, podívejte se tento článek .

long answer je "záleží". Jak uvedli jiní, ICloneable není podporován generiky, vyžaduje zvláštní úvahy pro kruhové reference tříd a je vlastně některými vnímán jako "chyba" v rozhraní .NET Framework. Metoda serializace závisí na tom, zda jsou vaše objekty serializovatelné, což nemusí být a nemusíte mít nad nimi kontrolu. Tam je ještě hodně debata ve společenství přes kterého je “nejlepší” praxe. Ve skutečnosti, žádné z řešení není jedno-velikosti padne vše nejlepší praxe pro všechny situace, jako ICloneable byl původně interpretován být.

Podívejte se na tento článek Developer's Corner pro několik dalších možností (kredit pro Iana).

20
Zach Burlingame

Nejlepší je implementovat metodu extension like

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

a pak jej použijte kdekoli v řešení

var copy = anyObject.DeepClone();

Můžeme mít následující tři implementace:

  1. Podle serializace (nejkratší kód)
  2. Odrazem - 5x rychlejší
  3. Podle výrazových stromů - 20x rychlejší

Všechny navázané metody jsou dobře funkční a byly hluboce testovány.

19
frakon
  1. V podstatě musíte implementovat ICloneable rozhraní a pak realizovat kopírování struktury objektů.
  2. Pokud je to hluboká kopie všech členů, je třeba se ujistit (nikoli o řešení, které si vyberete), že všechny děti jsou také klonovatelné.
  3. Někdy si musíte být vědomi nějakého omezení během tohoto procesu, například když kopírujete objekty ORM, většina rámců umožňuje pouze jeden objekt připojený k relaci a NESMÍTE vytvářet klony tohoto objektu, nebo pokud je to možné, musíte se starat o připojení těchto objektů k relaci.

Na zdraví.

16
dimarzionist

Pokud chcete skutečné klonování neznámých typů, můžete se podívat na fastclone .

To je klonování založené na výrazu, které pracuje asi desetkrát rychleji než binární serializace a udržuje úplnou integritu grafu objektů.

To znamená, že pokud odkazujete vícekrát na stejný objekt ve své hierarchii, bude mít klon také odkaz na jednu instanci.

Není třeba pro rozhraní, atributy nebo jiné modifikace klonovaných objektů.

15
Michael Sander

Udržujte věci jednoduché a používejte AutoMapper jako jiní zmínili, je to jednoduchá malá knihovna mapovat jeden objekt na jiný ... Chcete-li kopírovat objekt do jiného se stejným typem, vše, co potřebujete, jsou tři řádky kódu:

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

Cílový objekt je nyní kopií zdrojového objektu. Není to dost jednoduché? Vytvořte metodu rozšíření, která bude použita všude ve vašem řešení:

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

Pomocí metody rozšíření se tyto tři řádky stanou jedním řádkem:

MyType copy = source.Copy();
11
Stacked

Přišel jsem s tím, abych překonal .NET nedostatek, který musel ručně kopírovat seznam <T>.

Používám toto:

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

A na jiném místě:

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

Snažil jsem se přijít s oneliner, který to dělá, ale není to možné, kvůli výnosu nefunguje uvnitř anonymních bloků metod.

Ještě lépe používejte obecný seznam <T> cloner:

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

Proč bych si tuto odpověď vybral?

  • Zvolte tuto odpověď, pokud chcete nejrychlejší rychlost .NET je schopen.
  • Ignorujte tuto odpověď, pokud chcete opravdu, opravdu snadnou metodu klonování.

Jinými slovy, jít s jinou odpovědí, pokud nemáte výkon překážku, která potřebuje upevnění, a můžete dokázat, že s profiler .

10x rychlejší než jiné metody

Následující způsob provedení hlubokého klonu je:

  • 10x rychlejší než cokoliv, co zahrnuje serializaci/deserializaci;
  • Docela zatraceně blízko teoretické maximální rychlosti .NET je schopen.

A metoda ...

Pro maximální rychlost můžete použít Nested MemberwiseClone pro provedení hluboké kopie . Jeho téměř stejná rychlost jako kopírování hodnoty struct, a je mnohem rychlejší než (a) reflexe nebo (b) serializace (jak je popsáno v jiných odpovědích na této stránce).

Všimněte si, že if / použijete Vnořené memberwiseClone pro hlubokou kopii , musíte ručně implementovat ShallowCopy pro každou vnořenou úroveň ve třídě a DeepCopy, která volá všechny zmíněné metody ShallowCopy k vytvoření kompletní klon. To je jednoduché: celkem jen několik řádků, viz níže uvedený demo kód.

Zde je výstup kódu ukazující rozdíl relativního výkonu pro 100 000 klonů:

  • 1.08 vteřin pro vnořenou strukturu vnořený memberwiseClone
  • 4,77 sekundy pro Nested memberwiseClone na vnořené třídy
  • 39,93 sekund pro serializaci/deserializaci

Použití Nested memberwiseClone na třídě téměř stejně rychle jako kopírování struktury a kopírování struktury je sakra blízko teoretické maximální rychlosti .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

Chcete-li pochopit, jak provádět hlubokou kopii pomocí programu MemberwiseCopy, zde je ukázkový projekt, který byl použit k vygenerování výše uvedených časů:

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

Poté zavolejte demo z hlavního:

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

Všimněte si, že if / použijete Vnořené MemberwiseClone pro hlubokou kopii , musíte ručně implementovat ShallowCopy pro každou vnořenou úroveň ve třídě a DeepCopy, která volá všechny zmíněné metody ShallowCopy vytvořit kompletní klon. To je jednoduché: celkem jen několik řádků, viz výše uvedený demo kód.

Typy hodnot vs. typy referencí

Všimněte si, že pokud jde o klonování objektu, existuje velký rozdíl mezi " struct " a " class ":

  • Pokud máte " struct ", je to typ hodnoty , takže ho můžete jen zkopírovat a obsah bude klonován (ale bude to jen mělký klon, pokud nepoužijete techniky).
  • Pokud máte " class ", je to typ odkazu , takže pokud jej zkopírujete, vše, co děláte, je kopírování ukazatele na něj. Chcete-li vytvořit skutečný klon, musíte být kreativnější a použít rozdíly mezi typy hodnot a typy referencí které vytvoří další kopii původního objektu v paměti.

Viz rozdíly mezi typy hodnot a typy referencí .

Kontrolní součty na pomoc při ladění

  • Nesprávné klonování objektů může vést k velmi obtížným chybám. Ve výrobním kódu mám tendenci implementovat kontrolní součet, aby se zkontrolovalo, že objekt byl správně klonován, a nebyl poškozen jiným odkazem na něj. Tento kontrolní součet lze vypnout v režimu uvolnění.
  • Tuto metodu považuji za velmi užitečnou: často chcete pouze klonovat části objektu, ne celou věc.

Opravdu užitečné pro oddělení mnoha vláken z mnoha jiných vláken

Jedním z vynikajících případů použití tohoto kódu je podávání klonů vnořené třídy nebo struct do fronty, aby se implementoval vzor výrobce/spotřebitel.

  • Můžeme mít jednu (nebo více) podprocesů, které upravují třídu, kterou vlastní, a potom posunout úplnou kopii této třídy do ConcurrentQueue.
  • Pak máme jednu (nebo více) nití tahajících kopie těchto tříd a zabývajících se nimi.

V praxi to funguje velmi dobře a umožňuje nám oddělit mnoho vláken (výrobců) od jednoho nebo více vláken (spotřebitelů).

A tato metoda je také velmi rychlá: použijeme-li vnořené struktury, je to 35x rychlejší než serializace/deserializace vnořených tříd a umožňuje nám využít všech vláken dostupných na stroji.

Aktualizace

Zdá se, že ExpressMapper je tak rychlý, ne-li rychlejší než ruční kódování, jak je uvedeno výše. Možná budu muset vidět, jak se porovnávají s profilerem.

7
Contango

Viděl jsem, že je realizován také prostřednictvím reflexe. V podstatě tam byla metoda, která by iterovala přes členy objektu a vhodně kopírovat je do nového objektu. Když dosáhl referenčních typů nebo sbírek, myslím, že to udělal rekurzivní volání na sebe. Reflexe je drahá, ale fungovala docela dobře.

7
xr280xr

Jelikož jsem nemohl najít klonáře, který by splňoval všechny mé požadavky v různých projektech, vytvořil jsem hluboký klonovač, který lze konfigurovat a přizpůsobit různým strukturám kódu namísto přizpůsobení kódu tak, aby splňoval požadavky klonů. Toho bylo dosaženo přidáním anotací do kódu, který má být klonován, nebo ponecháte kód tak, jak má mít výchozí chování. Využívá reflexi, typ cache a je založena na rapidflect . Proces klonování je velmi rychlý pro velké množství dat a vysokou hierarchii objektů (ve srovnání s jinými algoritmy založenými na reflexi/serializaci).

https://github.com/kalisohn/CloneBehave

K dispozici také jako balíček nuget: https://www.nuget.org/packages/Clone.Behave/1.0.0

Například: Následující kód bude deepClone Address, ale provede pouze malou kopii pole _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

Obecně implementujete rozhraní ICloneable a implementujete Clone sami. Objekty C # mají vestavěnou metodu MemberwiseClone, která provádí malou kopii, která vám může pomoci pro všechny primitivy.

Pro hlubokou kopii neexistuje způsob, jak by to dokázala automaticky.

7
HappyDude

Zde je implementace hluboké kopie:

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

Tato metoda problém vyřešila:

private static MyObj DeepCopy(MyObj source)
        {

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

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

        }

Použijte toto: MyObj a = DeepCopy(b);

6
JerryGoyal

Líbí se mi 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));
        }
    }

Pokud máte více věcí, které chcete zkopírovat, přidejte je

5
LuckyLikey

Generátor kódů

Viděli jsme spoustu nápadů od serializace přes ruční implementaci až po reflexi a chci navrhnout zcela odlišný přístup s využitím CGbR Code Generator . Metoda generování klonů je efektivní z paměti a procesoru a proto je 300x rychlejší než standardní DataContractSerializer.

Vše, co potřebujete, je částečná definice třídy s ICloneable a generátor provede zbytek:

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

Poznámka: Poslední verze má více nulových kontrol, ale nechala jsem je pro lepší pochopení.

5
Toxantron

Zde řešení rychlé a snadné, že pracoval pro mě bez relaying na Serialization/Deserialization.

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

EDITOVAT: vyžaduje

    using System.Linq;
    using System.Reflection;

Tak jsem to použil

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

Myslím, že to můžete zkusit.

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

Následuj tyto kroky:

  • Definujte ISelf<T> s vlastnostmi Self jen pro čtení, která vrací T a ICloneable<out T>, která pochází z ISelf<T> a obsahuje metodu T Clone().
  • Pak definujte CloneBase typ, který implementuje protected virtual generic VirtualClone casting MemberwiseClone do typu pass-in.
  • Každý odvozený typ by měl implementovat VirtualClone zavoláním metody základního klonu a pak udělat vše, co je třeba udělat, aby se správně klonovaly ty aspekty odvozeného typu, které dosud neřídila metoda VirtualClone.

Pro maximální univerzálnost dědičnosti by třídy vystavující funkci veřejného klonování měly být sealed, ale měly by být odvozeny od základní třídy, která je jinak identická, s výjimkou nedostatku klonování. Namísto předávání proměnných typu explicitní klonovatelnosti vezměte parametr typu ICloneable<theNonCloneableType>. To umožní rutině, která očekává, že klonovatelný derivát Foo bude pracovat s klonovatelným derivátem DerivedFoo, ale také umožní vytvoření ne-klonovatelných derivátů Foo.

4
supercat

Vytvořil jsem verzi přijaté odpovědi, která pracuje s '[Serializable]' a '[DataContract]'. Je to už dlouho, co jsem to napsal, ale pokud si dobře vzpomínám [DataContract] potřeboval jiný serializátor.

Vyžaduje Systém, 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
Jeroen Ritmeijer

Pokud je strom objektů Serializeable, můžete také použít něco takového

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

být informován, že toto řešení je velmi snadné, ale není to tak výkonné jako jiné řešení.

A buďte si jisti, že pokud bude třída roste, budou stále jen ty klonované oblasti, které se také serializují.

3
LuckyLikey

Klonovat objekt třídy můžete pomocí metody Object.MemberwiseClone,

přidejte tuto funkci do své třídy:

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

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

pak provést hluboké nezávislé kopie, stačí zavolat metodu DeepCopy:

yourClass newLine = oldLine.DeepCopy();

snad to pomůže.

3
Chtiwi Malek

Ok, tam jsou některé zřejmé, příklad s odrazem v tomto příspěvku, ale reflexe je obvykle pomalé, dokud začnete mezipaměti správně.

pokud to budeš správně ukládat, bude to o hloubce klon 1000000 objekt o 4,6s (měřeno Watcherem).

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

než si vezmete vlastnosti uložené v mezipaměti nebo přidáte nové do slovníku a použijete je jednoduše

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

plnou kontrolu kódu v mém příspěvku v jiné odpovědi

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

3
Roma Borodov

Vzhledem k tomu, že téměř všechny odpovědi na tuto otázku byly neuspokojivé nebo prostě nefungují v mé situaci, napsal AnyClone který je plně realizován s odrazem a řeší všechny potřeby zde. Nemohl jsem dostat serializaci do práce ve složitém scénáři s komplexní strukturou a IClonable je méně než ideální - ve skutečnosti by to nemělo být ani nutné.

Standardní atributy ignorování jsou podporovány pomocí [IgnoreDataMember], [NonSerialized]. Podporuje složité kolekce, vlastnosti bez nastavení, readonly polí atd.

Doufám, že to pomůže někomu jinému, kdo narazil na stejné problémy, které jsem udělal.

2
Michael Brown

Když používáte Marc Gravells protobuf-net jako váš serializátor, přijatá odpověď potřebuje nějaké drobné úpravy, protože objekt, který chcete kopírovat, nebude přisuzován [Serializable], a proto není serializovatelný a metoda Clone-metoda bude vyvolávat výjimku.
Upravil jsem ji pro práci s 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);
    }
}

To kontroluje přítomnost atributu [ProtoContract] a používá vlastní formátovač protobufs k serializaci objektu.

1
Basti M

C # Rozšíření, které bude podporovat i typy "not ISerializable ".

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

Používání

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

Je neuvěřitelné, kolik úsilí můžete strávit s rozhraním IClonable - zejména pokud máte hierarchii s vysokou třídou. Také memberwiseClone funguje nějak zvláštně - to není přesně klonovat ani normální typ seznamu struktur.

A samozřejmě nejzajímavějším dilematem pro serializaci je serializace zpětných referencí - např. tříd hierarchií, kde máte vztahy mezi nadřazenými a podřízenými. Pochybuji, že binární serializátor vám v tomto případě pomůže. (Skončí s rekurzivními smyčkami + přetečením zásobníku).

Nějak se mi líbilo řešení zde navrhované: Jak se vám udělat hlubokou kopii objektu v .NET (C # konkrétně)?

nicméně - to nepodporovalo Lists, dodal, že podpora, také vzal v úvahu re-rodičovství. Pro rodičovství pouze pravidlo, které jsem udělal, že pole nebo vlastnost by měla být pojmenována "rodič", pak bude ignorována DeepClone. Možná budete chtít rozhodnout o vlastních pravidlech pro zpětné odkazy - pro stromové hierarchie to může být "vlevo/vpravo" atd.

Zde je celý fragment kódu včetně zkušebního kódu:

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

Ještě další odpověď na JSON.NET. Tato verze pracuje s třídami, které neimplementují 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

Mapper provede hlubokou kopii. Předpokládejme členovi svého objektu, že vytvoří nový objekt a přiřadí všechny jeho hodnoty. Pracuje rekurzivně na každém primitivním vnitřním členu.

Navrhuji vám jeden z nejrychlejších, v současné době aktivně rozvinutých. Navrhuji UltraMapper https://github.com/maurosampietro/UltraMapper

Balíčky Nuget: https://www.nuget.org/packages/UltraMapper/

1
Mauro Sampietro

Obecné přístupy jsou technicky platné, ale já jsem chtěl přidat poznámku od sebe, protože jen zřídka potřebujeme skutečnou hlubokou kopii. místa, kde jsou objekty kopírovány a pak modifikovány explicitně, je snadné se ztratit.

Ve většině situací v reálném životě také chcete mít co nejvíce granulární kontrolu nad procesem kopírování, protože nejste jen spojeni s rámcem pro přístup k datům, ale také v praxi by kopírované obchodní objekty měly být zřídka 100% stejné. Myslíte si, že příklad referenceId, který používá ORM k identifikaci odkazů na objekty, bude plná kopie také kopírovat toto ID, takže zatímco v paměti budou objekty jiné, jakmile je odešlete do datového úložiště, bude si stěžovat, takže budete po kopírování musí tyto vlastnosti ručně upravit a pokud se objekt změní, je třeba jej upravit na všech místech, která používají obecné hluboké kopírování.

Co je vlastně hluboká kopie? Je to právě nově přidělený objekt na haldě, který je totožný s původním objektem, ale zabere jiný paměťový prostor, jako takový, spíše než použití obecné funkce cloneru, proč nejen vytvořit nový objekt?

Já osobně používám myšlenku statických výrobních metod na mém doménovém objektu.

Příklad:

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

Pokud se někdo dívá na to, jak může objektovou strukturu objektu zachovat při zachování plné kontroly nad procesem kopírování, je to řešení, s nímž jsem byl osobně velmi úspěšný. Chráněné konstruktory to také dělají, jiní vývojáři jsou nuceni používat tovární metody, které dávají úhlednému jedinému bodu objektové instanci zapouzdření stavební logiky uvnitř objektu. Můžete také přetížit metodu a mít několik logických klonů pro různá místa v případě potřeby.

0

Našel jsem nový způsob, jak to udělat, je Emit.

Můžeme použít Emit přidat IL do aplikace a spustit. Ale já si nemyslím, že je to dobrý způsob, jak to chci zdokonalit, abych napsal odpověď.

Emit může vidět oficiální dokument a Guide

Měli byste se naučit nějaký IL číst kód. Budu psát kód, který může kopírovat vlastnost ve třídě.

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

Kód může být hluboká kopie, ale může kopírovat vlastnost. Pokud chcete, aby to do hluboké kopie, že můžete změnit pro IL je příliš těžké, že nemůžu udělat.

0
lindexi

jak jen o přepracování uvnitř metody, která by měla vyvolat v podstatě konstruktor automatického kopírování

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

zdá se, že to funguje

0
will_m

To zkopíruje všechny čitelné a zapisovatelné vlastnosti objektu do jiného.

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

a to je to, jak je používáte:

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

nebo zkopírovat vše:

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