Содержание
Напомню, что классы относятся к ссылочным типам данных в 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 .