P: Dlaczego miałbym wybrać tę odpowiedź?
- Wybierz tę odpowiedź, jeśli chcesz najszybszej prędkości .NET.
- Zignoruj tę odpowiedź, jeśli chcesz naprawdę bardzo łatwej metody klonowania.
Innymi słowy, idź z inną odpowiedzią, chyba że masz wąskie gardło wydajności, które wymaga naprawy, i możesz to udowodnić za pomocą profilera .
10 razy szybszy niż inne metody
Następująca metoda wykonywania głębokiego klonowania to:
- 10 razy szybszy niż cokolwiek, co wymaga serializacji / deserializacji;
- Całkiem blisko teoretycznej maksymalnej prędkości .NET jest w stanie.
I metoda ...
Aby uzyskać najwyższą prędkość, możesz użyć Nested MemberwiseClone, aby wykonać głęboką kopię . Ma prawie taką samą szybkość jak kopiowanie struktury wartości i jest znacznie szybszy niż (a) odbicie lub (b) serializacja (jak opisano w innych odpowiedziach na tej stronie).
Zauważ, że jeśli używasz Nested MemberwiseClone do głębokiej kopii , musisz ręcznie wdrożyć ShallowCopy dla każdego poziomu zagnieżdżonego w klasie oraz DeepCopy, który wywołuje wszystkie wspomniane metody ShallowCopy w celu utworzenia pełnego klonu. To proste: w sumie tylko kilka wierszy, zobacz poniższy kod demonstracyjny.
Oto wynik kodu pokazującego względną różnicę wydajności dla 100 000 klonów:
- 1,08 sekundy dla Nested MemberwiseClone na zagnieżdżonych strukturach
- 4,77 sekundy dla Nested MemberwiseClone w klasach zagnieżdżonych
- 39,93 sekundy na serializację / deserializację
Używanie Nested MemberwiseClone na klasie prawie tak szybko, jak kopiowanie struktury, a kopiowanie struktury jest dość zbliżone do teoretycznej maksymalnej prędkości, jaką potrafi .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
Aby zrozumieć, jak wykonać głęboką kopię przy użyciu MemberwiseCopy, oto projekt demonstracyjny, który został użyty do wygenerowania powyższych czasów:
// 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;
}
}
Następnie wywołaj demo z main:
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();
}
Ponownie zauważ, że jeśli używasz Nested MemberwiseClone do głębokiej kopii , musisz ręcznie wdrożyć ShallowCopy dla każdego poziomu zagnieżdżonego w klasie oraz DeepCopy, który wywołuje wszystkie wymienione metody ShallowCopy w celu utworzenia pełnego klonu. To proste: w sumie tylko kilka wierszy, patrz powyższy kod demonstracyjny.
Typy wartości a typy referencji
Zauważ, że jeśli chodzi o klonowanie obiektu, istnieje duża różnica między „ strukturą ” a „ klasą ”:
- Jeśli masz „ struct ”, jest to typ wartości, więc możesz go po prostu skopiować, a zawartość zostanie sklonowana (ale utworzy tylko płytki klon, chyba że użyjesz technik opisanych w tym poście).
- Jeśli masz „ klasę ”, jest to typ referencyjny , więc jeśli ją skopiujesz, wystarczy skopiować do niej wskaźnik. Aby stworzyć prawdziwy klon, musisz być bardziej kreatywny i używać różnic między typami wartości i typami referencji, które tworzą kolejną kopię oryginalnego obiektu w pamięci.
Zobacz różnice między typami wartości a typami referencji .
Sumy kontrolne ułatwiające debugowanie
- Niepoprawne klonowanie obiektów może prowadzić do bardzo trudnych do zlokalizowania błędów. W kodzie produkcyjnym mam tendencję do implementowania sumy kontrolnej w celu podwójnego sprawdzenia, czy obiekt został poprawnie sklonowany i nie został uszkodzony przez inne odniesienie do niego. Tę sumę kontrolną można wyłączyć w trybie zwolnienia.
- Uważam tę metodę za bardzo przydatną: często chcesz sklonować tylko części obiektu, a nie całość.
Naprawdę przydatne do oddzielania wielu wątków od wielu innych wątków
Doskonałym przykładem użycia tego kodu jest podawanie klonów zagnieżdżonej klasy lub struktury do kolejki w celu zaimplementowania wzorca producent / konsument.
- Możemy mieć jeden (lub więcej) wątków modyfikujących klasę, której są właścicielami, a następnie wypychających pełną kopię tej klasy do pliku
ConcurrentQueue
.
- Następnie mamy jeden (lub więcej) wątków wyciągających kopie tych klas i radzących sobie z nimi.
Działa to wyjątkowo dobrze w praktyce i pozwala nam oddzielić wiele wątków (producentów) od jednego lub więcej wątków (konsumentów).
Ta metoda jest również niesamowicie szybka: jeśli użyjemy zagnieżdżonych struktur, jest 35 razy szybsza niż serializacja / deserializacja zagnieżdżonych klas i pozwala nam skorzystać ze wszystkich wątków dostępnych na maszynie.
Aktualizacja
Najwyraźniej ExpressMapper jest tak szybki, jeśli nie szybszy, niż ręczne kodowanie, takie jak powyżej. Może będę musiał zobaczyć, jak porównują się z profilerem.