Копирование объектов. Интерфейс ICloneable


Поскольку классы представляют ссылочные типы, то это накладывает некоторые ограничения на их использование. В частности, допустим, у нас есть следующий класс:

class Person
{
public string Name { get; set; }
public int Age { get; set; }
public Person(string name, int age)
{
Name = name;
Age = age;
}
}
Создадим один объект Person и попробуем скопировать его данные в другой объект Person:

var tom = new Person("Tom", 23);
var bob = tom;
bob.Name = "Bob";
Console.WriteLine(tom.Name); // Bob
В данном случае объекты tom и bob будут указывать на один и тот же объект в памяти, поэтому изменения свойств для переменной bob затронут также и переменную tom.

Чтобы переменная bob указывала на новый объект, но при этом имела значения из переменной tom, мы можем применить клонирование с помощью реализации интерфейса ICloneable:

public interface ICloneable
{
object Clone();
}
Поверхностное копирование
Реализация интерфейса в классе Person могла бы выглядеть следующим образом:

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);
}
}
Использование:

var tom = new Person("Tom", 23);
var bob = (Person)tom.Clone();
bob.Name = "Bob";
Console.WriteLine(tom.Name); // Tom
Теперь все нормально копируется, изменения в свойствах переменной bob не сказываются на свойствах из переменной tom.

Для сокращения кода копирования мы можем использовать специальный метод MemberwiseClone(), который возвращает копию объекта:

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();
}
}
Этот метод реализует поверхностное (неглубокое) копирование. Однако данного копирования может быть недостаточно. Например, пусть класс Person содержит ссылку на объект класса 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;
}
В этом случае при копировании новая копия будет указывать на тот же объект 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 - а должно быть Microsoft
Глубокое копирование
Поверхностное копирование работает только для свойств, представляющих примитивные типы, но не для сложных объектов. И в этом случае надо применять глубокое копирование:

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;
}