Cópia profunda em C#: por que isso é uma dor de cabeça para todo desenvolvedor?
\n\nVocê já copiou um objeto em C# e depois descobriu, surpreso, que a alteração na "cópia" quebra o original? Familiar? Este é o clássico problema da cópia superficial (shallow copy). Quando um objeto possui campos de referência (outras classes, listas, arrays), o MemberwiseClone() simplesmente copia as referências. No final, tanto o original quanto a cópia apontam para o mesmo objeto aninhado.
Neste artigo, vamos analisar o deep cloning em C# — o processo de criar uma cópia totalmente independente de um objeto com todos os objetos aninhados. Vamos comparar 5 abordagens populares, escrever código real, realizar um benchmark mental e descobrir qual método escolher para o seu projeto.
\n\nO que é Deep Clone e como ele difere do Shallow Clone?
\n\nImagine que você tem uma classe Person que contém um Address:
public class Address\n{\n public string City { get; set; }\n public string Street { get; set; }\n}\n\npublic class Person\n{\n public string Name { get; set; }\n public int Age { get; set; }\n public Address HomeAddress { get; set; }\n}\n\nCom um shallow clone (o MemberwiseClone() comum), você obtém uma nova pessoa, mas o HomeAddress dela apontará para o mesmo objeto Address do original. Mudou a rua na cópia — a rua mudou também no original. Catástrofe.
O Deep clone resolve esse problema: ele copia recursivamente todos os objetos aninhados, criando uma estrutura de dados totalmente independente.
\n\nAbordagem 1: Implementação manual via ICloneable e construtores de cópia
\n\nA maneira mais óbvia, mas também a mais trabalhosa, é implementar a interface ICloneable ou escrever seu próprio método DeepClone().
Exemplo de implementação:
\n\npublic class Address : ICloneable\n{\n public string City { get; set; }\n public string Street { get; set; }\n\n public object Clone()\n {\n return new Address { City = this.City, Street = this.Street };\n }\n}\n\npublic class Person : ICloneable\n{\n public string Name { get; set; }\n public int Age { get; set; }\n public Address HomeAddress { get; set; }\n\n public object Clone()\n {\n return new Person\n {\n Name = this.Name,\n Age = this.Age,\n HomeAddress = (Address)this.HomeAddress?.Clone()\n };\n }\n}\n\nVantagens:
\n- \n
- Controle total sobre o processo de cópia \n
- Máximo desempenho \n
- Código compreensível (sem "mágica") \n
Desvantagens:
\n- \n
- Grande quantidade de código boilerplate \n
- Fácil esquecer de atualizar o método Clone() ao adicionar um novo campo \n
- Não funciona para classes de terceiros (por exemplo, de bibliotecas) \n
Quando usar: Apenas para DTOs simples com 2-3 campos, onde o desempenho é crítico (por exemplo, em engines de jogos).
\n\nAbordagem 2: Reflexão (Reflection) — o soldado universal
\n\nA reflexão permite escrever um método deep cloning universal que funcionará com qualquer objeto.
\n\nExemplo de código:
\n\npublic static T DeepCloneByReflection<T>(T source)\n{\n if (source == null) return default;\n\n var type = source.GetType();\n \n // Para tipos de valor e strings, simplesmente retornamos o valor\n if (type.IsValueType || type == typeof(string))\n return source;\n\n // Para arrays\n if (type.IsArray)\n {\n var arr = source as Array;\n var clonedArr = Array.CreateInstance(type.GetElementType(), arr.Length);\n for (int i = 0; i < arr.Length; i++)\n clonedArr.SetValue(DeepCloneByReflection(arr.GetValue(i)), i);\n