З. Чому я обрав би цю відповідь?
- Виберіть цю відповідь, якщо ви хочете, щоб швидкість .NET була здатна.
- Ігноруйте цю відповідь, якщо ви хочете по-справжньому, дуже простому способу клонування.
Іншими словами, зверніться до іншої відповіді, якщо у вас немає вузького місця, яке потребує виправлення, і ви можете довести це за допомогою профілера .
10 разів швидше, ніж інші методи
Наступний метод виконання глибокого клону:
- 10 разів швидше за все, що передбачає серіалізацію / десеріалізацію;
- Досить чорт, близький до теоретичної максимальної швидкості .NET здатний.
І метод ...
Для досягнення максимальної швидкості ви можете використовувати Nested MemberwiseClone, щоб зробити глибоку копію . Його майже така ж швидкість, як і копіювання ціннісної структури, і набагато швидша, ніж (a) відображення або (b) серіалізація (як описано в інших відповідях на цій сторінці).
Зауважте, що якщо ви використовуєте Nested MemberwiseClone для глибокої копії , вам потрібно вручну реалізувати ShallowCopy для кожного вкладеного рівня в класі та DeepCopy, який викликає всі зазначені методи ShallowCopy для створення повного клону. Це просто: всього кілька рядків, дивіться демо-код нижче.
Ось вихід коду, що показує відносну різницю продуктивності для 100000 клонів:
- 1,08 секунди для вкладеного MemberwiseClone на вкладені структури
- 4,77 секунди для вкладеного MemberwiseClone на вкладених класах
- 39,93 секунди для серіалізації / десеріалізації
Використання Nested MemberwiseClone в класі майже так само швидко, як і копіювання структури, а також копіювання структури досить проклятене, близьке до максимальної теоретичної швидкості. NET здатна.
Demo 1 of shallow and deep copy, using classes and MemberwiseClone:
Create Bob
Bob.Age=30, Bob.Purchase.Description=Lamborghini
Clone Bob >> BobsSon
Adjust BobsSon details
BobsSon.Age=2, BobsSon.Purchase.Description=Toy car
Proof of deep copy: If BobsSon is a true clone, then adjusting BobsSon details will not affect Bob:
Bob.Age=30, Bob.Purchase.Description=Lamborghini
Elapsed time: 00:00:04.7795670,30000000
Demo 2 of shallow and deep copy, using structs and value copying:
Create Bob
Bob.Age=30, Bob.Purchase.Description=Lamborghini
Clone Bob >> BobsSon
Adjust BobsSon details:
BobsSon.Age=2, BobsSon.Purchase.Description=Toy car
Proof of deep copy: If BobsSon is a true clone, then adjusting BobsSon details will not affect Bob:
Bob.Age=30, Bob.Purchase.Description=Lamborghini
Elapsed time: 00:00:01.0875454,30000000
Demo 3 of deep copy, using class and serialize/deserialize:
Elapsed time: 00:00:39.9339425,30000000
Щоб зрозуміти, як зробити глибоку копію за допомогою MemberwiseCopy, ось демонстраційний проект, який використовувався для генерування вищезазначених часів:
// 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;
}
}
Потім зателефонуйте до демо-версії з основного:
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();
}
Ще раз зауважте, що якщо ви використовуєте Nested MemberwiseClone для глибокої копії , вам доведеться вручну реалізувати ShallowCopy для кожного вкладеного рівня в класі та DeepCopy, який викликає всі зазначені методи ShallowCopy для створення повного клону. Це просто: всього кілька рядків, дивіться демо-код вище.
Типи значення порівняно з типами посилань
Зауважте, що якщо мова йде про клонування об'єкта, існує велика різниця між " структура " та " клас ":
- Якщо у вас є " структура ", це тип значення, тому ви можете просто скопіювати його, а вміст буде клоновано (але це зробить лише неглибокий клон, якщо ви не використовуєте методи в цій публікації).
- Якщо у вас є " клас ", це тип посилання , тому якщо ви його копіюєте, все, що ви робите, - це копіювання вказівника на нього. Щоб створити справжній клон, ви повинні бути більш креативними та використовувати відмінності між типами значень та типами посилань, що створює ще одну копію оригінального об'єкта в пам'яті.
Дивіться відмінності між типами значень та типами посилань .
Контрольні суми допоможуть у налагодженні
- Неправильне клонування об’єктів може призвести до дуже важких помилок, які можна легко усунути. У виробничому коді я схильний застосовувати контрольну суму, щоб подвійно перевірити, чи об'єкт був клонований належним чином, і не був пошкоджений іншим посиланням на нього. Цю контрольну суму можна вимкнути в режимі випуску.
- Я вважаю цей метод досить корисним: часто вам потрібно клонувати лише частини об’єкта, а не всю річ.
Дійсно корисно для роз'єднання багатьох потоків від багатьох інших потоків
Одним з відмінних випадків використання цього коду є подавання клонів вкладеного класу або структури в чергу для реалізації моделі виробник / споживач.
- У нас може бути одна (або більше) ниток, що змінюють клас, яким вони володіють, а потім висуваючи повну копію цього класу в a
ConcurrentQueue
.
- Потім у нас є одна (або більше) ниток, які витягують копії цих класів і розбираються з ними.
Це дуже добре працює на практиці і дозволяє нам відокремити багато ниток (виробників) від однієї або декількох ниток (споживачів).
І цей метод також сліпуче швидкий: якщо ми використовуємо вкладені структури, це на 35 разів швидше, ніж серіалізація / дезаріалізація вкладених класів, і дозволяє нам скористатися всіма потоками, доступними на машині.
Оновлення
Мабуть, ExpressMapper такий же швидкий, якщо не швидший, ніж ручне кодування, як вище. Можливо, мені доведеться побачити, як вони порівнюються з профілером.