OOP Course
Сьогодні

Підрозділ 7.6

Копіювання об'єктів. Інтерфейс ICloneable

Розглядає копіювання об'єктів через ICloneable, поверхневе й глибоке копіювання, а також сортування через IComparable та IComparer.

7.6. Копіювання об'єктів. Інтерфейс ICloneable

Оскільки класи є посилальними типами (reference types), змінна класу зберігає не сам об'єкт, а адресу у пам'яті, де цей об'єкт знаходиться. Просте присвоєння b = a не створює нового об'єкта — воно лише копіює адресу: тепер обидві змінні вказують на той самий об'єкт у купі.

Проблема копіювання посилальних типів

Щоб patient2 вказував на новий, незалежний об'єкт з такими самими даними, застосовують клонування.

Інтерфейс ICloneable

Стандартна бібліотека .NET пропонує інтерфейс ICloneable з єдиним методом:

public interface ICloneable
{
    object Clone();
}

Метод Clone() повертає object — незалежну копію поточного об'єкта. Конкретний спосіб копіювання клас реалізує самостійно, обираючи між поверхневим і глибоким копіюванням.

Поверхневе копіювання

Поверхневе копіювання (shallow copy) створює новий об'єкт і копіює до нього всі поля поточного об'єкта «як є». Для полів-значень (int, bool, struct тощо) це повноцінна незалежна копія. Але для полів-посилань копіюється лише адреса — новий об'єкт і оригінал будуть вказувати на той самий вкладений об'єкт у пам'яті.

Метод MemberwiseClone(), успадкований від object, виконує саме поверхневе копіювання:

Для полів-значень поверхневе копіювання достатнє. Але якщо клас містить поле-посилання на інший об'єкт, виникає проблема:

Глибоке копіювання

Поверхневе та глибоке копіювання

Глибоке копіювання (deep copy) вирішує проблему: для кожного поля-посилання вручну створюється новий об'єкт із тими самими даними. Clone() повністю будує незалежну копію всього графа об'єктів:

Правило вибору: якщо клас містить лише поля значимих типів або рядки (які незмінні в C#) — достатньо MemberwiseClone(). Якщо є вкладені об'єкти-посилання, що можуть змінюватися — потрібне глибоке копіювання.

Сортування об'єктів. Інтерфейс IComparable

Вбудовані типи C# — числа, рядки — вміють порівнювати себе між собою: Array.Sort на масиві int[] спрацює одразу. Але для власних класів компілятор не знає, який порядок вважати «правильним». Для цього існує інтерфейс IComparable<T>:

public interface IComparable<T>
{
    int CompareTo(T? other);
}

Метод CompareTo повертає:

  • від'ємне число — поточний об'єкт стоїть до other у відсортованому порядку
  • нуль — об'єкти рівні за критерієм порівняння
  • додатне число — поточний об'єкт стоїть після other

Реалізуємо сортування пацієнтів за прізвищем:

Застосування компаратора IComparer

Іноді потрібно сортувати за різними критеріями в різних ситуаціях — наприклад, за прізвищем в одному місці програми і за віком в іншому. Один клас не може мати два різні CompareTo. Для цього існує окремий інтерфейс IComparer<T>:

public interface IComparer<in T>
{
    int Compare(T? x, T? y);
}

Компаратор — це окремий клас, що описує один конкретний спосіб порівняння. Він передається як другий аргумент у Array.Sort:

Правила компаратора мають вищий пріоритет над CompareTo: якщо клас реалізує IComparable<T> і одночасно передається IComparer<T> — перемагає компаратор.

Підсумок

Інтерфейс Призначення Де реалізується
ICloneable Копіювання об'єкта У класі, що копіюється
IComparable<T> Вбудований порядок сортування У класі, що порівнюється
IComparer<T> Зовнішній, змінний критерій сортування В окремому класі-компараторі

Поверхневе копіювання (MemberwiseClone) підходить для об'єктів без вкладених посилальних типів. Для складних об'єктів з вкладеними посиланнями потрібне глибоке копіювання через ручне створення копій кожного вкладеного об'єкта.

Розроблено Tomka Yurii · © 2026 ·