Páginas

sexta-feira, 1 de abril de 2011

Interface ICloneable

O que é?
A interface ICloneable contém o método Clone, responsável por clonar (criar uma cópia) de uma classe.

Porque utilizar
Se ao desenvolver você se depara com um cenário que necessita criar um objeto a partir de outro, mas não quer que ele aponte para o mesmo endereço de memôria do original, você precisará clonar este objeto.

Se você criar um objeto e atribuir o objeto original para este novo, você estará apenas apontando este objeto novo para o mesmo endereço de memôria do antigo, conforme exemplo abaixo. Isto ocorre devido ao .NET ter como princípio que todo objeto é passado como referência.
PessoaEntity pOriginal = new PessoaEntity();
pOriginal.Nome = "Jose Otavio";

/*
Ao atribuir o objeto original ao novo, você estará apontando para o mesmo endereço de memória. Se algum dos objetos sofrerem alguma alteração, esta será realizada no mesmo endereço de memôria, ou seja, a alteração será feita para os dois objetos (original e novo).
*/
PessoaEntity pNovo = pOriginal;
Muitas pessoas utilizam uma forma alternativa para clonar um objeto, confome exemplo abaixo.
PessoaEntity pOriginal = new PessoaEntity();
pOriginal.Nome = "Jose Otavio";

PessoaEntity pNovo = new PessoaEntity();
pNovo.Nome = pOriginal.Nome;
Esta forma é bastante comum para quem não conhece a interface ICloneable.
Mas imagine que seu objeto tenha várias propriedades, você criará um método com várias linhas para fazer a cópia? Acredito que você achará "feio" fazer desta maneira.

Porque não utilizar o que o .NET te proporciona?

Exemplo
Vamos ao exemplo e criar uma classe que implementa a interface ICloneable.
No método Clone, iremos utilizar o método MemberwiseClone responsável por criar a cópia deste objeto.
public class PessoaEntity : ICloneable
{
    public string Nome { get; set; }

    public int Idade { get; set; }

    public object Clone()
    {
        return this.MemberwiseClone();
    }
}

Utilizando esta classe ficaria:
static void Main(string[] args)
{
    PessoaEntity pOriginal = new PessoaEntity();
    pOriginal.Nome = "Jose Otavio";
    pOriginal.Idade = 27;

    PessoaEntity pNovo = (PessoaEntity)pOriginal.Clone();
    pNovo.Nome = "jQuaglio";

    Console.WriteLine("Nome Original: {0}", pOriginal.Nome);
    Console.WriteLine("Idade Original: {0}", pOriginal.Idade.ToString());
    Console.WriteLine();
    Console.WriteLine("Nome Novo: {0}", pNovo.Nome);
    Console.WriteLine("Idade Novo: {0}", pNovo.Idade.ToString());

    Console.ReadKey();
}

Saída:

Agora vamos enriquecer a classe Pessoa e mostrar o que muitas pessoas não tem conhecimento.
Imaginamos o cenário de que nesta classe exista uma propriedade com o tipo de um outro objeto.

public class PessoaEntity : ICloneable
{
    public string Nome { get; set; }

    public int Idade { get; set; }

    //Objeto EnderecoEntity
    public EnderecoEntity Endereco { get; set; }

    public object Clone()
    {
        return this.MemberwiseClone();
    }
}

public class EnderecoEntity
{
    public string Logradouro { get; set; }

    public string CEP { get; set; }
}

Utilização da classe:
static void Main(string[] args)
{
    PessoaEntity pOriginal = new PessoaEntity();
    pOriginal.Nome = "Jose Otavio";
    pOriginal.Idade = 27;
    pOriginal.Endereco = new EnderecoEntity();
    pOriginal.Endereco.Logradouro = "Rua XYZ, 15";
    pOriginal.Endereco.CEP = "00000-000";

    PessoaEntity pNovo = (PessoaEntity)pOriginal.Clone();
    pNovo.Nome = "jQuaglio";
    pNovo.Endereco.Logradouro = "Avenida ABC, 4000";

    Console.WriteLine("Nome Original: {0}", pOriginal.Nome);
    Console.WriteLine("Idade Original: {0}", pOriginal.Idade.ToString());
    Console.WriteLine("Endereco Original: {0} {1}", pOriginal.Endereco.Logradouro, pOriginal.Endereco.CEP);
    Console.WriteLine();
    Console.WriteLine("Nome Novo: {0}", pNovo.Nome);
    Console.WriteLine("Idade Novo: {0}", pNovo.Idade.ToString());
    Console.WriteLine("Endereco Novo: {0} {1}", pNovo.Endereco.Logradouro, pNovo.Endereco.CEP);

    Console.ReadKey();
}

Ao executar podemos identificar que o valor das propriedades do objeto original (Endereco) foram alteradas, pois como disse anteriormente, todo objeto é passado por referência, então se alterarmos qualquer instância que aponte para o mesmo endereço, todas as instâncias serão alteradas.

Saída:

Para resolver este problemas, não podemos esquecer de implementar a interface ICloneable na classe EnderecoEntity e alterar o método Clone da classe PessoaEntity para poder clonar este outro objeto.

public class PessoaEntity : ICloneable
{
    public string Nome { get; set; }

    public int Idade { get; set; }

    public EnderecoEntity Endereco { get; set; }

    public object Clone()
    {
        PessoaEntity p = (PessoaEntity)this.MemberwiseClone();
        p.Endereco = (EnderecoEntity)p.Endereco.Clone();
        return p;
    }
}

public class EnderecoEntity : ICloneable
{
    public string Logradouro { get; set; }

    public string CEP { get; set; }

    public object Clone()
    {
        return this.MemberwiseClone();
    }
}

Ao executarmos a mesma saída, teremos:

Lembre-se, ao utilizar a interface ICloneable, não esqueça de clonar todos os objetos que pertencem a classe que será clonada.

2 comentários: