В. Почему я выбрал этот ответ?
- Выберите этот ответ, если вы хотите самую быструю скорость, на которую способен .NET.
- Не обращайте внимания на этот ответ, если вам нужен действительно простой метод клонирования.
Другими словами, используйте другой ответ, если только у вас нет узкого места в производительности, которое необходимо исправить, и вы можете доказать это с помощью профилировщика. .
В 10 раз быстрее, чем другие методы
Следующий метод выполнения глубокого клона:
- В 10 раз быстрее, чем все, что связано с сериализацией / десериализацией;
- Довольно чертовски близко к теоретической максимальной скорости, на которую способен .NET.
И метод ...
Для максимальной скорости вы можете использовать Nested MemberwiseClone для создания глубокой копии . Это почти такая же скорость, как копирование структуры значений, и намного быстрее, чем (а) отражение или (б) сериализация (как описано в других ответах на этой странице).
Обратите внимание, что если вы используете Nested MemberwiseClone для глубокой копии , вы должны вручную реализовать ShallowCopy для каждого вложенного уровня в классе и DeepCopy, который вызывает все упомянутые методы ShallowCopy для создания полного клона. Это просто: всего несколько строк, см. Демонстрационный код ниже.
Вот выходные данные кода, показывающие относительную разницу в производительности для 100 000 клонов:
- 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 для создания полного клона. Это просто: всего несколько строк, см. Демонстрационный код выше.
Типы значений и типы ссылок
Обратите внимание, что когда дело доходит до клонирования объекта, существует большая разница между « struct » и « class »:
- Если у вас есть « структура », это тип значения, так что вы можете просто скопировать его, и его содержимое будет клонировано (но это будет только мелкий клон, если вы не будете использовать методы в этом посте).
- Если у вас есть « класс », это ссылочный тип , поэтому, если вы копируете его, все, что вы делаете, это копируете указатель на него. Чтобы создать настоящий клон, вы должны быть более креативными и использовать различия между типами значений и ссылочными типами, что создает другую копию исходного объекта в памяти.
Смотрите различия между типами значений и ссылочными типами .
Контрольные суммы, чтобы помочь в отладке
- Неправильное клонирование объектов может привести к очень сложным ошибкам. В производственном коде я склонен реализовывать контрольную сумму, чтобы дважды проверить, что объект был клонирован правильно и не был испорчен другой ссылкой на него. Эта контрольная сумма может быть отключена в режиме деблокирования.
- Я нахожу этот метод весьма полезным: часто вам нужно только клонировать части объекта, а не все.
Действительно полезно для отделения многих потоков от многих других потоков
Одним из отличных вариантов использования этого кода является подача клонов вложенного класса или структуры в очередь для реализации шаблона производитель / потребитель.
- У нас может быть один (или несколько) потоков, которые изменяют принадлежащий им класс, а затем помещают полную копию этого класса в
ConcurrentQueue
.
- Затем у нас есть один (или более) поток, извлекающий копии этих классов и работающий с ними.
На практике это работает очень хорошо и позволяет нам отделить множество потоков (производителей) от одного или нескольких потоков (потребителей).
И этот метод слишком быстр: если мы используем вложенные структуры, он в 35 раз быстрее, чем сериализация / десериализация вложенных классов, и позволяет нам использовать все потоки, доступные на машине.
Обновить
Очевидно, что ExpressMapper работает быстрее, если не быстрее, чем ручное кодирование, как описано выше. Возможно, мне придется посмотреть, как они сравниваются с профилировщиком.