Deep cloning em C#: da reflexão à serialização

Treinador Online de Python para Iniciantes

Aprenda Python facilmente sem sobrecarga teórica. Resolva tarefas práticas com verificação automática e escreva código diretamente no navegador.

Start Course

Cópia profunda em C#: por que isso é uma dor de cabeça para todo desenvolvedor?

\n\n

Você 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.

\n\n

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\n

O que é Deep Clone e como ele difere do Shallow Clone?

\n\n

Imagine que você tem uma classe Person que contém um Address:

\n\n
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\n

Com 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.

\n\n

O Deep clone resolve esse problema: ele copia recursivamente todos os objetos aninhados, criando uma estrutura de dados totalmente independente.

\n\n

Abordagem 1: Implementação manual via ICloneable e construtores de cópia

\n\n

A maneira mais óbvia, mas também a mais trabalhosa, é implementar a interface ICloneable ou escrever seu próprio método DeepClone().

\n\n

Exemplo de implementação:

\n\n
public 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\n

Vantagens:

\n
    \n
  • Controle total sobre o processo de cópia
  • \n
  • Máximo desempenho
  • \n
  • Código compreensível (sem "mágica")
  • \n
\n\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
\n\n

Quando usar: Apenas para DTOs simples com 2-3 campos, onde o desempenho é crítico (por exemplo, em engines de jogos).

\n\n

Abordagem 2: Reflexão (Reflection) — o soldado universal

\n\n

A reflexão permite escrever um método deep cloning universal que funcionará com qualquer objeto.

\n\n

Exemplo de código:

\n\n
public 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                

Blogs

Book Recommendations