Поскольку классы представляют ссылочные типы, то это накладывает некоторые ограничения на их использование. В частности, допустим, у нас есть следующий класс:
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; }