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

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

person: Петя Пупкин 25
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}");
person: Василий Пупкин 18
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}");
person: Василий Пупкин 18
newPerson: Петя Пупкин 25

Более того, если у нас вдруг изменится состав свойств класса Person, то нам не надо будет перелопачивать весь код проекта в поисках где мы производили ручное клонирование объектов, а всего лишь изменить метод Clone в нашем классе.

Поверхностное копирование (метод MemberwiseClone)

Метод MemberwiseClone определен у класса 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 — это класс. И при вызове MemberwiseClone будут сделаны копии свойств 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() в классе мы можем использовать любый способы создания копий объекта, в том числе, использовать поверхностное копирование методом MemberwiseClone .

Подписаться
Уведомить о
guest
0 Комментарий
Старые
Новые Популярные
Межтекстовые Отзывы
Посмотреть все комментарии