Q. Por que eu escolheria esta resposta?
- Escolha esta resposta se desejar a velocidade mais rápida com a qual o .NET é capaz.
- Ignore esta resposta se desejar um método muito, muito fácil de clonar.
Em outras palavras, escolha outra resposta, a menos que você tenha um gargalo de desempenho que precise ser corrigido e possa provar isso com um criador de perfil .
10x mais rápido que outros métodos
O método a seguir para executar um clone profundo é:
- 10x mais rápido do que qualquer coisa que envolva serialização / desserialização;
- Muito perto da velocidade máxima teórica em que o .NET é capaz.
E o método ...
Para velocidade máxima, você pode usar Nested MemberwiseClone para fazer uma cópia detalhada . É quase a mesma velocidade que copiar uma estrutura de valor e é muito mais rápido que (a) reflexão ou (b) serialização (conforme descrito em outras respostas nesta página).
Observe que se você usar o Nested MemberwiseClone para obter uma cópia detalhada , precisará implementar manualmente um ShallowCopy para cada nível aninhado na classe e um DeepCopy que chame todos os métodos ShallowCopy para criar um clone completo. Isso é simples: apenas algumas linhas no total, veja o código de demonstração abaixo.
Aqui está a saída do código que mostra a diferença de desempenho relativa para 100.000 clones:
- 1,08 segundos para Nested MemberwiseClone em estruturas aninhadas
- 4,77 segundos para Nested MemberwiseClone em classes aninhadas
- 39,93 segundos para serialização / desserialização
Usar o Nested MemberwiseClone em uma classe quase tão rápido quanto copiar uma estrutura, e copiar uma estrutura é muito parecido com a velocidade máxima teórica em que o .NET é capaz.
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
Para entender como fazer uma cópia profunda usando MemberwiseCopy, aqui está o projeto de demonstração que foi usado para gerar os tempos acima:
// 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;
}
}
Em seguida, chame a demonstração do 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();
}
Novamente, observe que, se você usar Nested MemberwiseClone para uma cópia profunda , precisará implementar manualmente um ShallowCopy para cada nível aninhado na classe e um DeepCopy que chame todos os métodos ShallowCopy para criar um clone completo. Isso é simples: apenas algumas linhas no total, veja o código de demonstração acima.
Tipos de valor vs. tipos de referências
Observe que quando se trata de clonar um objeto, há uma grande diferença entre uma " estrutura " e uma " classe ":
- Se você tem um " struct ", é um tipo de valor, então você pode copiá-lo e o conteúdo será clonado (mas ele fará apenas um clone superficial, a menos que você use as técnicas deste post).
- Se você tem uma " classe ", é um tipo de referência ; portanto, se você a copiar, tudo o que você está fazendo é copiar o ponteiro para ela. Para criar um clone verdadeiro, você precisa ser mais criativo e usar diferenças entre tipos de valor e tipos de referência, o que cria outra cópia do objeto original na memória.
Veja as diferenças entre tipos de valor e tipos de referência .
Soma de verificação para ajudar na depuração
- A clonagem incorreta de objetos pode levar a erros muito difíceis de identificar. No código de produção, eu tendem a implementar uma soma de verificação para verificar se o objeto foi clonado corretamente e não foi corrompido por outra referência a ele. Essa soma de verificação pode ser desativada no modo Release.
- Acho esse método bastante útil: geralmente, você só quer clonar partes do objeto, não a coisa toda.
Realmente útil para dissociar muitos threads de muitos outros threads
Um excelente caso de uso para esse código é alimentar clones de uma classe ou estrutura aninhada em uma fila, para implementar o padrão produtor / consumidor.
- Podemos ter um (ou mais) threads modificando uma classe que eles possuem e, em seguida, enviar uma cópia completa dessa classe para um
ConcurrentQueue
.
- Em seguida, temos um (ou mais) tópicos puxando cópias dessas classes e lidando com elas.
Isso funciona muito bem na prática e permite dissociar muitos threads (os produtores) de um ou mais threads (os consumidores).
E esse método também é incrivelmente rápido: se usarmos estruturas aninhadas, será 35x mais rápido que serializar / desserializar classes aninhadas e nos permitirá tirar proveito de todos os threads disponíveis na máquina.
Atualizar
Aparentemente, o ExpressMapper é tão rápido, se não mais rápido, quanto a codificação manual, como acima. Talvez eu precise ver como eles se comparam com um criador de perfil.