Copierea obiectelor - Interfața ICloneable
Deoarece clasele reprezintă tipuri de referință, acest lucru impune anumite limitări asupra utilizării lor. De exemplu, să presupunem că avem următoarea clasă:
class Person
{
public string Name { get; set; }
public int Age { get; set; }
public Person(string name, int age)
{
Name = name;
Age = age;
}
}
Creăm un obiect Person și încercăm să copiem datele sale într-un alt obiect Person:
var tom = new Person("Tom", 23);
var bob = tom;
bob.Name = "Bob";
Console.WriteLine(tom.Name); // Bob
În acest caz, obiectele tom și bob vor indica același obiect în memorie, astfel încât modificările proprietăților pentru variabila bob vor afecta și variabila tom.
Pentru ca variabila bob să indice către un nou obiect, dar să aibă valorile din variabila tom, putem utiliza clonarea prin implementarea interfeței ICloneable:
public interface ICloneable
{
object Clone();
}
Copierea superficială
Implementarea interfeței în clasa Person ar putea arăta astfel:
class Person : ICloneable
{
public string Name { get; set; }
public int Age { get; set; }
public Person(string name, int age)
{
Name = name;
Age = age;
}
public object Clone()
{
return new Person(Name, Age);
}
}
Utilizare:
var tom = new Person("Tom", 23);
var bob = (Person)tom.Clone();
bob.Name = "Bob";
Console.WriteLine(tom.Name); // Tom
Acum totul se copiază corect, iar modificările în proprietățile variabilei bob nu afectează proprietățile din variabila tom.
Pentru a simplifica codul de copiere, putem folosi metoda specială MemberwiseClone(), care returnează o copie a obiectului:
class Person : ICloneable
{
public string Name { get; set; }
public int Age { get; set; }
public Person(string name, int age)
{
Name = name;
Age = age;
}
public object Clone()
{
return MemberwiseClone();
}
}
Această metodă realizează o copiere superficială. Cu toate acestea, această copiere poate fi insuficientă. De exemplu, să presupunem că clasa Person conține o referință la un obiect al clasei Company:
class Person : ICloneable
{
public string Name { get; set; }
public int Age { get; set; }
public Company Work { get; set; }
public Person(string name, int age, Company company)
{
Name = name;
Age = age;
Work = company;
}
public object Clone() => MemberwiseClone();
}
class Company
{
public string Name { get; set; }
public Company(string name) => Name = name;
}
În acest caz, la copiere, noua copie va indica același obiect Company:
var tom = new Person("Tom", 23, new Company("Microsoft"));
var bob = (Person)tom.Clone();
bob.Work.Name = "Google";
Console.WriteLine(tom.Work.Name); // Google - ar trebui să fie Microsoft
Copierea profundă
Copierea superficială funcționează doar pentru proprietățile care reprezintă tipuri primitive, nu și pentru obiectele complexe. În acest caz, trebuie să aplicăm copierea profundă:
class Person : ICloneable
{
public string Name { get; set; }
public int Age { get; set; }
public Company Work { get; set; }
public Person(string name, int age, Company company)
{
Name = name;
Age = age;
Work = company;
}
public object Clone() => new Person(Name, Age, new Company(Work.Name));
}
class Company
{
public string Name { get; set; }
public Company(string name) => Name = name;
}