Objek kloning yang dalam

2226

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?

NakedBrunch
sumber
81
Semoga bermanfaat: "Mengapa Menyalin Obyek adalah hal yang mengerikan untuk dilakukan?" agiledeveloper.com/articles/cloning072002.htm
Pedro77
stackoverflow.com/questions/8025890/ ... Solusi lain ...
Felix K.
18
Anda harus melihat di AutoMapper
Daniel Little
3
Solusi Anda jauh lebih kompleks, saya bingung membacanya ... hehehe. Saya menggunakan antarmuka DeepClone. antarmuka publik IDeepCloneable <T> {T DeepClone (); }
Pedro77
1
@ Pedro77 - Meskipun, yang menarik, artikel itu akhirnya mengatakan untuk membuat clonemetode di kelas, lalu minta ia memanggil konstruktor internal dan privat yang diteruskan this. Jadi, menyalin itu mengerikan, tetapi menyalin dengan hati-hati (dan artikel itu layak dibaca) tidak. ; ^)
ruffin

Jawaban:

1715

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

Seperti yang disebutkan di tempat lain, itu 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.", 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);
        }
    }
}

Idenya adalah bahwa ia membuat serial objek Anda dan kemudian deserializes itu menjadi objek segar. Manfaatnya adalah Anda tidak perlu khawatir 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 C # 3.0, ubah metode untuk memiliki tanda tangan berikut:

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

Sekarang pemanggilan metode menjadi objectBeingCloned.Clone(); .

EDIT (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 dari tag [Serializable]. ( 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);
}
johnc
sumber
24
stackoverflow.com/questions/78536/cloning-objects-in-c/… memiliki tautan ke kode di atas [dan merujuk dua implementasi lainnya, salah satunya lebih sesuai dalam konteks saya]
Ruben Bartelink
102
Serialisasi / deserialisasi melibatkan overhead yang signifikan yang tidak perlu. Lihat antarmuka ICloneable dan metode klon .MemberWise () di C #.
3Dave
18
@ David, memang, tapi jika objeknya ringan, dan kinerjanya bagus saat menggunakannya tidak terlalu tinggi untuk kebutuhan Anda, maka itu adalah tip yang berguna. Saya belum menggunakannya secara intensif dengan sejumlah besar data dalam satu lingkaran, saya akui, tetapi saya belum pernah melihat masalah kinerja tunggal.
johnc
16
@ Ammir: sebenarnya, tidak: typeof(T).IsSerializablejuga benar jika tipe telah ditandai dengan [Serializable]atribut. Itu tidak harus mengimplementasikan ISerializableantarmuka.
Daniel Gehriger
11
Hanya berpikir saya akan menyebutkan bahwa sementara metode ini berguna, dan saya sudah sering menggunakannya sendiri, itu sama sekali tidak kompatibel dengan Medium Trust - jadi berhati-hatilah jika Anda menulis kode yang memerlukan kompatibilitas. BinaryFormatter mengakses bidang pribadi dan karenanya tidak dapat bekerja di set izin default untuk lingkungan kepercayaan parsial. Anda bisa mencoba serializer lain, tetapi pastikan penelepon Anda tahu bahwa klon mungkin tidak sempurna jika objek yang masuk bergantung pada bidang pribadi.
Alex Norcliffe
298

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);
    }
}
craastad
sumber
13
solutiojn bahkan lebih cepat daripada solusi BinaryFormatter, .NET Perbandingan Kinerja Serialisasi
esskar
3
Terima kasih untuk ini. Saya bisa melakukan hal yang sama pada dasarnya dengan serializer BSON yang dikirimkan dengan driver MongoDB untuk C #.
Mark Ewer
3
Ini adalah cara terbaik bagi saya, Namun, saya menggunakan Newtonsoft.Json.JsonConverttetapi itu sama
Pierre
1
Agar ini berfungsi, objek yang akan dikloning harus berseri-seri seperti yang telah disebutkan - ini juga berarti misalnya bahwa ia mungkin tidak memiliki dependensi melingkar
radomeit
2
Saya pikir ini adalah solusi terbaik karena implementasinya dapat diterapkan pada sebagian besar bahasa pemrograman.
mr5
178

Alasan untuk tidak menggunakan ICloneable adalah tidak 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, MemberwiseClonebuat salinan yang dangkal, tetapi yang sebaliknya MemberwiseClonebukan Clone; mungkin, mungkin,DeepClone , yang tidak ada. Saat Anda menggunakan objek melalui antarmuka ICloneable, Anda tidak dapat mengetahui jenis kloning yang dilakukan oleh objek yang mendasarinya. (Dan komentar XML tidak akan membuatnya jelas, karena Anda akan mendapatkan komentar antarmuka daripada yang ada di metode Kloning objek.)

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

Ryan Lundy
sumber
Saya tidak jelas mengapa ICloneable dianggap kabur. Diberi jenis seperti Kamus (Dari T, U), saya berharap bahwa ICloneable.Clone harus melakukan tingkat penyalinan dalam dan dangkal apa pun yang diperlukan untuk membuat kamus baru menjadi kamus independen yang berisi T dan U yang sama (konten struktur, dan / atau referensi objek) seperti aslinya. Di mana ambiguitasnya? Yang pasti, generik ICloneable (Of T), yang mewarisi ISelf (Of T), yang termasuk metode "Self", akan jauh lebih baik, tapi saya tidak melihat ambiguitas pada kloning mendalam vs dangkal.
supercat
31
Contoh Anda menggambarkan masalahnya. Misalkan Anda memiliki Kamus <string, Pelanggan>. Haruskah Kamus yang dikloning memiliki objek Pelanggan yang sama seperti aslinya, atau salinan dari objek Pelanggan tersebut? Ada kasus penggunaan yang masuk akal untuk keduanya. Tapi ICloneable tidak menjelaskan yang mana yang akan Anda dapatkan. Itu sebabnya itu tidak berguna.
Ryan Lundy
@ Kirralessa Artikel Microsoft MSDN sebenarnya menyatakan masalah yang sangat tidak diketahui ini jika Anda meminta salinan yang dalam atau dangkal.
naksir
Jawaban dari duplikat stackoverflow.com/questions/129389/… menjelaskan ekstensi Salin, berdasarkan MembershipClone rekursif
Michael Freidgeim
123

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 itu) dan solusi terbaik disediakan oleh Pedro77 Link 's 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 dalam C tajam!

Pertama dan terutama, itu semua adalah pilihan kami:

The Artikel Cepat Jauh Copy oleh Trees Expression juga memiliki kinerja perbandingan kloning oleh serialisasi, Refleksi dan Pohon Ekspresi.

Mengapa saya memilih ICloneable (yaitu secara manual)

Tn. Venkat Subramaniam (tautan berlebihan di sini) menjelaskan secara mendetail mengapa .

Semua artikelnya melingkari contoh yang mencoba berlaku untuk sebagian besar kasus, menggunakan 3 objek: Orang , Otak dan Kota . 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 oleh 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 klon seperti yang disediakan dalam C # (dan Java) bisa sangat bermasalah juga. Lebih baik memberikan 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 Brain@1fcc69
This is person with Brain@253498
SkilledPerson: This is person with SmarterBrain@1fef6f
SkilledPerson: This is person with SmarterBrain@209f4e

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

cregox
sumber
6
MS merekomendasikan untuk tidak menggunakan ICloneableuntuk anggota publik. "Karena penelepon Clone tidak dapat bergantung pada metode yang melakukan operasi kloning yang dapat diprediksi, kami menyarankan agar ICloneable tidak diimplementasikan dalam API publik." msdn.microsoft.com/en-us/library/... Namun, berdasarkan penjelasan yang diberikan oleh Venkat Subramaniam dalam artikel tertaut Anda, saya pikir masuk akal untuk digunakan dalam situasi ini selama pembuat objek ICloneable memiliki kedalaman memahami sifat mana yang harus dalam vs salinan dangkal (yaitu copy dalam Otak, salinan dangkal Kota)
BateTech
Pertama, saya jauh dari ahli dalam topik ini (API publik). Saya berpikir untuk sekali bahwa komentar MS sangat masuk akal. Dan saya pikir tidak aman untuk menganggap pengguna API itu akan memiliki pemahaman yang mendalam. Jadi, masuk akal untuk mengimplementasikannya pada API publik jika benar-benar tidak masalah bagi siapa pun yang akan menggunakannya. Saya kira memiliki semacam UML secara eksplisit membuat perbedaan pada setiap properti dapat membantu. Tetapi saya ingin mendengar dari seseorang yang lebih berpengalaman. : P
cregox
Anda dapat menggunakan CGbR Clone Generator dan mendapatkan hasil yang serupa tanpa menulis kode secara manual.
Toxantron
Implementasi Bahasa Antara berguna
Michael Freidgeim
Tidak ada final di C #
Konrad
84

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

Nick
sumber
5
.Net tidak memiliki copy constructor.
Pop Catalin
48
Tentu ya: MyObject baru (objToCloneFrom) Hanya mendeklarasikan ctor yang mengambil objek untuk dikloning sebagai parameter.
Nick
30
Bukan hal yang sama. Anda harus menambahkannya ke setiap kelas secara manual, dan Anda bahkan tidak tahu apakah Anda menjamin salinan yang dalam.
Dave Van den Eynde
15
+1 untuk copy ctor. Anda harus secara manual menulis fungsi clone () untuk setiap jenis objek juga, dan semoga berhasil ketika hierarki kelas Anda mencapai beberapa level.
Andrew Grant
3
Dengan copy constructor Anda kehilangan hierarki. agiledeveloper.com/articles/cloning072002.htm
Will
42

Metode ekstensi sederhana untuk menyalin semua properti publik. Bekerja untuk objek apa pun dan tidak memerlukan kelas [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 ) } );
        }
    };
}
Konstantin Salavatov
sumber
15
Sayangnya, ini cacat. Ini setara dengan memanggil objectOne.MyProperty = objectTwo.MyProperty (yaitu, itu hanya akan menyalin referensi di seluruh). Itu tidak akan mengkloning nilai-nilai properti.
Alex Norcliffe
1
untuk Alex Norcliffe: penulis pertanyaan bertanya tentang "menyalin setiap properti" daripada kloning. dalam banyak kasus duplikasi properti yang tepat tidak diperlukan.
Konstantin Salavatov
1
saya berpikir tentang menggunakan metode ini tetapi dengan rekursi. jadi jika nilai properti adalah referensi, buat objek baru dan panggil CopyTo lagi. saya hanya melihat satu masalah, bahwa semua kelas yang digunakan harus memiliki konstruktor tanpa parameter. Adakah yang sudah mencoba ini? saya juga bertanya-tanya apakah ini benar-benar akan bekerja dengan properti yang mengandung kelas .net seperti DataRow dan DataTable?
Koryu
33

Saya baru saja membuat proyek CloneExtensionsperpustakaan . 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 sendiri Cloneatau Copymetode dengan nada penugasan antara bidang dan properti membuat program melakukannya sendiri, menggunakan Pohon Ekspresi. GetClone<T>()metode yang ditandai sebagai metode ekstensi memungkinkan Anda memanggilnya dengan instan:

var newInstance = source.GetClone();

Anda dapat memilih apa yang harus disalin dari sourceke newInstancemenggunakan CloningFlagsenum:

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 publik, bukan bidang yang hanya bisa dibaca
  • 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 diberikanT .

Ini juga lebih cepat daripada solusi berbasis serialisasi ketika Anda mengkloning lebih dari beberapa instance dari 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 arti 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 seperti bagaimana Anda akan menulis Clonemetode Anda sendiri List<int>?

MarcinJuraszek
sumber
2
Bagaimana peluang mendapatkan ini di NuGet? Sepertinya solusi terbaik. Bagaimana cara membandingkannya dengan NClone ?
naksir
Saya pikir jawaban ini harus terangkat lebih banyak kali. Implementasi ICloneable secara manual itu membosankan dan rawan kesalahan, menggunakan refleksi atau serialisasi lambat jika kinerjanya penting dan Anda perlu menyalin ribuan objek selama periode waktu yang singkat.
nightcoder
Tidak sama sekali, Anda salah tentang refleksi, Anda harus cukup cache ini dengan benar. Periksa jawaban saya di bawah ini stackoverflow.com/a/34368738/4711853
Roma Borodov
31

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);
    }
}
Michael White
sumber
31

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 ISerializableatau ICloneablepada objek Anda. Ini umum dengan pola MVC / MVVM, jadi alat sederhana seperti ini telah dibuat.

lihat sampel kloning mendalam ValueInjecter di GitHub .

Michael Cox
sumber
26

Yang terbaik adalah menerapkan metode ekstensi seperti

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.

frakon
sumber
kode kloning menggunakan pohon Ekspresi yang telah Anda posting codeproject.com/Articles/1111658/... , gagal dengan versi yang lebih baru. Kerangka Net dengan pengecualian keamanan, Operasi dapat mendestabilkan runtime , pada dasarnya merupakan pengecualian karena pohon ekspresi cacat, yang digunakan untuk menghasilkan Func saat runtime, silakan periksa apakah Anda memiliki beberapa solusi. Bahkan saya telah melihat masalah hanya dengan objek kompleks dengan hierarki yang dalam, yang sederhana mudah disalin
Mrinal Kamboj
1
Implementasi ExpressionTree tampaknya sangat baik. Ia bahkan bekerja dengan referensi melingkar dan anggota pribadi. Tidak diperlukan atribut. Jawaban terbaik yang saya temukan.
N73k
Jawaban terbaik, bekerja dengan sangat baik, Anda menyelamatkan hari saya
Adel Mourad
23

Jawaban singkatnya adalah Anda mewarisi dari antarmuka ICloneable dan kemudian mengimplementasikan fungsi .clone. Klon harus melakukan salinan sesuai dengan anggota dan melakukan salinan dalam pada anggota 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 .

The panjang jawabannya adalah "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 Pojok Pengembang ini untuk beberapa opsi lagi (dikreditkan ke Ian).

Zach Burlingame
sumber
1
ICloneable tidak memiliki antarmuka generik, jadi tidak disarankan untuk menggunakan antarmuka itu.
Karg
Solusi Anda berfungsi sampai perlu menangani referensi melingkar, maka hal-hal mulai menyulitkan, lebih baik untuk mencoba menerapkan kloning mendalam menggunakan serialisasi yang mendalam.
Pop Catalin
Sayangnya, tidak semua objek bisa serial, jadi Anda tidak bisa selalu menggunakan metode itu. Tautan Ian adalah jawaban paling komprehensif sejauh ini.
Zach Burlingame
19
  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 menyadari 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.

Bersulang.

dimarzionis
sumber
4
ICloneable tidak memiliki antarmuka generik, jadi tidak disarankan untuk menggunakan antarmuka itu.
Karg
Jawaban yang sederhana dan ringkas adalah yang terbaik.
DavidGuaita
17

EDIT: proyek dihentikan

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 daripada 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.

Michael Sander
sumber
Yang ini sepertinya cukup berguna
LuckyLikey
Lebih mudah untuk mulai bekerja dari satu snapshot kode daripada keseluruhan sistem, terutama yang tertutup. Dapat dimengerti bahwa tidak ada pustaka yang dapat menyelesaikan semua masalah dengan satu kesempatan. Beberapa relaksasi harus dilakukan.
TarmoPikaro
1
Saya sudah mencoba solusi Anda dan tampaknya berfungsi dengan baik, terima kasih! Saya pikir jawaban ini harus terangkat lebih banyak kali. Implementasi ICloneable secara manual itu membosankan dan rawan kesalahan, menggunakan refleksi atau serialisasi lambat jika kinerjanya penting dan Anda perlu menyalin ribuan objek selama periode waktu yang singkat.
nightcoder
Saya mencobanya dan tidak berhasil sama sekali untuk saya. Melempar pengecualian MemberAccess.
Michael Brown
Ini tidak berfungsi dengan versi .NET yang lebih baru dan dihentikan
Michael Sander
14

Buat semuanya tetap sederhana dan gunakan AutoMapper seperti yang disebutkan lainnya, 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;
}

Metode ekstensi dapat digunakan sebagai berikut:

MyType copy = source.Copy();
Ditumpuk
sumber
Hati-hati dengan yang ini, ini berkinerja sangat buruk. Saya akhirnya beralih ke jawaban Johnc yang sesingkat ini dan melakukan jauh lebih baik.
Agorilla
1
Ini hanya salinan dangkal.
N73k
11

Saya datang dengan ini untuk mengatasi kekurangan .NET harus secara manual Daftar 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();
        }
    }
}
Daniel Mošmondor
sumber
10

Q. Mengapa saya memilih jawaban ini?

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

Dengan kata lain, gunakan jawaban lain kecuali 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 yang 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 lebih 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 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 Serialisasi / Deserialisasi

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 bagaimana cara menyalin secara mendalam 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;
    }
}

Lalu, 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 " class ":

  • Jika Anda memiliki " struct ", ini adalah tipe nilai sehingga Anda bisa menyalinnya, dan kontennya akan dikloning (tetapi itu hanya akan membuat klon yang dangkal kecuali Anda menggunakan teknik dalam posting ini).
  • Jika Anda memiliki " kelas ", itu adalah tipe referensi , jadi jika Anda menyalinnya, yang Anda lakukan hanyalah menyalin pointer ke sana. Untuk membuat tiruan sejati, Anda harus lebih kreatif, dan menggunakan perbedaan antara tipe nilai dan tipe referensi yang membuat salinan lain dari objek asli di 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 bahwa 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

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 dalam 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 benang (produsen) dari satu atau lebih benang (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.

Contango
sumber
Jika Anda menyalin struct Anda mendapatkan salinan dangkal, Anda mungkin masih perlu implementasi khusus untuk salinan yang mendalam.
Lasse V. Karlsen
@Lasse V. Karlsen. Ya, Anda benar sekali, saya telah memperbarui jawaban untuk membuatnya lebih jelas. Metode ini dapat digunakan untuk membuat salinan struct dan kelas yang mendalam . Anda dapat menjalankan contoh kode demo yang disertakan untuk menunjukkan bagaimana hal itu dilakukan, ia memiliki contoh kloning mendalam sebuah nested struct, dan contoh lain dari kloning mendalam kelas bersarang.
Contango
9

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

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

HappyDude
sumber
ICloneable tidak memiliki antarmuka generik, jadi tidak disarankan untuk menggunakan antarmuka itu.
Karg
8

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

xr280xr
sumber
8

Berikut ini adalah implementasi dalam salinan:

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;
}
dougajmcdonald
sumber
2
Ini terlihat seperti tiruan memberwise karena tidak mengetahui properti tipe referensi
sll
1
Jika Anda ingin kinerja yang sangat cepat, jangan gunakan untuk implementasi ini: ia menggunakan refleksi, sehingga tidak akan secepat itu. Sebaliknya, "optmization prematur adalah yang paling jahat", jadi abaikan sisi kinerja sampai setelah Anda menjalankan profiler.
Contango
1
CreateInstanceOfType tidak ditentukan?
MonsterMMORPG
Gagal pada interger: "Metode non-statis memerlukan target."
Mr.B
8

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. Itu dicapai dengan menambahkan anotasi ke kode yang akan dikloning atau Anda hanya meninggalkan kode karena memiliki perilaku default. Ini menggunakan refleksi, ketik cache dan didasarkan pada flect yang lebih cepat . 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
kalisohn
sumber
7

Generator kode

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

Yang Anda butuhkan hanyalah definisi kelas parsial ICloneabledan 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, tetapi saya meninggalkannya untuk pemahaman yang lebih baik.

Toxantron
sumber
6

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 disalin, tambahkan

LuckyLikey
sumber
6

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

JerryGoyal
sumber
6

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();
}
Daniele D.
sumber
5

Ikuti langkah ini:

  • Tetapkan sebuah ISelf<T>dengan Selfproperti hanya baca yang kembali T, dan ICloneable<out T>, yang berasal dari ISelf<T>dan termasuk metode T Clone().
  • Kemudian tentukan CloneBasetipe yang mengimplementasikan protected virtual generic VirtualClonecasting MemberwiseCloneke tipe yang dilewatkan.
  • Setiap tipe turunan harus diimplementasikan VirtualClonedengan memanggil metode clone dasar dan kemudian melakukan apa pun yang perlu dilakukan untuk mengkloning dengan baik aspek-aspek dari tipe turunan yang metode induk VirtualClone belum ditangani.

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

supercat
sumber
5

Saya pikir Anda dapat mencoba ini.

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

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

} 
Jeroen Ritmeijer
sumber
4

Ok, ada beberapa contoh 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 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

Roma Borodov
sumber
2
Panggilan prop.GetValue(...)masih refleksi dan tidak bisa di-cache. Dalam pohon ekspresi yang dikompilasi, jadi lebih cepat
Tseng
4

Karena hampir semua jawaban untuk pertanyaan ini tidak memuaskan atau jelas tidak berfungsi dalam situasi saya, saya telah menulis AnyClone yang sepenuhnya diimplementasikan dengan refleksi dan menyelesaikan semua kebutuhan di sini. Saya tidak bisa mendapatkan serialisasi untuk bekerja dalam skenario yang rumit dengan struktur yang kompleks, danIClonable kurang 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.

Michael Brown
sumber
4

Penafian: Saya penulis paket yang disebutkan.

Saya terkejut bagaimana jawaban teratas untuk pertanyaan ini pada tahun 2019 masih menggunakan serialisasi atau refleksi.

Serialisasi terbatas (memerlukan atribut, konstruktor tertentu, dll.) Dan sangat lambat

BinaryFormattermembutuhkan Serializableatribut, JsonConvertermemerlukan konstruktor tanpa parameter atau atribut, tidak menangani bidang baca saja atau antarmuka dengan sangat baik dan keduanya 10-30x lebih lambat dari yang diperlukan.

Pohon Ekspresi

Anda bisa menggunakan Pohon Ekspresi atau Refleksi. Sebaliknya untuk menghasilkan kode kloning hanya sekali, kemudian gunakan kode yang dikompilasi itu bukan refleksi lambat atau serialisasi.

Setelah menemukan masalah sendiri dan tidak melihat solusi yang memuaskan, saya memutuskan untuk membuat paket yang melakukan hal itu dan bekerja dengan setiap jenis dan hampir secepat kode tertulis kustom .

Anda dapat menemukan proyek di GitHub: https://github.com/marcelltoth/ObjectCloner

Pemakaian

Anda dapat menginstalnya dari NuGet. Dapatkan ObjectClonerpaket dan gunakan sebagai:

var clone = ObjectCloner.DeepClone(original);

atau jika Anda tidak keberatan mencemari jenis objek Anda dengan ekstensi ObjectCloner.Extensionsjuga dan menulis:

var clone = original.DeepClone();

Performa

Tolok ukur sederhana kloning hierarki kelas menunjukkan kinerja ~ 3x lebih cepat daripada menggunakan Refleksi, ~ 12x lebih cepat dari serialisasi Newtonsoft.Json dan ~ 36x lebih cepat dari yang sangat disarankan BinaryFormatter.

Marcell Toth
sumber