Q. Tại sao tôi chọn câu trả lời này?
- Chọn câu trả lời này nếu bạn muốn tốc độ nhanh nhất .NET có khả năng.
- Bỏ qua câu trả lời này nếu bạn muốn một phương pháp nhân bản thực sự, thực sự dễ dàng.
Nói cách khác, đi với một câu trả lời khác trừ khi bạn có một nút cổ chai hiệu năng cần sửa chữa, và bạn có thể chứng minh điều đó bằng một hồ sơ .
Nhanh hơn gấp 10 lần so với các phương pháp khác
Phương pháp sau đây để thực hiện một bản sao sâu là:
- Nhanh hơn 10 lần so với bất cứ điều gì liên quan đến tuần tự hóa / giải tuần tự hóa;
- Khá gần với tốc độ tối đa lý thuyết .NET có khả năng.
Và phương pháp ...
Để có tốc độ tối đa, bạn có thể sử dụng Nested MemberwiseClone để sao chép sâu . Tốc độ của nó gần giống như sao chép một cấu trúc giá trị và nhanh hơn nhiều so với (a) phản chiếu hoặc (b) tuần tự hóa (như được mô tả trong các câu trả lời khác trên trang này).
Lưu ý rằng nếu bạn sử dụng Nested MemberwiseClone cho một bản sao sâu , bạn phải triển khai ShallowCopy theo cách thủ công cho từng cấp độ lồng nhau trong lớp và DeepCopy gọi tất cả các phương thức ShallowCopy đã nói để tạo một bản sao hoàn chỉnh. Điều này rất đơn giản: chỉ có một vài dòng trong tổng số, xem mã demo bên dưới.
Đây là đầu ra của mã hiển thị sự khác biệt hiệu suất tương đối cho 100.000 bản sao:
- 1,08 giây cho Nested MemberwiseClone trên các cấu trúc lồng nhau
- 4,77 giây cho Nested MemberwiseClone trên các lớp lồng nhau
- 39,93 giây cho tuần tự hóa / khử lưu huỳnh
Sử dụng Nested MemberwiseClone trên một lớp gần như nhanh như sao chép một cấu trúc và sao chép một cấu trúc khá gần với tốc độ tối đa theo lý thuyết mà .NET có khả năng.
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
Để hiểu cách thực hiện một bản sao sâu bằng cách sử dụng MemberwiseCopy, đây là dự án demo được sử dụng để tạo thời gian ở trên:
// 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;
}
}
Sau đó, gọi bản demo từ chính:
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();
}
Một lần nữa, lưu ý rằng nếu bạn sử dụng Nested MemberwiseClone cho một bản sao sâu , bạn phải triển khai ShallowCopy theo cách thủ công cho từng cấp độ lồng nhau trong lớp và DeepCopy gọi tất cả các phương thức ShallowCopy đã nói để tạo một bản sao hoàn chỉnh. Điều này rất đơn giản: chỉ có một vài dòng trong tổng số, xem mã demo ở trên.
Các loại giá trị so với các loại tài liệu tham khảo
Lưu ý rằng khi nói đến việc nhân bản một đối tượng, có một sự khác biệt lớn giữa " struct " và " class ":
- Nếu bạn có " struct ", đó là một loại giá trị để bạn có thể sao chép nó và nội dung sẽ được sao chép (nhưng nó sẽ chỉ tạo một bản sao nông trừ khi bạn sử dụng các kỹ thuật trong bài đăng này).
- Nếu bạn có một " lớp ", đó là một loại tham chiếu , vì vậy nếu bạn sao chép nó, tất cả những gì bạn đang làm là sao chép con trỏ vào nó. Để tạo một bản sao thực sự, bạn phải sáng tạo hơn và sử dụng sự khác biệt giữa các loại giá trị và các loại tham chiếu để tạo một bản sao khác của đối tượng gốc trong bộ nhớ.
Xem sự khác biệt giữa các loại giá trị và các loại tham chiếu .
Tổng kiểm tra để hỗ trợ gỡ lỗi
- Nhân bản các đối tượng không chính xác có thể dẫn đến các lỗi rất khó xác định. Trong mã sản xuất, tôi có xu hướng thực hiện tổng kiểm tra để kiểm tra lại xem đối tượng đã được nhân bản đúng chưa và đã không bị hỏng bởi một tham chiếu khác đến nó. Tổng kiểm tra này có thể được tắt trong chế độ Phát hành.
- Tôi thấy phương pháp này khá hữu ích: thông thường, bạn chỉ muốn sao chép các phần của đối tượng, không phải toàn bộ.
Thực sự hữu ích cho việc tách rời nhiều chủ đề từ nhiều chủ đề khác
Một trường hợp sử dụng tuyệt vời cho mã này là cho các bản sao của một lớp lồng nhau hoặc cấu trúc vào một hàng đợi, để thực hiện mô hình nhà sản xuất / người tiêu dùng.
- Chúng ta có thể có một (hoặc nhiều) chủ đề sửa đổi một lớp mà họ sở hữu, sau đó đẩy một bản sao hoàn chỉnh của lớp này vào một
ConcurrentQueue
.
- Sau đó chúng tôi có một (hoặc nhiều) chủ đề kéo các bản sao của các lớp này ra và xử lý chúng.
Điều này hoạt động rất tốt trong thực tế và cho phép chúng tôi tách rời nhiều chủ đề (nhà sản xuất) từ một hoặc nhiều chủ đề (người tiêu dùng).
Và phương pháp này cũng rất nhanh: nếu chúng ta sử dụng các cấu trúc lồng nhau, nó nhanh hơn 35 lần so với các lớp lồng nhau / giải tuần tự hóa và cho phép chúng ta tận dụng tất cả các luồng có sẵn trên máy.
Cập nhật
Rõ ràng, ExpressMapper nhanh như vậy, nếu không nhanh hơn so với mã hóa tay như ở trên. Tôi có thể phải xem làm thế nào họ so sánh với một hồ sơ.