Содержание
Напомню, что классы относятся к ссылочным типам данных в C#. В связи с этим, клонирование объектов в C# может вызвать некоторые затруднения у тех, кто только начинает осваивать работы с этим языком программирования. В особенности, когда необходимо скопировать (клонировать) какой-либо объект.
Пример копирования ссылки и ручного копирования объекта
Рассмотрим такой пример:
internal class Person { public string Name { get; set; } public string Surname { get; set; } public int Age { get; set; } } Person person = new Person() { Name = "Василий", Surname = "Пупкин", Age = 18 }; Person newPerson = person; newPerson.Name = "Петя"; newPerson.Age = 25; Console.WriteLine($"person: {person.Name} {person.Surname} {person.Age}"); Console.WriteLine($"newPerson: {newPerson.Name} {newPerson.Surname} {newPerson.Age}");
Здесь мы создали объект person класса Person
, определили для этого объекта необходимые свойства, затем, создали новую переменную newPerson
и присвоили ей значение переменной person
, после чего изменили у новой переменной некоторые свойства. Так как классы в C# относятся к ссылочным типам данных, то при выполнении Person newPerson = person
мы просто присвоили переменной ссылку на объект типа Person
в памяти. В результате, на экране мы увидим следующие строки:
newPerson: Петя Пупкин 25
то есть у обеих переменных свойства поменялись. Если нам необходимо именно клонировать объект в C#, т.е. сделать его точную копию с которой впоследствии можно работать, не затрагивая значения свойств клонируемого объекта, то можно, например, скопировать все свойства объекта » в лоб»:
Person person = new Person() { Name = "Василий", Surname = "Пупкин", Age = 18 }; Person newPerson = new Person(); newPerson.Name = person.Name; newPerson.Surname = person.Surname; newPerson.Age = person.Age; newPerson.Name = "Петя"; newPerson.Age = 25; Console.WriteLine($"person: {person.Name} {person.Surname} {person.Age}"); Console.WriteLine($"newPerson: {newPerson.Name} {newPerson.Surname} {newPerson.Age}");
newPerson: Петя Пупкин 25
Способ, как говорят, «имеет место быть», но это далеко не самый универсальный способ клонирования объектов в C#. А можно использовать более универсальный способ копирования — использовать интерфейс ICloneable
.
Интерфейс ICloneable
Реализация метода Clone
Интерфейс ICloneable
поддерживает копирование, при котором создается новый экземпляр класса с тем же значением, что и у существующего экземпляра. В этом интерфейсе определен все го один метод Clone
, который мы и должны реализовать в нашем классе. Например, для класса Person
можно написать такую реализацию метода Clone
:
internal class Person: ICloneable { public string Name { get; set; } public string Surname { get; set; } public int Age { get; set; } public object Clone() { return new Person() { Name = this.Name, Surname = this.Surname, Age = this.Age }; } }
Теперь нам достаточно вызвать метод Clone
у нашего объекта, чтобы его клонировать:
Person person = new Person() { Name = "Василий", Surname = "Пупкин", Age = 18 }; Person newPerson = (Person)person.Clone(); //клонируем объект newPerson.Name = "Петя"; newPerson.Age = 25; Console.WriteLine($"person: {person.Name} {person.Surname} {person.Age}"); Console.WriteLine($"newPerson: {newPerson.Name} {newPerson.Surname} {newPerson.Age}");
newPerson: Петя Пупкин 25
Более того, если у нас вдруг изменится состав свойств класса Person
, то нам не надо будет перелопачивать весь код проекта в поисках где мы производили ручное клонирование объектов, а всего лишь изменить метод Clone
в нашем классе.
Поверхностное копирование (метод Memberwise Clone)
Метод Memberwise
определен у класса Object
и позволяет осуществлять поверхностное копирование объекта. Поверхностное копирование объекта позволяет сделать копии свойств с примитивными типами данных (числа, строки, bool и т.д.). Например, так как наш класс Person
не содержит свойств, которые бы представляли собой классы, то метод Clone
можно было бы реализовать так:
public object Clone() { return this.MemberwiseClone(); }
Поверхностного копирования было бы недостаточно, если бы наш класс имел следующее описание:
internal class Person: ICloneable { public string Name { get; set; } public string Surname { get; set; } public int Age { get; set; } //свойство с ссылочным типом данных public Company Company { get; set; } }
Как видите, свойство Company
— это класс. И при вызове Memberwise
будут сделаны копии свойств Name
, Surname
и Age
, однако ссылка в свойстве Company
будет указывать на один и тот же объект в памяти. Следовательно, в этом случае нам необходимо делать не поверхностное, а глубокое копирование, например, так:
public object Clone() { //делаем поверхностное копирование "примитивных" свойств Person copy = (Person)this.MemberwiseClone(); //копируем свойства Company copy.Company = new Company(); copy.Company.Name = this.Company.Name; return copy; }
Итого
Интерфейс ICloneable
содержит метод Clone()
предназначенный для создании копии объекта. При реализации метода Clone()
в классе мы можем использовать любый способы создания копий объекта, в том числе, использовать поверхностное копирование методом Memberwise
.