Głębokie klonowanie obiektów


2226

Chcę zrobić coś takiego:

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

Następnie wprowadź zmiany w nowym obiekcie, które nie są odzwierciedlone w oryginalnym obiekcie.

Często nie potrzebuję tej funkcjonalności, więc kiedy było to konieczne, zacząłem tworzyć nowy obiekt, a następnie kopiować każdą właściwość osobno, ale zawsze daje mi to poczucie, że istnieje lepszy lub bardziej elegancki sposób obsługi sytuacja.

Jak mogę sklonować lub głęboko skopiować obiekt, aby sklonowany obiekt mógł być modyfikowany bez odzwierciedlania jakichkolwiek zmian w oryginalnym obiekcie?


81
Może się przydać: „Dlaczego kopiowanie obiektu jest straszne?” agiledeveloper.com/articles/cloning072002.htm
Pedro77


18
Powinieneś spojrzeć na AutoMapper
Daniel Little

3
Twoje rozwiązanie jest o wiele bardziej złożone, zgubiłem się, czytając je ... hehehe. Używam interfejsu DeepClone. interfejs publiczny IDeepCloneable <T> {T DeepClone (); }
Pedro77,

1
@ Pedro77 - Co ciekawe, artykuł kończy się tym, że tworzy clonemetodę w klasie, a następnie wywołuje wewnętrznego, prywatnego konstruktora, który zostaje przekazany this. Więc kopiowanie jest okropne [sic], ale kopiowanie ostrożne (a artykuł zdecydowanie warto przeczytać) nie jest. ; ^)
ruffin

Odpowiedzi:


1715

Podczas gdy standardową praktyką jest implementacja ICloneableinterfejsu (opisanego tutaj , więc nie będę regurgitować), oto ładna kopiarka obiektów z głębokim klonowaniem, którą znalazłem w The Code Project i włączyłem ją do naszych materiałów.

Jak wspomniano w innym miejscu, Twoje obiekty muszą być serializowane.

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

Chodzi o to, że serializuje Twój obiekt, a następnie przekształca go w nowy obiekt. Zaletą jest to, że nie trzeba się martwić klonowaniem wszystkiego, gdy obiekt staje się zbyt złożony.

I przy użyciu metod rozszerzenia (również z pierwotnie przywoływanego źródła):

Jeśli wolisz korzystać z nowych metod rozszerzenia C # 3.0, zmień metodę, tak aby miała następujący podpis:

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

Teraz wywołanie metody staje się po prostu objectBeingCloned.Clone();.

EDYCJA (10 stycznia 2015 r.) Pomyślałem, że wrócę do tego, aby wspomnieć, że ostatnio zacząłem używać Jsona (Newtonsoft), aby to zrobić, powinien być lżejszy i pozwala uniknąć narzutów tagów [Serializable]. ( NB @atconway zwrócił uwagę w komentarzach, że członkowie prywatni nie są klonowani przy użyciu metody 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);
}

24
stackoverflow.com/questions/78536/cloning-objects-in-c/… ma link do powyższego kodu [i odwołuje się do dwóch innych takich implementacji, z których jedna jest bardziej odpowiednia w moim kontekście]
Ruben Bartelink

102
Serializacja / deserializacja wiąże się ze znacznym narzutem, który nie jest konieczny. Zobacz interfejs ICloneable i metody klonowania .MemberWise () w języku C #.
3Dave

18
@David, to prawda, ale jeśli obiekty są lekkie, a wydajność osiągnięta podczas korzystania z nich nie jest zbyt wysoka jak na twoje wymagania, to jest to przydatna wskazówka. Przyznaję, że nie korzystałem intensywnie z dużą ilością danych w pętli, ale nigdy nie widziałem żadnego problemu z wydajnością.
johnc

16
@Amir: właściwie no: typeof(T).IsSerializablejest również prawdziwe, jeśli typ został oznaczony [Serializable]atrybutem. Nie musi implementować ISerializableinterfejsu.
Daniel Gehriger

11
Pomyślałem, że wspominam, że chociaż ta metoda jest przydatna i używałem jej wiele razy, nie jest ona wcale zgodna ze średnim poziomem zaufania - więc uważaj, jeśli piszesz kod, który wymaga zgodności. BinaryFormatter uzyskuje dostęp do pól prywatnych i dlatego nie może działać w domyślnym zestawie uprawnień dla środowisk z częściowym zaufaniem. Możesz wypróbować inny serializator, ale upewnij się, że dzwoniący wie, że klon może nie być idealny, jeśli przychodzący obiekt opiera się na polach prywatnych.
Alex Norcliffe

298

Chciałem klonera dla bardzo prostych obiektów, głównie prymitywów i list. Jeśli Twój obiekt jest po wyjęciu z pudełka JSON serializowalny, ta metoda załatwi sprawę. Nie wymaga to modyfikacji ani implementacji interfejsów w sklonowanej klasie, tylko serializator JSON, taki jak JSON.NET.

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

Możesz także użyć tej metody rozszerzenia

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

13
solutiojn jest jeszcze szybszy niż rozwiązanie BinaryFormatter, porównanie wydajności serializacji .NET
esskar

3
Dzięki za to. Byłem w stanie zrobić zasadniczo to samo z serializatorem BSON, który jest dostarczany ze sterownikiem MongoDB dla C #.
Mark Ewer

3
To dla mnie najlepszy sposób, jednak używam, Newtonsoft.Json.JsonConvertale jest tak samo
Pierre

1
Aby to zadziałało, obiekt do klonowania musi być szeregowalny, jak już wspomniano - oznacza to również na przykład, że może nie mieć okrągłych zależności
radomeit

2
Myślę, że to najlepsze rozwiązanie, ponieważ implementację można zastosować w większości języków programowania.
mr5

178

Powodem nieużywania ICloneable nie jest to, że nie ma on ogólnego interfejsu. Powodem, dla którego nie należy go używać, jest niejasny . Nie jest jasne, czy otrzymujesz płytką czy głęboką kopię; to zależy od implementatora.

Tak, MemberwiseClonetworzy płytką kopię, ale przeciwieństwo MemberwiseClonenie jest Clone; być może DeepClonenie istniałoby. Kiedy używasz obiektu przez jego interfejs ICloneable, nie możesz wiedzieć, jakiego rodzaju klonowanie wykonuje dany obiekt. (A komentarze XML nie wyjaśnią tego, ponieważ otrzymasz komentarze interfejsu zamiast tych dotyczących metody klonowania obiektu).

Zwykle robię po prostu Copymetodę, która robi dokładnie to, czego chcę.


Nie jestem pewien, dlaczego ICloneable jest uważany za niejasny. Biorąc pod uwagę typ taki jak Dictionary (Of T, U), oczekiwałbym, że ICloneable.Clone powinien wykonać każdy poziom głębokiego i płytkiego kopiowania niezbędny, aby nowy słownik był niezależnym słownikiem zawierającym te same T i U (treść strukturalna, i / lub odniesienia do obiektów) jako oryginał. Gdzie jest dwuznaczność? Z pewnością ogólny ICloneable (Of T), który odziedziczył ISelf (Of T), który zawierał metodę „Self”, byłby znacznie lepszy, ale nie widzę dwuznaczności przy głębokim kontra płytkim klonowaniu.
supercat 12.01.11

31
Twój przykład ilustruje problem. Załóżmy, że masz słownik <ciąg, klient>. Czy sklonowany słownik powinien zawierać te same obiekty klienta, co oryginał, czy kopie tych obiektów klienta? Istnieją uzasadnione przypadki użycia dla jednego z nich. Ale ICloneable nie wyjaśnia, który dostaniesz. Dlatego nie jest użyteczny.
Ryan Lundy,

@ Kyralessa Artykuł Microsoft MSDN w rzeczywistości stwierdza ten problem polegający na tym, że nie wiadomo, czy żądana jest głęboka czy płytka kopia.
zmiażdżyć

Odpowiedź ze zduplikowanego stackoverflow.com/questions/129389/... opisuje rozszerzenie Kopiuj na podstawie rekurencyjnego MembershipClone
Michael Freidgeim

123

Po wielu lekturach na temat wielu połączonych tutaj opcji i możliwych rozwiązań tego problemu, uważam, że wszystkie opcje są dość dobrze podsumowane pod linkiem Iana P. (wszystkie inne opcje są ich odmianami), a najlepsze rozwiązanie zapewnia Pedro77 link, na komentarzach zapytania.

Więc po prostu skopiuję odpowiednie części tych 2 referencji tutaj. W ten sposób możemy mieć:

Najlepszą rzeczą do klonowania obiektów w Cis!

Przede wszystkim są to wszystkie nasze opcje:

W artykule Szybka, głęboka kopia według drzew wyrażeń zawiera również porównanie wydajności klonowania przez drzewa serializacji, odbicia i wyrażenia.

Dlaczego wybieram ICloneable (tj. Ręcznie)

Pan Venkat Subramaniam (zbędny link tutaj) wyjaśnia szczegółowo, dlaczego .

Cały jego artykuł krąży wokół przykładu, który próbuje znaleźć zastosowanie w większości przypadków, używając 3 obiektów: Osoba , Mózg i Miasto . Chcemy sklonować osobę, która będzie miała własny mózg, ale to samo miasto. Możesz albo zobrazować wszystkie problemy, które dowolna z powyższych metod może przynieść, albo przeczytać artykuł.

Oto moja nieznacznie zmodyfikowana wersja jego wniosku:

Kopiowanie obiektu przez podanie Newnazwy klasy często prowadzi do kodu, którego nie można rozszerzyć. Zastosowanie sklonowanego wzoru prototypowego jest lepszym sposobem na osiągnięcie tego. Jednak użycie klonowania w wersji C # (i Java) może być również dość problematyczne. Lepiej jest zapewnić chroniony (niepubliczny) konstruktor kopii i wywołać go z metody klonowania. Daje nam to możliwość przekazania zadania tworzenia obiektu do instancji samej klasy, zapewniając w ten sposób rozszerzalność, a także bezpieczne tworzenie obiektów za pomocą chronionego konstruktora kopii.

Mamy nadzieję, że ta implementacja wyjaśni wszystko:

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

Teraz rozważ, aby klasa pochodziła 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();
    }
}

Możesz spróbować uruchomić następujący kod:

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

Wytworzona produkcja będzie:

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

Zauważ, że jeśli utrzymamy liczbę obiektów, zastosowany tutaj klon zachowa prawidłową liczbę obiektów.


6
MS zaleca, aby nie używać ICloneabledla członków publicznych. „Ponieważ osoby wywołujące klon nie mogą polegać na metodzie wykonującej przewidywalną operację klonowania, zalecamy, aby ICloneable nie był implementowany w publicznych interfejsach API”. msdn.microsoft.com/en-us/library/… Jednak na podstawie wyjaśnień podanych przez Venkat Subramaniam w twoim powiązanym artykule, myślę, że warto stosować w tej sytuacji, o ile twórcy obiektów ICloneable mają głębokie zrozumienie, które właściwości powinny być głębokie a płytkie (tj. głęboka kopia Mózg, płytka kopia Miasto)
BateTech

Po pierwsze, daleko mi do eksperta w tym temacie (publiczne interfejsy API). Myślę , że choć raz ta uwaga stwardnienia rozsianego ma sens. I nie sądzę, że można bezpiecznie zakładać, że użytkownicy tego interfejsu API będą mieli tak głębokie zrozumienie. Dlatego wdrożenie go w publicznym interfejsie API ma sens tylko wtedy, gdy naprawdę nie będzie miało znaczenia, kto będzie go używał. Wydaje mi się, że posiadanie jakiegoś rodzaju UML bardzo wyraźnego rozróżnienia każdej właściwości może pomóc. Ale chciałbym usłyszeć od kogoś z większym doświadczeniem. : P
cregox 10.01.2015

Możesz użyć CGbR Clone Generator i uzyskać podobny wynik bez ręcznego pisania kodu.
Toxantron,

Przydatna jest implementacja języka pośredniego
Michael Freidgeim

Nie ma finału w C #
Konrad

84

Wolę konstruktora kopii niż klon. Cel jest wyraźniejszy.


5
.Net nie ma konstruktorów kopiowania.
Pop Catalin

48
Pewnie, że tak: new MyObject (objToCloneFrom) Wystarczy zadeklarować ctor, który przyjmuje parametr do sklonowania jako parametr.
Nick

30
To nie to samo. Musisz dodać go do każdej klasy ręcznie, a nawet nie wiesz, czy masz gwarancję głębokiej kopii.
Dave Van den Eynde

15
+1 za kopię ctor. Musisz ręcznie napisać funkcję clone () dla każdego typu obiektu i powodzenia z tym, gdy hierarchia klas osiągnie kilka poziomów.
Andrew Grant,

3
Jednak w przypadku konstruktorów kopiowania tracisz hierarchię. agiledeveloper.com/articles/cloning072002.htm
Czy

41

Prosta metoda rozszerzenia do skopiowania wszystkich właściwości publicznych. Działa dla dowolnych obiektów i nie wymaga klasy [Serializable]. Można rozszerzyć o inny poziom dostępu.

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

15
Jest to niestety wadliwe. Jest to równoważne z wywołaniem objectOne.MyProperty = objectTwo.MyProperty (tzn. Po prostu skopiuje referencję). Nie sklonuje wartości właściwości.
Alex Norcliffe

1
do Alexa Norcliffe'a: autor pytania zadanego o „kopiowanie każdej właściwości” zamiast klonowania. w większości przypadków dokładne kopiowanie właściwości nie jest potrzebne.
Konstantin Salavatov

1
myślę o użyciu tej metody, ale z rekurencją. więc jeśli wartością właściwości jest odwołanie, utwórz nowy obiekt i ponownie wywołaj CopyTo. widzę tylko jeden problem, że wszystkie używane klasy muszą mieć konstruktor bez parametrów. Ktoś już tego próbował? Zastanawiam się także, czy to rzeczywiście będzie działać z właściwościami zawierającymi klasy .net, takie jak DataRow i DataTable?
Koryu,

33

Właśnie stworzyłem projekt CloneExtensionsbiblioteki . Wykonuje szybki, głęboki klon przy użyciu prostych operacji przypisania generowanych przez kompilację kodu środowiska wykonawczego drzewa wyrażeń.

Jak tego użyć?

Zamiast pisać własne Clonelub Copymetody z tonem przypisań między polami i właściwościami, zmuś program do zrobienia tego sam, korzystając z drzewa wyrażeń. GetClone<T>()Metoda oznaczona jako metoda rozszerzenia pozwala po prostu wywołać ją w instancji:

var newInstance = source.GetClone();

Można wybrać to, co powinno być kopiowane z sourcedo newInstancekorzystania CloningFlagsENUM:

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

Co można sklonować?

  • Prymitywne (int, uint, bajt, double, char itp.), Znane typy niezmienne (DateTime, TimeSpan, String) i delegaci (w tym Action, Func itp.)
  • Nullable
  • Tablice T []
  • Niestandardowe klasy i struktury, w tym ogólne klasy i struktury.

Następujący członkowie klasy / struktury są klonowani wewnętrznie:

  • Wartości pól publicznych, a nie tylko do odczytu
  • Wartości właściwości publicznych zarówno z get, jak i set accessors
  • Elementy kolekcji dla typów implementujących ICollection

Jak szybko to jest

Rozwiązanie jest szybsze niż refleksja, ponieważ informacje o członkach należy zebrać tylko raz, zanim GetClone<T>zostaną użyte po raz pierwszy dla danego typu T.

Jest także szybszy niż rozwiązanie oparte na serializacji, gdy klonujesz więcej niż kilka instancji tego samego typu T.

i więcej...

Przeczytaj więcej o wygenerowanych wyrażeniach w dokumentacji .

Przykładowa lista debugowania wyrażeń dla 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 ma takie samo znaczenie jak następujący kod 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;
}

Czy to nie tak, jak napisałbyś własną Clonemetodę List<int>?


2
Jakie są szanse na dostanie się na NuGet? To wydaje się najlepszym rozwiązaniem. Jak to się ma do NClone ?
zmiażdżyć

Myślę, że ta odpowiedź powinna być oceniana więcej razy. Ręczne wdrażanie ICloneable jest żmudne i podatne na błędy, stosowanie refleksji lub serializacji jest powolne, jeśli wydajność jest ważna i trzeba skopiować tysiące obiektów w krótkim czasie.
nightcoder

Wcale nie mylisz się co do refleksji, powinieneś po prostu odpowiednio buforować to. Sprawdź moją odpowiedź poniżej stackoverflow.com/a/34368738/4711853
Roma Borodov

31

Cóż, miałem problemy z używaniem ICloneable w Silverlight, ale podobał mi się pomysł serializacji, mogę seralizować XML, więc zrobiłem to:

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

31

Jeśli już używasz aplikacji innej firmy, takiej jak ValueInjecter lub Automapper , możesz zrobić coś takiego:

MyObject oldObj; // The existing object to clone

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

Używając tej metody nie musisz implementować ISerializableani ICloneablena swoich obiektach. Jest to powszechne w przypadku wzorca MVC / MVVM, dlatego stworzono takie proste narzędzia, jak ten.

zobacz próbkę głębokiego klonowania ValueInjecter na GitHub .


25

Najlepiej jest zaimplementować metodę rozszerzenia taką jak

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

a następnie użyj go w dowolnym miejscu rozwiązania przez

var copy = anyObject.DeepClone();

Możemy mieć następujące trzy implementacje:

  1. Według serializacji (najkrótszy kod)
  2. Przez odbicie - 5 razy szybciej
  3. Drzewa ekspresji - 20x szybciej

Wszystkie powiązane metody działają dobrze i zostały głęboko przetestowane.


klonowanie kodu przy użyciu drzew wyrażeń, które opublikowałeś codeproject.com/Articles/1111658/... , kończy się niepowodzeniem w nowszych wersjach frameworku .Net z wyjątkiem bezpieczeństwa, Operacja może zdestabilizować środowisko wykonawcze , jest to w zasadzie wyjątek ze względu na zniekształcone drzewo wyrażeń, który jest używany do generowania Func w czasie wykonywania, sprawdź, czy masz jakieś rozwiązanie. W rzeczywistości widziałem problem tylko ze złożonymi obiektami o głębokiej hierarchii, prostymi, które łatwo można skopiować
Mrinal Kamboj

1
Implementacja ExpressionTree wydaje się bardzo dobra. Działa nawet z okólnikami i członkami prywatnymi. Nie wymaga atrybutów. Najlepsza odpowiedź, jaką znalazłem.
N73k

Najlepsza odpowiedź, działała bardzo dobrze, uratowałeś mi dzień
Adel Mourad

23

Krótka odpowiedź brzmi: dziedziczysz po interfejsie ICloneable, a następnie implementujesz funkcję .clone. Klon powinien wykonać kopię członka i wykonać głęboką kopię na dowolnym elemencie, który tego wymaga, a następnie zwrócić wynikowy obiekt. Jest to operacja rekurencyjna (wymaga, aby wszyscy członkowie klasy, którą chcesz sklonować, byli albo typami wartości, albo implementowali ICloneable, a ich członkowie byli albo typami wartości, albo implementowali ICloneable itd.).

Bardziej szczegółowe wyjaśnienie dotyczące klonowania przy użyciu ICloneable znajduje się w tym artykule .

Długie odpowiedź brzmi „to zależy”. Jak wspomnieli inni, ICloneable nie jest obsługiwany przez generyczne, wymaga specjalnych rozważań dla odwołań do klas okrągłych i przez niektórych jest postrzegany jako „błąd” w .NET Framework. Metoda serializacji zależy od możliwości serializacji obiektów, którymi mogą nie być i możesz nie mieć nad nimi kontroli. Społeczność wciąż toczy wiele dyskusji na temat „najlepszej” praktyki. W rzeczywistości żadne z tych rozwiązań nie jest uniwersalne dla wszystkich najlepszych praktyk we wszystkich sytuacjach, w których pierwotnie interpretowano ICloneable.

Zobacz ten artykuł dla deweloperów, aby uzyskać więcej opcji (podziękowania dla Iana).


1
ICloneable nie ma ogólnego interfejsu, więc nie zaleca się korzystania z tego interfejsu.
Karg

Twoje rozwiązanie działa, dopóki nie będzie musiało obsługiwać referencji cyklicznych, a następnie sprawy zaczną się komplikować, lepiej spróbować wdrożyć głębokie klonowanie przy użyciu głębokiej serializacji.
Pop Catalin

Niestety, nie wszystkie obiekty mogą być również serializowane, więc nie zawsze możesz również użyć tej metody. Link Iana jest jak dotąd najbardziej wyczerpującą odpowiedzią.
Zach Burlingame,

19
  1. Zasadniczo musisz zaimplementować interfejs ICloneable, a następnie wykonać kopiowanie struktury obiektu.
  2. Jeśli jest to głęboka kopia wszystkich członków, musisz zapewnić (nie odnosząc się do wybranego przez ciebie rozwiązania), że wszystkie dzieci są również klonowalne.
  3. Czasami musisz być świadomy pewnych ograniczeń podczas tego procesu, na przykład jeśli kopiujesz obiekty ORM, większość ram zezwala na tylko jeden obiekt dołączony do sesji i NIE MUSISZ tworzyć klonów tego obiektu, lub jeśli to możliwe, musisz się tym przejmować o dołączaniu sesji do tych obiektów.

Twoje zdrowie.


4
ICloneable nie ma ogólnego interfejsu, więc nie zaleca się korzystania z tego interfejsu.
Karg

Proste i zwięzłe odpowiedzi są najlepsze.
DavidGuaita,

17

EDYCJA: projekt został przerwany

Jeśli chcesz prawdziwego klonowania do nieznanych typów, możesz rzucić okiem na fastclone .

To klonowanie oparte na wyrażeniach działa około 10 razy szybciej niż serializacja binarna i zachowuje pełną spójność grafu obiektowego.

Oznacza to, że: jeśli wielokrotnie odwołasz się do tego samego obiektu w hierarchii, do klonu będzie również przypisana jedna instancja.

Nie ma potrzeby tworzenia interfejsów, atrybutów ani innych modyfikacji klonowanych obiektów.


Ten wydaje się bardzo przydatny
LuckyLikey

Łatwiej jest rozpocząć pracę od jednej migawki kodu niż dla całego systemu, zwłaszcza zamkniętego. Zrozumiałe jest, że żadna biblioteka nie jest w stanie rozwiązać wszystkich problemów za jednym razem. Należy się odprężyć.
TarmoPikaro

1
Wypróbowałem twoje rozwiązanie i wydaje się, że działa dobrze, dzięki! Myślę, że ta odpowiedź powinna być oceniana więcej razy. Ręczne wdrażanie ICloneable jest żmudne i podatne na błędy, stosowanie refleksji lub serializacji jest powolne, jeśli wydajność jest ważna i trzeba skopiować tysiące obiektów w krótkim czasie.
nightcoder

Próbowałem i nie działało to wcale dla mnie. Zgłasza wyjątek MemberAccess.
Michael Brown

Nie działa z nowszymi wersjami .NET i został wycofany
Michael Sander,

14

Uprość i używaj AutoMapper, jak wspomniano inni, to prosta biblioteka do mapowania jednego obiektu na inny ... Aby skopiować obiekt na inny tego samego typu, wystarczy trzy linie kodu:

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

Obiekt docelowy jest teraz kopią obiektu źródłowego. Nie dość proste? Utwórz metodę rozszerzenia do użycia wszędzie w swoim rozwiązaniu:

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

Metodę rozszerzenia można zastosować w następujący sposób:

MyType copy = source.Copy();

Uważaj na ten, działa naprawdę słabo. Skończyło się na przejściu na odpowiedź Johna, która jest tak krótka jak ta i działa znacznie lepiej.
Agorilla,

1
Robi to tylko płytką kopię.
N73k

11

Wymyśliłem to, aby przezwyciężyć niedociągnięcie platformy .NET polegające na ręcznym głębokim kopiowaniu listy <T>.

Używam tego:

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

I w innym miejscu:

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

Próbowałem wymyślić oneliner, który to robi, ale nie jest to możliwe, ponieważ wydajność nie działa w anonimowych blokach metod.

Jeszcze lepiej, użyj ogólnego klonera 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

P: Dlaczego miałbym wybrać tę odpowiedź?

  • Wybierz tę odpowiedź, jeśli chcesz najszybszej prędkości .NET.
  • Zignoruj ​​tę odpowiedź, jeśli chcesz naprawdę bardzo łatwej metody klonowania.

Innymi słowy, idź z inną odpowiedzią, chyba że masz wąskie gardło wydajności, które wymaga naprawy, i możesz to udowodnić za pomocą profilera .

10 razy szybszy niż inne metody

Następująca metoda wykonywania głębokiego klonowania to:

  • 10 razy szybszy niż cokolwiek, co wymaga serializacji / deserializacji;
  • Całkiem blisko teoretycznej maksymalnej prędkości .NET jest w stanie.

I metoda ...

Aby uzyskać najwyższą prędkość, możesz użyć Nested MemberwiseClone, aby wykonać głęboką kopię . Ma prawie taką samą szybkość jak kopiowanie struktury wartości i jest znacznie szybszy niż (a) odbicie lub (b) serializacja (jak opisano w innych odpowiedziach na tej stronie).

Zauważ, że jeśli używasz Nested MemberwiseClone do głębokiej kopii , musisz ręcznie wdrożyć ShallowCopy dla każdego poziomu zagnieżdżonego w klasie oraz DeepCopy, który wywołuje wszystkie wspomniane metody ShallowCopy w celu utworzenia pełnego klonu. To proste: w sumie tylko kilka wierszy, zobacz poniższy kod demonstracyjny.

Oto wynik kodu pokazującego względną różnicę wydajności dla 100 000 klonów:

  • 1,08 sekundy dla Nested MemberwiseClone na zagnieżdżonych strukturach
  • 4,77 sekundy dla Nested MemberwiseClone w klasach zagnieżdżonych
  • 39,93 sekundy na serializację / deserializację

Używanie Nested MemberwiseClone na klasie prawie tak szybko, jak kopiowanie struktury, a kopiowanie struktury jest dość zbliżone do teoretycznej maksymalnej prędkości, jaką potrafi .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

Aby zrozumieć, jak wykonać głęboką kopię przy użyciu MemberwiseCopy, oto projekt demonstracyjny, który został użyty do wygenerowania powyższych czasów:

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

Następnie wywołaj demo z 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();
}

Ponownie zauważ, że jeśli używasz Nested MemberwiseClone do głębokiej kopii , musisz ręcznie wdrożyć ShallowCopy dla każdego poziomu zagnieżdżonego w klasie oraz DeepCopy, który wywołuje wszystkie wymienione metody ShallowCopy w celu utworzenia pełnego klonu. To proste: w sumie tylko kilka wierszy, patrz powyższy kod demonstracyjny.

Typy wartości a typy referencji

Zauważ, że jeśli chodzi o klonowanie obiektu, istnieje duża różnica między „ strukturą ” a „ klasą ”:

  • Jeśli masz „ struct ”, jest to typ wartości, więc możesz go po prostu skopiować, a zawartość zostanie sklonowana (ale utworzy tylko płytki klon, chyba że użyjesz technik opisanych w tym poście).
  • Jeśli masz „ klasę ”, jest to typ referencyjny , więc jeśli ją skopiujesz, wystarczy skopiować do niej wskaźnik. Aby stworzyć prawdziwy klon, musisz być bardziej kreatywny i używać różnic między typami wartości i typami referencji, które tworzą kolejną kopię oryginalnego obiektu w pamięci.

Zobacz różnice między typami wartości a typami referencji .

Sumy kontrolne ułatwiające debugowanie

  • Niepoprawne klonowanie obiektów może prowadzić do bardzo trudnych do zlokalizowania błędów. W kodzie produkcyjnym mam tendencję do implementowania sumy kontrolnej w celu podwójnego sprawdzenia, czy obiekt został poprawnie sklonowany i nie został uszkodzony przez inne odniesienie do niego. Tę sumę kontrolną można wyłączyć w trybie zwolnienia.
  • Uważam tę metodę za bardzo przydatną: często chcesz sklonować tylko części obiektu, a nie całość.

Naprawdę przydatne do oddzielania wielu wątków od wielu innych wątków

Doskonałym przykładem użycia tego kodu jest podawanie klonów zagnieżdżonej klasy lub struktury do kolejki w celu zaimplementowania wzorca producent / konsument.

  • Możemy mieć jeden (lub więcej) wątków modyfikujących klasę, której są właścicielami, a następnie wypychających pełną kopię tej klasy do pliku ConcurrentQueue .
  • Następnie mamy jeden (lub więcej) wątków wyciągających kopie tych klas i radzących sobie z nimi.

Działa to wyjątkowo dobrze w praktyce i pozwala nam oddzielić wiele wątków (producentów) od jednego lub więcej wątków (konsumentów).

Ta metoda jest również niesamowicie szybka: jeśli użyjemy zagnieżdżonych struktur, jest 35 razy szybsza niż serializacja / deserializacja zagnieżdżonych klas i pozwala nam skorzystać ze wszystkich wątków dostępnych na maszynie.

Aktualizacja

Najwyraźniej ExpressMapper jest tak szybki, jeśli nie szybszy, niż ręczne kodowanie, takie jak powyżej. Może będę musiał zobaczyć, jak porównują się z profilerem.


Jeśli skopiujesz strukturę, otrzymasz płytką kopię, nadal możesz potrzebować konkretnej implementacji dla głębokiej kopii.
Lasse V. Karlsen

@Lasse V. Karlsen. Tak, masz absolutną rację, zaktualizowałem odpowiedź, aby to wyjaśnić. Ta metoda może być używana do wykonywania głębokich kopii struktur i klas. Możesz uruchomić dołączony przykładowy kod demonstracyjny, aby pokazać, jak to zrobiono, zawiera przykład głębokiego klonowania zagnieżdżonej struktury i inny przykład głębokiego klonowania zagnieżdżonej klasy.
Contango,

9

Zasadniczo implementujesz interfejs ICloneable i sam klonujesz. Obiekty C # mają wbudowaną metodę MemberwiseClone, która wykonuje płytką kopię, która może pomóc ci w przypadku wszystkich prymitywów.

W przypadku głębokiej kopii nie ma możliwości, aby wiedzieć, jak to zrobić automatycznie.


ICloneable nie ma ogólnego interfejsu, więc nie zaleca się korzystania z tego interfejsu.
Karg

8

Widziałem to także poprzez refleksję. Zasadniczo istniała metoda, która iterowałaby przez elementy obiektu i odpowiednio kopiowała je do nowego obiektu. Kiedy osiągnął typy referencyjne lub kolekcje, myślę, że wykonał rekurencyjne wywołanie. Odbicie jest drogie, ale działało całkiem dobrze.


8

Oto implementacja głębokiej kopii:

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

2
Wygląda to na członka klonu, ponieważ nie zna właściwości typu odwołania
sll

1
Jeśli chcesz oślepiająco szybkiej wydajności, nie wybieraj tej implementacji: używa refleksji, więc nie będzie tak szybko. I odwrotnie, „przedwczesna optymalizacja jest złem”, więc ignoruj ​​stronę wydajności, dopóki nie uruchomisz profilera.
Contango,

1
CreateInstanceOfType nie jest zdefiniowany?
MonsterMMORPG

Nie udaje się przy intergerze: „Metoda niestatyczna wymaga celu”.
Mr.B

8

Ponieważ nie mogłem znaleźć klonera, który spełniałby wszystkie moje wymagania w różnych projektach, stworzyłem głębokiego klonera, który można skonfigurować i dostosować do różnych struktur kodu zamiast dostosowywać mój kod, aby spełniał wymagania klonerów. Osiąga się to poprzez dodanie adnotacji do kodu, który należy sklonować, lub po prostu zostawiasz kod tak, jak ma to być zachowanie domyślne. Wykorzystuje odbicia, typy pamięci podręcznych i jest oparty na szybszym odbiciu . Proces klonowania jest bardzo szybki dla ogromnej ilości danych i wysokiej hierarchii obiektów (w porównaniu do innych algorytmów opartych na odbiciu / serializacji).

https://github.com/kalisohn/CloneBehave

Dostępny również jako pakiet nuget: https://www.nuget.org/packages/Clone.Behave/1.0.0

Na przykład: Poniższy kod będzie głębokoClone Adres, ale wykona tylko płytką kopię pola _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

Generator kodów

Widzieliśmy wiele pomysłów od serializacji poprzez ręczne wdrożenie do refleksji i chcę zaproponować zupełnie inne podejście przy użyciu CGbR Code Generator . Metoda generowania klonów jest wydajna pod względem pamięci i procesora, a zatem 300 razy szybsza niż standardowy DataContractSerializer.

Wystarczy częściowa definicja klasy, ICloneablea generator zajmie się resztą:

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

Uwaga: najnowsza wersja ma bardziej zerowe kontrole, ale pominąłem je dla lepszego zrozumienia.


6

Lubię takie 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));
        }
    }

Jeśli masz więcej rzeczy do skopiowania, dodaj je


6

Ta metoda rozwiązała dla mnie problem:

private static MyObj DeepCopy(MyObj source)
        {

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

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

        }

Użyj tego w ten sposób: MyObj a = DeepCopy(b);


6

Tutaj rozwiązanie jest szybkie i łatwe, które działało dla mnie bez konieczności przechodzenia do serializacji / deserializacji.

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

EDYCJA : wymaga

    using System.Linq;
    using System.Reflection;

Tak to wykorzystałem

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

5

Wykonaj następujące kroki:

  • Zdefiniuj właściwość ISelf<T>tylko do odczytu, Selfktóra zwraca T, i ICloneable<out T>która pochodzi z ISelf<T>metody i obejmuje ją T Clone().
  • Następnie zdefiniuj CloneBasetyp, który implementuje protected virtual generic VirtualClonerzutowanie MemberwiseClonedo typu przekazywanego.
  • Każdy typ pochodny powinien zostać zaimplementowany VirtualCloneprzez wywołanie metody podstawowego klonowania, a następnie wykonanie wszelkich czynności, które należy zrobić, aby poprawnie sklonować te aspekty typu pochodnego, których nadrzędna metoda VirtualClone jeszcze nie obsłużyła.

Aby uzyskać maksymalną wszechstronność dziedziczenia, klasy udostępniające publiczną funkcję klonowania powinny być sealed, ale wywodzą się z klasy bazowej, która poza tym jest identyczna, z wyjątkiem braku klonowania. Zamiast przekazywać zmienne jawnego typu klonowalnego, weź parametr typu ICloneable<theNonCloneableType>. Pozwoli to rutynie, która oczekuje, że klonowalna pochodna Foobędzie działać z klonowalną pochodną DerivedFoo, ale także pozwoli na tworzenie nie klonowalnych pochodnych Foo.


5

Myślę, że możesz spróbować.

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

4

Utworzyłem wersję zaakceptowanej odpowiedzi, która działa zarówno z „[Serializable]”, jak i „[DataContract]”. Minęło trochę czasu, odkąd go napisałem, ale jeśli dobrze pamiętam [DataContract] potrzebowałem innego serializatora.

Wymaga System, 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;
    }

} 

4

Ok, jest jakiś oczywisty przykład z odbiciem w tym poście, ALE odbicie jest zwykle wolne, dopóki nie zaczniesz go odpowiednio buforować.

jeśli odpowiednio go zbuforujesz, spowoduje to głębokie sklonowanie obiektu 1000000 o 4,6s (mierzone przez Watchera).

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

niż bierzesz właściwości z pamięci podręcznej lub dodajesz nowe do słownika i używasz ich po prostu

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

pełny kod sprawdź w moim poście w innej odpowiedzi

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


2
Połączenia prop.GetValue(...)nadal są odbiciem i nie mogą być buforowane. Jednak w drzewie wyrażeń jest ono skompilowane, tak szybko
Tseng

4

Ponieważ prawie wszystkie odpowiedzi na to pytanie są niezadowalające lub po prostu nie działają w mojej sytuacji, napisałem AnyClone, który został w pełni wdrożony z refleksją i rozwiązał wszystkie potrzeby tutaj. Nie byłem w stanie uzyskać serializacji do pracy w skomplikowanym scenariuszu o złożonej strukturze i IClonablejest mniej niż idealny - w rzeczywistości nie powinno to nawet być konieczne.

Standardowe ignorować atrybuty są obsługiwane za pomocą [IgnoreDataMember], [NonSerialized]. Obsługuje złożone kolekcje, właściwości bez ustawiaczy, pola tylko do odczytu itp.

Mam nadzieję, że pomoże to komuś innemu, kto miał takie same problemy jak ja.


4

Oświadczenie: Jestem autorem wspomnianego pakietu.

Byłem zaskoczony, jak najlepsze odpowiedzi na to pytanie w 2019 roku nadal używają serializacji lub refleksji.

Serializacja jest ograniczona (wymaga atrybutów, określonych konstruktorów itp.) I jest bardzo wolna

BinaryFormatterwymaga Serializableatrybutu, JsonConverterwymaga konstruktora lub atrybutów bez parametrów, ani nie obsługuje bardzo dobrze pól ani interfejsów tylko do odczytu, a oba są 10-30x wolniejsze niż to konieczne.

Drzewa ekspresji

Zamiast tego możesz użyć drzew wyrażeń lub Reflection.Emit, aby wygenerować kod klonujący tylko raz, a następnie użyć skompilowanego kodu zamiast powolnego odzwierciedlania lub serializacji.

Po tym, jak sam spotkałem się z problemem i nie znalazłem zadowalającego rozwiązania, postanowiłem stworzyć pakiet, który właśnie to działa i działa z każdym typem i jest prawie tak szybki, jak niestandardowy kod napisany .

Możesz znaleźć projekt na GitHub: https://github.com/marcelltoth/ObjectCloner

Stosowanie

Możesz zainstalować go z NuGet. Pobierz ObjectClonerpakiet i użyj go jako:

var clone = ObjectCloner.DeepClone(original);

lub jeśli nie masz nic przeciwko zanieczyszczeniu typu obiektu rozszerzeniami, pobierz ObjectCloner.Extensionsrównież i napisz:

var clone = original.DeepClone();

Wydajność

Prosty test klonowania hierarchii klas wykazał wydajność ~ 3-krotnie szybszą niż użycie Reflection, ~ 12-krotnie szybszą niż Newtonsoft. Serializacja Jsona i ~ 36-krotnie szybsza niż wysoce sugerowana BinaryFormatter.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.