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