Skip to content

Instantly share code, notes, and snippets.

@sunmeat
Last active October 7, 2025 10:20
Show Gist options
  • Save sunmeat/6005a18468f4f71389d12b7d4c386f13 to your computer and use it in GitHub Desktop.
Save sunmeat/6005a18468f4f71389d12b7d4c386f13 to your computer and use it in GitHub Desktop.
IClonable example C#
using System.Text;
namespace MakeClone
{
class Monster : ICloneable // public object Clone()
{
public string Name { get; set; }
public int Health { get; set; }
public int Mana { get; set; }
public int Ammo { get; set; }
public Monster(int health, int mana, int ammo, string name)
{
Health = health;
Mana = mana;
Ammo = ammo;
Name = name;
}
public Monster ShallowClone()
{
// повертає поверхневу копію через MemberwiseClone (копіює поля, але референси на об'єкти лишаються спільними)
return (Monster)this.MemberwiseClone();
}
public object Clone()
{
// повертає користувацьку копію (створює новий об'єкт з копійованими значеннями)
return new Monster(Health, Mana, Ammo, "Клон " + Name);
}
virtual public void Passport()
{
Console.WriteLine("Монстр {0} з здоров'ям = {1}, манною = {2} та боєприпасами = {3}", Name, Health, Mana, Ammo);
}
}
class Program
{
static void Main()
{
Console.OutputEncoding = Encoding.UTF8;
Monster original = new Monster(70, 50, 80, "Шепетуха");
Monster referenceCopy = original;
Monster shallowCopy = original.ShallowClone();
Monster customCopy = (Monster)original.Clone();
// демонстрація: зміна значень для ілюстрації типів копіювання
original.Health = 100;
shallowCopy.Health = 200;
customCopy.Health = 300;
Console.WriteLine("Інформація про монстрів:");
original.Passport();
Console.WriteLine("Копія за посиланням:");
referenceCopy.Passport();
Console.WriteLine("Поверхнева копія:");
shallowCopy.Passport();
Console.WriteLine("Користувацька копія:");
customCopy.Passport();
}
}
}
/*
Реалізація клонування за допомогою інтерфейсу ICloneable в коді вище все ще технічно працює в .NET 9,
оскільки інтерфейс не позначено як obsolete (застарілий) у фреймворку. Однак Microsoft не рекомендує його використовувати
в публічних API, бо метод Clone() не уточнює, чи це поверхневе (shallow) чи глибоке (deep) клонування, що призводить
до непередбачуваної поведінки та помилок у коді. Це проблема існує ще з часів .NET Framework і досі не виправлена в .NET 9,
де прийнято робити акцент на сучасні, безпечніші підходи до даних, такі наприклад як records та immutable об'єкти.
ShallowClone() з MemberwiseClone() — це класичний shallow-клон, який копіює тільки значення полів першого рівня
(референси лишаються спільними). Він актуальний для простих сценаріїв, але не масштабується для складних об'єктів.
Ручна реалізація Clone() — це deep-клон для примітивних типів, але для вкладених об'єктів (наприклад, колекцій) потребує рекурсії,
що зробить код громіздким.
На сьогодні така реалізація вважається застарілою для нових проєктів: вона порушує принципи ясності коду, тестування та продуктивності.
Краще уникати ICloneable взагалі, особливо в enterprise-додатках, де важлива передбачуваність !!!
===================================================================================================================
Ось основні сучасні альтернативи для клонування об'єктів у C# 13 / .NET 9.
1) Records з with-експрешенами (рекомендовано для immutable даних):
Введено в C# 9, удосконалено в C# 12/13. Records — це reference-типи, але з автоматичним equals/hashcode/to-string
і вбудованим клонуванням через with.
Переваги: Автоматичний shallow-клон за замовчуванням, легко зробити deep для вкладених records.
Недоліки: Не для mutable станів (як у класі Monster, де змінюється Health).
2) Copy-конструктори:
Простий клас з конструктором, що приймає інший екземпляр того ж типу. Легко реалізувати shallow або deep.
Переваги: Явний, без інтерфейсів. Підходить для mutable класів.
Недоліки: Треба писати вручну для кожного класу.
3) Serialization (для deep-клону):
Використовуйте System.Text.Json або його альтернативи. Серіалізуйте об'єкт у потік і десеріалізуйте.
Переваги: Автоматичний deep-клон для складних графів.
Недоліки: Overhead на продуктивність, не для частих операцій
===================================================================================================================
приклад з рекордсами:
using System.Text;
namespace MakeClone
{
// monster як record: автоматично immutable, з вбудованим клонуванням через 'with'
public record Monster(int Health, int Mana, int Ammo, string Name);
class Program
{
static void Main()
{
Console.OutputEncoding = Encoding.UTF8;
Monster original = new(70, 50, 80, "Шепетуха");
Monster referenceCopy = original; // копія за посиланням (mutable зміни вплинуть на обидва)
Monster shallowCopy = original with { }; // shallow-клон через with (копіює значення, референси спільні)
Monster customCopy = original with { Name = "Клон " + original.Name, Health = original.Health }; // користувацький клон з модифікаціями
// демонстрація: records immutable, тож зміни створюють нові об'єкти
Monster modifiedOriginal = original with { Health = 100 };
Monster modifiedShallow = shallowCopy with { Health = 200 };
Monster modifiedCustom = customCopy with { Health = 300 };
Console.WriteLine("Інформація про монстрів:");
Console.WriteLine($"Оригінал: {original}");
Console.WriteLine("Копія за посиланням:");
Console.WriteLine($"Копія за посиланням: {referenceCopy}");
Console.WriteLine("Поверхнева копія:");
Console.WriteLine($"Поверхнева копія: {shallowCopy}");
Console.WriteLine("Користувацька копія:");
Console.WriteLine($"Користувацька копія: {customCopy}");
// перевірка незмінності: оригінал не змінився
Console.WriteLine("\nПісля модифікацій:");
Console.WriteLine($"Модифікований оригінал: {modifiedOriginal}");
Console.WriteLine($"Модифікована поверхнева: {modifiedShallow}");
Console.WriteLine($"Модифікована користувацька: {modifiedCustom}");
}
}
}
*/
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment