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.