it-swarm-id.com

Objek kloning yang dalam

Saya ingin melakukan sesuatu seperti:

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

Dan kemudian membuat perubahan pada objek baru yang tidak tercermin pada objek asli.

Saya tidak sering membutuhkan fungsi ini, jadi ketika itu diperlukan, saya terpaksa membuat objek baru dan kemudian menyalin setiap properti secara individual, tetapi selalu membuat saya merasa bahwa ada cara penanganan yang lebih baik atau lebih elegan. situasi.

Bagaimana saya bisa mengkloning atau menyalin objek dalam sehingga objek yang dikloning dapat dimodifikasi tanpa perubahan yang tercermin pada objek asli?

2028
NakedBrunch

Sementara praktik standar adalah untuk mengimplementasikan antarmuka ICloneable (dijelaskan di sini , jadi saya tidak akan memuntahkan), inilah mesin fotokopi objek tiruan dalam yang saya temukan di Proyek Kode beberapa waktu yang lalu dan memasukkannya ke dalam barang-barang kami.

Seperti disebutkan di tempat lain, itu memang membutuhkan objek Anda menjadi serial.

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

Idenya adalah bahwa ia membuat serial objek Anda dan kemudian deserializes itu menjadi objek baru. Manfaatnya adalah Anda tidak perlu mengkhawatirkan diri sendiri tentang mengkloning segala sesuatu ketika suatu objek menjadi terlalu kompleks.

Dan dengan menggunakan metode ekstensi (juga dari sumber yang dirujuk sebelumnya):

Jika Anda lebih suka menggunakan metode ekstensi baru dari C # 3.0, ubah metode untuk memiliki tanda tangan berikut:

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

Sekarang pemanggilan metode menjadi objectBeingCloned.Clone();.

SUNTING(10 Januari 2015) Saya pikir saya akan kembali ini, untuk menyebutkan saya baru-baru ini mulai menggunakan (Newtonsoft) Json untuk melakukan ini, itu harus lebih ringan, dan menghindari overhead [ Tag yang dapat diserialisasi]. (NB@atconway telah menunjukkan dalam komentar bahwa anggota pribadi tidak dikloning menggunakan metode 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

Saya ingin cloner untuk objek yang sangat sederhana yang kebanyakan primitif dan daftar. Jika objek Anda di luar kotak JSON serializable maka metode ini akan melakukan trik. Ini tidak memerlukan modifikasi atau implementasi antarmuka pada kelas kloning, hanya serializer JSON seperti JSON.NET.

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

Anda juga dapat menggunakan metode ekstensi ini

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

Alasan untuk tidak menggunakan ICloneable adalah not karena tidak memiliki antarmuka generik. Alasan untuk tidak menggunakannya adalah karena tidak jelas . Tidak memperjelas apakah Anda mendapatkan salinan yang dangkal atau dalam; itu terserah pelaksana.

Ya, MemberwiseClone membuat salinan dangkal, tetapi kebalikan dari MemberwiseClone bukanlah Clone; mungkin, DeepClone, yang tidak ada. Saat Anda menggunakan objek melalui antarmuka ICloneable, Anda tidak bisa mengetahui jenis kloning yang dilakukan objek yang mendasarinya. (Dan komentar XML tidak akan membuatnya lebih jelas, karena Anda akan mendapatkan komentar antarmuka daripada yang ada di metode Klon objek.)

Apa yang biasanya saya lakukan hanyalah membuat metode Copy yang melakukan persis apa yang saya inginkan.

162
Ryan Lundy

Setelah banyak membaca tentang banyak opsi yang ditautkan di sini, dan kemungkinan solusi untuk masalah ini, saya percaya semua opsi dirangkum dengan cukup baik di tautan Ian P (semua opsi lain adalah variasi dari semua opsi tersebut. ) dan solusi terbaik disediakan oleh tautan Pedro77 pada komentar pertanyaan.

Jadi saya hanya akan menyalin bagian yang relevan dari 2 referensi tersebut di sini. Dengan begitu kita dapat memiliki:

Hal terbaik untuk dilakukan untuk mengkloning objek dengan tajam!

Pertama dan terutama, itu semua adalah pilihan kami:

The artikel Fast Deep Copy oleh Pohon Ekspresi juga memiliki perbandingan kinerja kloning oleh Serialisasi, Refleksi dan Pohon Ekspresi.

Mengapa saya memilih ICloneable (yaitu secara manual)

Mr Venkat Subramaniam (tautan berlebihan di sini) menjelaskan secara lebih rinci mengapa .

Semua artikelnya melingkari contoh yang mencoba berlaku untuk sebagian besar kasus, menggunakan 3 objek: Person, Brain dan City. Kami ingin mengkloning seseorang, yang akan memiliki otak sendiri tetapi kota yang sama. Anda dapat menggambarkan semua masalah yang ada di antara metode lain di atas yang dapat membawa atau membaca artikel.

Ini adalah versi kesimpulan saya yang sedikit dimodifikasi:

Menyalin objek dengan menentukan New diikuti dengan nama kelas sering mengarah ke kode yang tidak dapat diperluas. Menggunakan clone, aplikasi pola prototipe, adalah cara yang lebih baik untuk mencapai ini. Namun, menggunakan clone seperti yang disediakan di C # (dan Java) bisa sangat bermasalah juga. Lebih baik menyediakan konstruktor salinan yang dilindungi (non-publik) dan memohonnya dari metode klon. Ini memberi kita kemampuan untuk mendelegasikan tugas membuat objek ke instance kelas itu sendiri, sehingga memberikan ekstensibilitas dan juga, dengan aman membuat objek menggunakan copy constructor yang dilindungi.

Semoga implementasi ini dapat memperjelas:

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

Sekarang pertimbangkan untuk memiliki kelas yang berasal dari Person.

public class SkilledPerson extends Person
{
    private String theSkills;
    public SkilledPerson(Brain aBrain, int theAge, String skills)
    {
        super(aBrain, theAge);
        theSkills = skills;
    }
    protected SkilledPerson(SkilledPerson another)
    {
        super(another);
        theSkills = another.theSkills;
    }

    public Object clone()
    {
        return new SkilledPerson(this);
    }
    public String toString()
    {
        return "SkilledPerson: " + super.toString();
    }
}

Anda dapat mencoba menjalankan kode berikut:

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

Output yang dihasilkan adalah:

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]

Perhatikan bahwa, jika kita menyimpan hitungan jumlah objek, klon seperti yang diterapkan di sini akan menyimpan jumlah yang benar dari jumlah objek.

102
cregox

Saya lebih suka copy constructor daripada clone. Maksudnya lebih jelas.

77
Nick

Metode ekstensi sederhana untuk menyalin semua properti publik. Berfungsi untuk objek apa pun dan tidak mengharuskan kelas menjadi [Serializable]. Dapat diperpanjang untuk tingkat akses lainnya.

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

Yah saya mengalami masalah menggunakan ICloneable di Silverlight, tapi saya menyukai ide seralisasi, saya dapat melakukan seralize XML, jadi saya melakukan ini:

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

Saya baru saja membuatCloneExtensions libraryproject. Ini melakukan klon yang cepat dan dalam menggunakan operasi penugasan sederhana yang dihasilkan oleh kompilasi kode runtime Expression Tree.

Bagaimana cara menggunakannya?

Alih-alih menulis metode Clone atau Copy Anda sendiri dengan nada penugasan antara bidang dan properti, buatlah program melakukannya sendiri, menggunakan Expression Tree. Metode GetClone<T>() yang ditandai sebagai metode ekstensi memungkinkan Anda memanggilnya dengan instan:

var newInstance = source.GetClone();

Anda dapat memilih apa yang harus disalin dari source ke newInstance menggunakan CloningFlags enum:

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

Apa yang bisa dikloning?

  • Primitive (int, uint, byte, double, char, dll.), Tipe yang dikenal abadi (DateTime, TimeSpan, String) dan delegasi (termasuk Action, Func, dll)
  • Tidak dapat dibatalkan
  • T [] array
  • Kelas dan struct khusus, termasuk kelas dan struct umum.

Anggota kelas/struct berikut dikloning secara internal:

  • Nilai bidang publik, bukan bidang baca saja
  • Nilai properti publik dengan akses dan pengaturan akses
  • Barang koleksi untuk jenis yang menerapkan ICollection

Seberapa cepat?

Solusinya lebih cepat daripada refleksi, karena informasi anggota harus dikumpulkan hanya sekali, sebelum GetClone<T> digunakan untuk pertama kalinya untuk jenis yang diberikan T.

Ini juga lebih cepat daripada solusi berbasis serialisasi ketika Anda mengkloning lebih dari beberapa instance dengan tipe yang sama T.

dan banyak lagi ...

Baca lebih lanjut tentang ekspresi yang dihasilkan pada dokumentasi .

Contoh daftar debug ekspresi untuk 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:
}

}

apa yang memiliki makna yang sama seperti mengikuti kode 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;
}

Bukankah itu cukup seperti bagaimana Anda akan menulis metode Clone Anda sendiri untuk List<int>?

27
MarcinJuraszek

Jika Anda sudah menggunakan aplikasi pihak ke-3 seperti ValueInjecter atau Automapper , Anda dapat melakukan sesuatu seperti ini:

MyObject oldObj; // The existing object to clone

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

Menggunakan metode ini Anda tidak harus mengimplementasikan ISerializable atau ICloneable pada objek Anda. Ini umum dengan pola MVC/MVVM, jadi alat sederhana seperti ini telah dibuat.

lihat solusi kloning dalam valueinjecter pada CodePlex .

26
Michael Cox

Jawaban singkatnya adalah Anda mewarisi dari antarmuka ICloneable dan kemudian mengimplementasikan fungsi .clone. Klon harus melakukan copy yang memberwise dan melakukan copy yang dalam pada member yang membutuhkannya, lalu mengembalikan objek yang dihasilkan. Ini adalah operasi rekursif (mengharuskan semua anggota kelas yang ingin Anda klon adalah tipe nilai atau mengimplementasikan ICloneable dan anggotanya adalah tipe nilai atau mengimplementasikan ICloneable, dan sebagainya).

Untuk penjelasan lebih rinci tentang Kloning menggunakan ICloneable, lihat artikel ini .

long answer adalah "itu tergantung". Seperti disebutkan oleh orang lain, ICloneable tidak didukung oleh obat generik, memerlukan pertimbangan khusus untuk referensi kelas melingkar, dan sebenarnya dipandang oleh beberapa orang sebagai "kesalahan" dalam .NET Framework. Metode serialisasi tergantung pada objek Anda yang dapat serial, yang mungkin tidak dan Anda mungkin tidak memiliki kendali atas. Masih ada banyak perdebatan di masyarakat yang merupakan praktik "terbaik". Pada kenyataannya, tidak ada solusi yang cocok untuk semua praktik terbaik untuk semua situasi seperti ICloneable yang awalnya ditafsirkan.

Lihat artikel ini Pojok Pengembang untuk beberapa opsi lainnya (dikreditkan ke Ian).

20
Zach Burlingame

Yang terbaik adalah menerapkan metode extension like

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

dan kemudian menggunakannya di mana saja dalam solusi oleh

var copy = anyObject.DeepClone();

Kami dapat memiliki tiga implementasi berikut:

  1. Dengan Serialisasi (kode terpendek)
  2. Dengan Refleksi - 5x lebih cepat
  3. Dengan Pohon Ekspresi - 20x lebih cepat

Semua metode yang terhubung bekerja dengan baik dan diuji secara mendalam.

19
frakon
  1. Pada dasarnya Anda perlu mengimplementasikan antarmuka ICloneable dan kemudian mewujudkan menyalin struktur objek.
  2. Jika ini adalah salinan mendalam dari semua anggota, Anda perlu memastikan (tidak berkaitan dengan solusi yang Anda pilih) bahwa semua anak juga dapat dikloning.
  3. Kadang-kadang Anda perlu mengetahui beberapa pembatasan selama proses ini, misalnya jika Anda menyalin objek ORM sebagian besar kerangka kerja memungkinkan hanya satu objek yang melekat pada sesi dan Anda TIDAK HARUS membuat klon objek ini, atau jika mungkin Anda perlu peduli tentang sesi melampirkan benda-benda ini.

Tepuk tangan.

16
dimarzionist

Jika Anda ingin kloning yang benar untuk tipe yang tidak dikenal, Anda dapat melihat fastclone .

Itu kloning berbasis ekspresi bekerja sekitar 10 kali lebih cepat dari serialisasi biner dan mempertahankan integritas grafik objek lengkap.

Itu berarti: jika Anda merujuk beberapa kali ke objek yang sama dalam hierachy Anda, klon juga akan memiliki satu instance menjadi referensi.

Tidak perlu antarmuka, atribut, atau modifikasi lainnya pada objek yang dikloning.

15
Michael Sander

Buat semuanya tetap sederhana dan gunakan AutoMapper seperti yang disebutkan orang lain, ini adalah perpustakaan kecil sederhana untuk memetakan satu objek ke objek lain ... Untuk menyalin objek ke objek lain dengan tipe yang sama, yang Anda butuhkan adalah tiga baris kode:

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

Objek target sekarang merupakan salinan dari objek sumber. Tidak cukup sederhana? Buat metode ekstensi untuk digunakan di mana saja dalam solusi Anda:

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

Dengan menggunakan metode ekstensi, tiga baris menjadi satu baris:

MyType copy = source.Copy();
11
Stacked

Saya datang dengan ini untuk mengatasi .NET kekurangan harus secara manual Daftar dalam copy <T>.

Saya menggunakan ini:

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

Dan di tempat lain:

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

Saya mencoba untuk membuat oneliner yang melakukan ini, tetapi itu tidak mungkin, karena hasil tidak bekerja di dalam blok metode anonim.

Lebih baik lagi, gunakan Daftar generik <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

Q. Mengapa saya memilih jawaban ini?

  • Pilih jawaban ini jika Anda menginginkan kecepatan .NET mampu.
  • Abaikan jawaban ini jika Anda menginginkan metode kloning yang sangat, sangat mudah.

Dengan kata lain, lanjutkan dengan jawaban lain kecuali jika Anda memiliki hambatan kinerja yang perlu diperbaiki, dan Anda dapat membuktikannya dengan profiler .

10x lebih cepat dari metode lain

Metode melakukan klon mendalam adalah sebagai berikut:

  • 10x lebih cepat dari apa pun yang melibatkan serialisasi/deserialisasi;
  • Cukup sangat dekat dengan kecepatan maksimum teoritis. NET mampu.

Dan metodenya ...

Untuk kecepatan tertinggi, Anda dapat menggunakan Nested MemberwiseClone untuk melakukan penyalinan dalam . Kecepatannya hampir sama dengan menyalin struct nilai, dan jauh lebih cepat daripada (a) refleksi atau (b) serialisasi (seperti yang dijelaskan dalam jawaban lain di halaman ini).

Perhatikan bahwa jika Anda menggunakan Nested MemberwiseClone untuk salinan yang dalam , Anda harus mengimplementasikan ShallowCopy secara manual untuk setiap level bersarang di kelas, dan DeepCopy yang memanggil semua metode ShallowCopy untuk membuat klon lengkap. Ini sederhana: total hanya beberapa baris, lihat kode demo di bawah ini.

Berikut adalah output dari kode yang menunjukkan perbedaan kinerja relatif untuk 100.000 klon:

  • 1,08 detik untuk Nested MemberwiseClone pada struct bersarang
  • 4,77 detik untuk Nested MemberwiseClone pada kelas bersarang
  • 39,93 detik untuk Serialization/Deserialization

Menggunakan Nested MemberwiseClone di kelas hampir secepat menyalin struct, dan menyalin struct sangat sangat dekat dengan kecepatan maksimum teoritis. NET mampu.

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

Untuk memahami cara melakukan penyalinan dalam menggunakan MemberwiseCopy, berikut adalah proyek demo yang digunakan untuk menghasilkan waktu di atas:

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

Kemudian, panggil demo dari utama:

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

Sekali lagi, perhatikan bahwa jika Anda menggunakan Nested MemberwiseClone untuk salinan yang dalam , Anda harus mengimplementasikan ShallowCopy secara manual untuk setiap level bersarang di kelas, dan DeepCopy yang memanggil semua metode ShallowCopy yang dikatakan untuk membuat klon lengkap. Ini sederhana: total hanya beberapa baris, lihat kode demo di atas.

Jenis nilai vs. Jenis Referensi

Perhatikan bahwa ketika datang untuk mengkloning suatu objek, ada perbedaan besar antara " struct " dan a " class ":

  • Jika Anda memiliki " struct ", itu adalah tipe nilai jadi Anda bisa menyalinnya, dan konten akan dikloning (tetapi itu hanya akan membuat klon dangkal kecuali Anda menggunakan teknik dalam posting ini).
  • Jika Anda memiliki " class ", ini adalah tipe referensi , jadi jika Anda menyalinnya, yang Anda lakukan hanyalah menyalin pointer ke sana. Untuk membuat klon yang benar, Anda harus lebih kreatif, dan menggunakan perbedaan antara tipe nilai dan tipe referensi yang membuat salinan lain dari objek asli dalam memori.

Lihat perbedaan antara tipe nilai dan tipe referensi .

Checksum untuk membantu dalam debugging

  • Kloning objek yang salah dapat menyebabkan bug yang sangat sulit dijabarkan. Dalam kode produksi, saya cenderung menerapkan checksum untuk memeriksa ulang apakah objek telah dikloning dengan benar, dan belum rusak oleh referensi lain untuk itu. Checksum ini dapat dimatikan dalam mode rilis.
  • Saya menemukan metode ini cukup berguna: seringkali, Anda hanya ingin mengkloning bagian dari objek, bukan keseluruhannya.

Sangat berguna untuk memisahkan banyak utas dari banyak utas lainnya

Salah satu kasus penggunaan yang sangat baik untuk kode ini adalah memberi makan klon dari kelas bersarang atau struct ke dalam antrian, untuk menerapkan pola produsen/konsumen.

  • Kita dapat memiliki satu (atau lebih) utas memodifikasi kelas yang mereka miliki, lalu mendorong salinan lengkap kelas ini ke ConcurrentQueue.
  • Kami kemudian memiliki satu (atau lebih) utas menarik salinan dari kelas-kelas ini dan menanganinya.

Ini bekerja sangat baik dalam praktiknya, dan memungkinkan kami memisahkan banyak thread (produsen) dari satu atau lebih thread (konsumen).

Dan metode ini juga sangat cepat: jika kita menggunakan struct bersarang, itu 35x lebih cepat dari serialisasi/deserializing kelas bersarang, dan memungkinkan kita untuk mengambil keuntungan dari semua utas yang tersedia pada mesin.

Memperbarui

Rupanya, ExpressMapper lebih cepat, jika tidak lebih cepat, daripada pengkodean tangan seperti di atas. Saya mungkin harus melihat bagaimana mereka membandingkan dengan profiler.

7
Contango

Saya telah melihatnya diimplementasikan melalui refleksi juga. Pada dasarnya ada metode yang akan beralih melalui anggota objek dan menyalinnya ke objek baru. Ketika mencapai jenis referensi atau koleksi saya pikir itu melakukan panggilan rekursif pada dirinya sendiri Refleksi itu mahal, tetapi itu bekerja dengan cukup baik.

7
xr280xr

Karena saya tidak dapat menemukan cloner yang memenuhi semua persyaratan saya di proyek yang berbeda, saya membuat cloner yang mendalam yang dapat dikonfigurasi dan disesuaikan dengan struktur kode yang berbeda daripada mengadaptasi kode saya untuk memenuhi persyaratan cloners. Ini dicapai dengan menambahkan anotasi ke kode yang akan dikloning atau Anda hanya meninggalkan kode karena memiliki perilaku default. Menggunakan refleksi, ketik cache dan didasarkan pada fastflect . Proses kloning sangat cepat untuk sejumlah besar data dan hierarki objek yang tinggi (dibandingkan dengan algoritma berbasis refleksi/serialisasi lainnya).

https://github.com/kalisohn/CloneBehave

Juga tersedia sebagai paket nuget: https://www.nuget.org/packages/Clone.Behave/1.0.0

Sebagai contoh: Kode berikut akan deepClone Address, tetapi hanya melakukan salinan dangkal bidang _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

Secara umum, Anda mengimplementasikan antarmuka ICloneable dan mengimplementasikan sendiri Clone. Objek C # memiliki metode MemberwiseClone bawaan yang melakukan salinan dangkal yang dapat membantu Anda keluar untuk semua primitif.

Untuk salinan yang dalam, tidak mungkin mengetahui cara melakukannya secara otomatis.

7
HappyDude

Berikut ini adalah implementasi salinan dalam:

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

Metode ini memecahkan masalah bagi saya:

private static MyObj DeepCopy(MyObj source)
        {

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

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

        }

Gunakan seperti ini: MyObj a = DeepCopy(b);

6
JerryGoyal

Saya suka Copyconstructors seperti itu:

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

Jika Anda memiliki lebih banyak hal untuk menyalin, tambahkan

5
LuckyLikey

Generator kode

Kami telah melihat banyak ide dari serialisasi tentang implementasi manual hingga refleksi dan saya ingin mengusulkan pendekatan yang sama sekali berbeda menggunakan Generator Kode CGbR . Metode menghasilkan klon adalah memori dan CPU efisien dan karenanya 300x lebih cepat sebagai DataContractSerializer standar.

Yang Anda butuhkan hanyalah definisi kelas parsial dengan ICloneable dan generator mengerjakan sisanya:

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

Catatan: Versi terbaru memiliki lebih banyak pemeriksaan nol, tapi saya meninggalkannya untuk pemahaman yang lebih baik.

5
Toxantron

Di sini solusi cepat dan mudah yang berfungsi untuk saya tanpa menyampaikan 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;
    }
}

EDIT: membutuhkan

    using System.Linq;
    using System.Reflection;

Begitulah cara saya menggunakannya

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

Saya pikir Anda bisa mencoba ini.

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

Ikuti langkah ini:

  • Definisikan ISelf<T> dengan properti Self read-only yang mengembalikan T, dan ICloneable<out T>, yang berasal dari ISelf<T> dan mencakup metode T Clone().
  • Kemudian tentukan tipe CloneBase yang mengimplementasikan protected virtual generic VirtualClone casting MemberwiseClone ke tipe pass-in.
  • Setiap tipe turunan harus mengimplementasikan VirtualClone dengan memanggil metode basis klon dan kemudian melakukan apa pun yang perlu dilakukan untuk mengkloning dengan baik aspek-aspek dari tipe turunan yang belum ditangani oleh metode VirtualClone induk.

Untuk fleksibilitas pewarisan maksimum, kelas yang mengekspos fungsi kloning publik harus sealed, tetapi berasal dari kelas dasar yang sebaliknya identik kecuali karena kurangnya kloning. Daripada meneruskan variabel tipe klon yang eksplisit, ambil parameter tipe ICloneable<theNonCloneableType>. Ini akan memungkinkan suatu rutin yang mengharapkan turunan cloneable dari Foo untuk bekerja dengan turunan cloneable dari DerivedFoo, tetapi juga memungkinkan penciptaan turunan non-cloneable dari Foo.

4
supercat

Saya telah membuat versi dari jawaban yang diterima yang bekerja dengan '[Serializable]' dan '[DataContract]'. Sudah lama sejak saya menulisnya, tetapi jika saya ingat dengan benar [DataContract] membutuhkan serializer yang berbeda.

Membutuhkan Sistem, System.IO, System.Runtime.Serialisasi, 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

Jika Pohon Objek Anda Serializeable Anda juga bisa menggunakan sesuatu seperti ini

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

diberi tahu bahwa Solusi ini cukup mudah tetapi tidak sebagus solusi lain.

Dan pastikan bahwa jika Kelas tumbuh, hanya akan ada bidang-bidang yang dikloning, yang juga mendapatkan serial.

3
LuckyLikey

Untuk mengkloning objek kelas Anda, Anda dapat menggunakan metode Object.MemberwiseClone,

tambahkan saja fungsi ini ke kelas Anda:

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

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

kemudian untuk melakukan salinan independen yang mendalam, cukup panggil metode DeepCopy:

yourClass newLine = oldLine.DeepCopy();

semoga ini membantu.

3
Chtiwi Malek

Ok, ada beberapa contoh yang jelas dengan refleksi di posting ini, TAPI refleksi biasanya lambat, sampai Anda mulai men-cache dengan benar.

jika Anda akan men-cache dengan benar, maka itu akan mengkloning dalam 1000000 objek dengan 4,6s (diukur oleh Watcher).

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

daripada Anda mengambil properti dalam cache atau menambahkan baru ke kamus dan menggunakannya secara sederhana

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

periksa kode lengkap di posting saya di jawaban lain

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

3
Roma Borodov

Karena hampir semua jawaban untuk pertanyaan ini tidak memuaskan atau jelas tidak berfungsi dalam situasi saya, saya telah menulis AnyClone yang sepenuhnya dilaksanakan dengan refleksi dan menyelesaikan semua kebutuhan di sini. Saya tidak bisa mendapatkan serialisasi untuk bekerja dalam skenario yang rumit dengan struktur yang kompleks, dan IClonable kurang dari ideal - bahkan seharusnya tidak perlu.

Atribut abaikan standar didukung menggunakan [IgnoreDataMember], [NonSerialized]. Mendukung koleksi yang kompleks, properti tanpa seter, bidang baca saja dll.

Saya harap ini membantu orang lain di luar sana yang mengalami masalah yang sama dengan saya.

2
Michael Brown

Saat menggunakan Marc Gravells protobuf-net sebagai serializer Anda, jawaban yang diterima membutuhkan sedikit modifikasi, karena objek yang akan disalin tidak akan dikaitkan dengan [Serializable] dan, oleh karena itu, tidak serializable dan metode Klon akan mengeluarkan pengecualian.
Saya memodifikasinya agar berfungsi dengan 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);
    }
}

Ini memeriksa keberadaan atribut [ProtoContract] dan menggunakan formatter protobuf sendiri untuk membuat serialisasi objek.

1
Basti M

C # Ekstensi yang akan mendukung untuk tipe "tidak ISerializable " juga.

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

Pemakaian

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

Sulit dipercaya berapa banyak upaya yang dapat Anda lakukan dengan antarmuka IClonable - terutama jika Anda memiliki hierarki kelas berat. AnggotawiseClone juga berfungsi entah bagaimana aneh - tidak persis mengklon jenis struktur daftar normal.

Dan tentu saja dilema yang paling menarik untuk serialisasi adalah membuat cerita bersambung kembali referensi - mis. hierarki kelas tempat Anda memiliki hubungan anak-orangtua. Saya ragu bahwa serializer biner akan dapat membantu Anda dalam hal ini. (Ini akan berakhir dengan loop rekursif + stack overflow).

Saya entah bagaimana menyukai solusi yang diajukan di sini: Bagaimana Anda bisa menyalin objek dalam .NET (khusus C #)?

namun - itu tidak mendukung Daftar, menambahkan dukungan itu, juga memperhitungkan pengasuhan kembali akun. Untuk aturan pengasuhan saja yang saya buat bahwa bidang atau properti harus dinamai "parent", maka akan diabaikan oleh DeepClone. Anda mungkin ingin memutuskan aturan Anda sendiri untuk referensi-belakang - untuk hierarki pohon itu mungkin "kiri/kanan", dll ...

Berikut ini seluruh cuplikan kode termasuk kode uji:

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

Namun jawaban JSON.NET lainnya. Versi ini berfungsi dengan kelas yang tidak menerapkan 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

Seorang mapper melakukan deep-copy. Foreach anggota objek Anda itu menciptakan objek baru dan menetapkan semua nilainya. Itu bekerja secara rekursif pada setiap anggota batin non-primitif.

Saya menyarankan Anda salah satu yang tercepat, saat ini dikembangkan secara aktif. Saya menyarankan UltraMapper https://github.com/maurosampietro/UltraMapper

Paket Nuget: https://www.nuget.org/packages/UltraMapper/

1
Mauro Sampietro

Pendekatan generik semuanya valid secara teknis, tetapi saya hanya ingin menambahkan catatan dari diri saya sendiri karena kita jarang benar-benar membutuhkan salinan yang dalam, dan saya akan sangat menentang penggunaan penyalinan dalam generik dalam aplikasi bisnis yang sebenarnya karena hal itu memungkinkan Anda memiliki banyak tempat-tempat di mana objek disalin dan kemudian dimodifikasi secara eksplisit, mudah hilang.

Dalam sebagian besar situasi kehidupan nyata juga Anda ingin memiliki kontrol granular sebanyak mungkin atas proses penyalinan karena Anda tidak hanya digabungkan ke kerangka kerja akses data tetapi juga dalam prakteknya objek bisnis yang disalin harus jarang 100% sama. Pikirkan contoh referenceId yang digunakan oleh ORM untuk mengidentifikasi referensi objek, salinan lengkap yang dalam juga akan menyalin id ini jadi ketika dalam-memori objek akan berbeda, segera setelah Anda mengirimkannya ke datastore, itu akan mengeluh, sehingga Anda akan harus memodifikasi properti ini secara manual setelah menyalin dan jika objek berubah Anda perlu menyesuaikannya di semua tempat yang menggunakan penyalinan dalam generik.

Memperluas jawaban @cregox dengan ICloneable, apakah sebenarnya salinan yang dalam? Ini hanya objek yang baru dialokasikan pada heap yang identik dengan objek asli tetapi menempati ruang memori yang berbeda, seperti itu daripada menggunakan fungsionalitas kloner generik mengapa tidak hanya membuat objek baru?

Saya pribadi menggunakan ide metode pabrik statis pada objek domain saya.

Contoh:

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

Jika seseorang melihat bagaimana ia dapat menyusun Instansiasi objek sambil mempertahankan kontrol penuh atas proses penyalinan, itu adalah solusi yang secara pribadi sangat berhasil. Konstruktor yang dilindungi juga membuatnya, pengembang lain dipaksa untuk menggunakan metode pabrik yang memberikan titik tunggal yang rapi dari instance objek yang merangkum logika konstruksi di dalam objek. Anda juga dapat membebani metode ini dan memiliki beberapa logika kloning untuk tempat yang berbeda jika perlu.

0

Saya menemukan cara baru untuk melakukannya yaitu Emit.

Kita dapat menggunakan Emit untuk menambahkan IL ke aplikasi dan menjalankannya. Tapi saya rasa itu bukan cara yang baik untuk saya ingin menyempurnakan ini sehingga saya menulis jawaban saya.

Emit dapat melihat dokumen resmi dan Panduan

Anda harus mempelajari beberapa IL untuk membaca kode. Saya akan menulis kode yang dapat menyalin properti di kelas.

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

Kode dapat berupa salinan dalam tetapi dapat menyalin properti. Jika Anda ingin membuatnya menjadi salinan dalam sehingga Anda dapat mengubahnya untuk IL terlalu sulit sehingga saya tidak bisa melakukannya.

0
lindexi

bagaimana kalau hanya menyusun ulang di dalam metode yang pada dasarnya harus memanggil konstruktor salinan otomatis

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

sepertinya bekerja untuk saya

0
will_m

Ini akan menyalin semua properti yang dapat dibaca dan ditulis dari objek ke yang lain.

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

dan ini adalah bagaimana Anda menggunakannya:

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

atau menyalin semuanya:

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