Vraag Diep klonen van objecten


Ik wil iets doen als:

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

En breng vervolgens wijzigingen aan in het nieuwe object die niet worden weerspiegeld in het oorspronkelijke object.

Ik heb deze functionaliteit niet vaak nodig, dus wanneer het nodig was, heb ik mijn best gedaan om een ​​nieuw object te maken en vervolgens elke eigenschap afzonderlijk te kopiëren, maar het geeft me altijd het gevoel dat er een betere of elegantere manier van werken is de situatie.

Hoe kan ik een object klonen of diep kopiëren, zodat het gekloonde object kan worden gewijzigd zonder dat wijzigingen worden weergegeven in het oorspronkelijke object?


1827
2017-09-17 00:06


oorsprong


antwoorden:


Terwijl de standaardpraktijk het implementeren van de. Is ICloneable interface (beschreven hier, dus ik zal niet regurgitate), hier is een mooie diepe kloon object kopieermachine die ik vond Het codeproject een tijdje geleden en verwerkt het in onze spullen.

Zoals elders vermeld, moeten uw objecten wel serialiseerbaar zijn.

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

Het idee is dat het uw object serialiseert en het vervolgens deserialiseert tot een nieuw object. Het voordeel is dat u zich geen zorgen hoeft te maken over het klonen van alles wanneer een object te complex wordt.

En met het gebruik van uitbreidingsmethoden (ook van de bron waarnaar oorspronkelijk werd verwezen):

In het geval dat u de voorkeur geeft aan het nieuwe uitbreidingsmethoden van C # 3.0, verander de methode om de volgende handtekening te hebben:

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

Nu wordt de methodeaanroep gewoon objectBeingCloned.Clone();.

BEWERK (10 januari 2015) Ik dacht dat ik dit opnieuw zou bekijken, om te vermelden dat ik onlangs (Newtonsoft) Json begon te gebruiken om dit te doen, zou moeten zijn lichter en vermijdt de overhead van [Serializable] -tags. (NB @atconway heeft in de comments erop gewezen dat privéleden niet zijn gekloond met behulp van de JSON-methode)

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

1466
2018-04-03 13:31



Ik wilde een kloner voor heel eenvoudige objecten van meestal primitieven en lijsten. Als uw object uit de doos JSON serializable is, zal deze methode de slag slaan. Dit vereist geen aanpassing of implementatie van interfaces op de gekloonde klasse, alleen een JSON-serializer zoals JSON.NET.

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

181
2017-09-17 01:12



De reden om niet te gebruiken ICloneable is niet omdat het geen generieke interface heeft. De reden om het niet te gebruiken is omdat het vaag is. Het maakt niet duidelijk of u een ondiepe of een diepe kopie krijgt; dat is aan de uitvoerder.

Ja, MemberwiseClone maakt een ondiepe kopie, maar het tegenovergestelde daarvan MemberwiseClone is niet Clone; het zou misschien zijn DeepClone, wat niet bestaat. Wanneer u een object via de ICloneable-interface gebruikt, kunt u niet weten welk type klonen het onderliggende object uitvoert. (En XML-opmerkingen maken het niet duidelijk, omdat u de interface-opmerkingen krijgt in plaats van die op de methode Kloon van het object.)

Wat ik meestal doe, is gewoon een Copy methode die precies doet wat ik wil.


146
2017-09-26 20:18



Na veel te hebben gelezen over veel van de opties die hier zijn gekoppeld, en mogelijke oplossingen voor dit probleem, denk ik alle opties zijn redelijk goed samengevat in Ian Pde link (alle andere opties zijn variaties daarop) en de beste oplossing wordt geboden door Pedro77de link op de opmerkingen van de vraag.

Dus ik kopieer alleen relevante delen van die 2 referenties hier. Op die manier kunnen we hebben:

Het beste ding om te doen voor het klonen van objecten in c scherp!

Eerst en vooral zijn dit allemaal onze opties:

De article Fast Deep Copy door Expression Trees   heeft ook een prestatievergelijking van klonen door serialisatie, reflectie en expressiebomen.

Waarom ik kies ICloneable (d.w.z. handmatig)

De heer Venkat Subramaniam (redundante link hier) legt in detail uit waarom.

Al zijn artikelen cirkelen rond een voorbeeld dat in de meeste gevallen probeert te worden toegepast, met behulp van 3 objecten: Persoon, Hersenen en stad. We willen een persoon klonen, die zijn eigen brein heeft, maar dezelfde stad. Je kunt je alle problemen voorstellen, een van de andere methoden hierboven kan het artikel brengen of lezen.

Dit is mijn enigszins aangepaste versie van zijn conclusie:

Een object kopiëren door op te geven New gevolgd door de klassenaam leidt vaak tot code die niet uitbreidbaar is. Het gebruik van kloon, de toepassing van een prototype patroon, is een betere manier om dit te bereiken. Het gebruik van kloon zoals deze wordt geboden in C # (en Java) kan echter ook behoorlijk problematisch zijn. Het is beter om een ​​beschermde (niet-openbare) kopieconstructor te leveren en die aan te roepen vanuit de kloonmethode. Dit geeft ons de mogelijkheid om de taak van het maken van een object aan een instantie van een klasse zelf te delegeren, waardoor uitbreidbaarheid wordt geboden en ook, veilig de objecten te maken met behulp van de beschermde kopieerconstructor.

Hopelijk kan deze implementatie dingen duidelijk maken:

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

Overweeg nu een klasse af te leiden van Persoon.

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

U kunt proberen de volgende code uit te voeren:

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

De geproduceerde output zal zijn:

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

Merk op dat, als we een telling van het aantal objecten bijhouden, de kloon zoals hier geïmplementeerd een correcte telling van het aantal objecten houdt.


83
2017-09-17 00:13



Ik geef de voorkeur aan een kopie-constructor voor een kloon. De bedoeling is duidelijker.


69
2018-03-16 11:38



Eenvoudige uitbreidingsmethode om alle openbare eigenschappen te kopiëren. Werkt voor alle objecten en doet niet vereisen klasse te zijn [Serializable]. Kan worden uitgebreid voor een ander toegangsniveau.

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

36
2017-12-02 17:39



Nou, ik had problemen met het gebruik van ICloneable in Silverlight, maar ik vond het een goed idee om te serialiseren, ik kan XML serialiseren, dus ik deed dit:

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

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

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

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

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

        return sbTR.ToString();
    }

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

28
2017-10-15 17:55



Als u al een toepassing van derden gebruikt zoals ValueInjecter of Automapper, je kunt zoiets als dit doen:

MyObject oldObj; // The existing object to clone

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

Met deze methode hoeft u geen ISerializable of ICloneable op uw objecten te implementeren. Dit komt vaak voor bij het MVC / MVVM-patroon, dus eenvoudige tools zoals deze zijn gemaakt.

zien de valueinjecter deep cloning-oplossing op CodePlex.


26
2017-12-24 22:56



Ik heb zojuist gemaakt CloneExtensions bibliotheek project. Het voert een snelle, diepe kloon uit met behulp van eenvoudige toewijzingsbewerkingen die worden gegenereerd door Expression Tree runtime-codecalculatie.

Hoe te gebruiken?

In plaats van je eigen te schrijven Clone of Copy methoden met een toon van toewijzingen tussen velden en eigenschappen zorgen ervoor dat het programma het zelf doet, met behulp van Expression Tree. GetClone<T>() methode gemarkeerd als extensiemethode, kunt u het eenvoudigweg op uw instantie callen:

var newInstance = source.GetClone();

U kunt kiezen waaruit moet worden gekopieerd source naar newInstance gebruik makend van CloningFlags enum:

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

Wat kan worden gekloond?

  • Primitief (int, uint, byte, double, char, etc.), bekend als onveranderlijk types (DateTime, TimeSpan, String) en afgevaardigden (inclusief Actie, Func, etc.)
  • nullable
  • T [] -arrays
  • Aangepaste klassen en structs, inclusief generieke klassen en structs.

Volgende klasse / struct-leden worden intern gekloond:

  • Waarden van openbare, niet alleen-lezen velden
  • Waarden van openbare eigendommen met zowel get en set accessors
  • Verzamelitems voor typen die ICollection implementeren

Hoe snel is het?

De oplossing is sneller dan reflectie, omdat ledeninformatie slechts één keer eerder verzameld hoeft te worden GetClone<T> wordt voor het eerst gebruikt voor een bepaald type T.

Het is ook sneller dan op serialisatie gebaseerde oplossing wanneer u meer exemplaren van hetzelfde type kloon T.

en meer...

Lees meer over gegenereerde expressies op documentatie.

Voorbeeld van expressie debug lijst voor 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:
}

}

wat heeft dezelfde betekenis als het volgen van c # code:

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

Is het niet helemaal zoals hoe je je eigen schrijft Clone methode voor List<int>?


25
2017-09-17 00:14



Het korte antwoord is dat je van de ICloneable-interface gaat en vervolgens de .clone-functie implementeert. Clone moet een lid-kopie maken en een diepkopie uitvoeren op elk lid dat dit vereist en vervolgens het resulterende object retourneren. Dit is een recursieve bewerking (het vereist dat alle leden van de klasse die u wilt klonen ofwel waardetypes zijn, ofwel ICloneable implementeren en dat hun leden ofwel waardetypes zijn ofwel ICloneable implementeren, enzovoort).

Voor een meer gedetailleerde uitleg over klonen met ICloneable, check out Dit artikel.

De lang antwoord is "het hangt ervan af". Zoals door anderen wordt vermeld, wordt ICloneable niet ondersteund door generieke geneesmiddelen, vereist het speciale overwegingen voor verwijzingen naar circulaire klassen en wordt het door sommigen zelfs gezien als een "vergissing" in het .NET Framework. De serialisatiemethode hangt af van het feit dat uw objecten serialiseerbaar zijn, wat ze misschien niet zijn en u hebt misschien geen controle over. Er is nog steeds veel discussie in de gemeenschap waarover de "beste" praktijk bestaat. In werkelijkheid zijn geen van de oplossingen geschikt voor alle situaties, zoals ICONLeable oorspronkelijk was.

Zie dit Developer's Corner-artikel voor een paar meer opties (lof voor Ian).


20
2018-02-16 11:30



Als u echt klonen wilt naar onbekende typen, kunt u een kijkje nemen fastclone.

Dat is op expressie gebaseerd klonen dat ongeveer 10 keer sneller werkt dan binaire serialisatie en dat de integriteit van de gehele objectgrafiek handhaaft.

Dat betekent: als u meerdere keren verwijst naar hetzelfde object in uw hierachy, krijgt de kloon ook een enkele instantie waarnaar verwezen wordt.

Er is geen behoefte aan interfaces, attributen of andere wijzigingen aan de objecten die worden gekloond.


15
2017-08-03 22:24