C# - Criando classe clonáveis


Hoje vamos recordar o conceito de clonar objetos e veremos como criar classes que podem ser clonadas.

Na linguagem C# clonar um objeto é copiar todos os membros do objeto para outro objeto. E no C# podemos realizar uma clonagem superficial conhecida como Shallow Copy ou uma clonagem profunda conhecida como Deep Copy.

Um conceito importante é que na linguagem C# quando atribuímos um objeto para outro objeto estamos atribuindo objetos para mesma referência.

A seguir temos uma classe Cliente, e,  após criar um objeto cliente1 estamos atribuindo cliente1 a um objeto cliente2 do tipo Cliente:

Aqui os objetos cliente1 e cliente2 estão apontando para a mesma referência do objeto. Assim qualquer alteração feita no objeto cliente2 vai se refletir no objeto cliente1.

Um clone superficial ou Shallow Clone copia as referências, mas não os objetos referenciados, já um clone profundo ou Deep Clone também copia os objetos referenciados.

Além disso a plataforma .NET oferece a interface ICloneable para clonar objetos. Ela é bem simples e possui apenas o método Clone():

    public interface ICloneable
    {
        object Clone()
    }

O problema com esta interface é o valor de retorno do método Clone, que é do  tipo de object, assim, sempre que usarmos o método Clone, teremos que fazer uma conversão para o tipo principal.

     
    Cliente cliente2 = (Cliente)cliente1.Clone();       

A seguir vamos implementar a clonagem superficial e a clonagem profunda usando a interface ICloneable.

Para isso vamos criar duas interfaces:

1- IShallowCopy

public interface IShallowCopy<T>
{
      T ShallowCopy();
}
 

2- IDeepCopy

public interface IDeepCopy<T>
{
      T DeepCopy();
}
 

A seguir vamos implementar a clonagem superficial criando a classe ShallowClone:

1- ShallowClone

using System.Collections.Generic;
namespace CShp_Clone
{
    public class ShallowClone : IShallowCopy<ShallowClone>
    {
        public int Data = 1;
        public List<string> ListData = new List<string>();
        public object ObjData = new object();
        public ShallowClone ShallowCopy() => (ShallowClone)this.MemberwiseClone();
    }
}

No código acima usamos o método MemberwiseClone() que cria uma copia superficial do objeto atual.

Depois vamos implementar a clonagem profunda criando a classe DeepClone:

2- DeepClone

using System.Collections.Generic;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
namespace CShp_Clone
{
    public class DeepClone : IDeepCopy<DeepClone>
    {
        public int data = 1;
        public List<string> ListData = new List<string>();
        public object objData = new object();

        public DeepClone DeepCopy()
        {
            BinaryFormatter BF = new BinaryFormatter();
            MemoryStream memStream = new MemoryStream();
            BF.Serialize(memStream, this);
            memStream.Flush();
            memStream.Position = 0;
            return (DeepClone)BF.Deserialize(memStream);
        }
    }
}

Agora para dar suporte a ambos os tipos de clonagem vamos implementar ambas as interfaces na classe MultiClone:

using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
namespace CShp_Clone
{
    [Serializable]
    public class MultiClone : IShallowCopy<MultiClone>, IDeepCopy<MultiClone>
    {
        public int data = 1;
        public List<string> ListData = new List<string>();
        public object objData = new object();

        public MultiClone ShallowCopy() => (MultiClone)this.MemberwiseClone();

        public MultiClone DeepCopy()
        {
            BinaryFormatter BF = new BinaryFormatter();
            MemoryStream memStream = new MemoryStream();
            BF.Serialize(memStream, this);
            memStream.Flush();
            memStream.Position = 0;
            return (MultiClone)BF.Deserialize(memStream);
        }
    }
}

Vamos entender o que código:

O suporte para cópia profunda não é fornecido automaticamente pelo .NET Framework, por isso usamos o código a seguir para implementar uma cópia profunda:

BinaryFormatter BF = novo BinaryFormatter();
MemoryStream memStream = novo MemoryStream ();
BF.Serialize (memStream, this);
memStream.Flush ();
memStream.Position = 0;
return (BF.Deserialize (memStream));


Basicamente, o objeto original é serializado para um fluxo de memória via serialização binária e então é desserializado em um novo objeto, que é retornado ao chamador.

Isto é importante reposicionar o ponteiro do fluxo de memória de volta ao início do fluxo antes de chamar o método Deserialize; caso contrário, uma exceção será lançada indicando que o objeto serializado não contém dados.

Realizar uma cópia profunda usando a serialização de objetos permite que você altere o objeto subjacente
 sem ter que modificar o código que executa a cópia profunda.

Assim para clonar o objeto Cliente, basta definir a classe como [Serializable] e fazer com que ela herde de MultiClone.

Vamos incluir uma nova propriedade que é um tipo Endereco

    [Serializable]
    public class Cliente : MultiClone
    {
        public int Id { get; set; }
        public string Nome { get; set; }
        public string Email { get; set; }
        public Endereco Endereco {get; set; }
    }

Cuja definição é a seguinte:

    [Serializable]
    public class Endereco
    {
        public string Local { get; set; }
    }

A seguir podemos realizar a clonagem profunda ou superficial:

Abaixo temos o resultado:

Como vemos a clonagem superficial copiou apenas a referência para Endereco enquanto que a clonagem profunda copiou os valores do objeto.

Pegue o projeto completo aqui: CShp_Clone.zip

"Visto como na sabedoria de Deus o mundo não conheceu a Deus pela sua sabedoria, aprouve a Deus salvar os crentes pela loucura da pregação.
Porque os judeus pedem sinal, e os gregos buscam sabedoria;
Mas nós pregamos a Cristo crucificado, que é escândalo para os judeus, e loucura para os gregos."
1 Coríntios 1:21-23

Referências:


José Carlos Macoratti