Как клонировать список (List<T>) в C#: полное руководство по копированию
Проблема поверхностного vs глубокого копирования
Вы столкнулись с классической проблемой: в C# объекты ссылочных типов (классы, списки) хранятся по ссылке. Простое присваивание или ToList() создают поверхностную копию (shallow copy), где копируется только ссылка на объект, а не сам объект. Для полной независимости нужно глубокое копирование (deep clone).
Вот несколько проверенных способов клонирования списка в C#:
Способ 1: Для простых типов (string, int, struct) — ToList() или конструктор
Для списков с типами-значениями (int, double, DateTime) или неизменяемыми ссылочными типами (string) достаточно конструктора или ToList():
List<int> numbers = new List<int> { 1, 2, 3 };
List<int> clone = new List<int>(numbers);
// Или: List<int> clone = numbers.ToList();
clone.Add(4);
Console.WriteLine(numbers.Count); // 3 — оригинал не изменился
Способ 2: Для ссылочных типов — ручное копирование через Select() + new
Если ваш класс простой и не содержит вложенных объектов:
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
}
List<Person> people = new List<Person>
{
new Person { Name = "Alice", Age = 30 },
new Person { Name = "Bob", Age = 25 }
};
List<Person> deepClone = people.Select(p => new Person
{
Name = p.Name,
Age = p.Age
}).ToList();
deepClone[0].Name = "Charlie";
Console.WriteLine(people[0].Name); // Alice — оригинал не изменился
Способ 3: Реализация ICloneable или метод Clone() в классе
Для сложных объектов с вложенными списками реализуйте интерфейс ICloneable:
public class Order : ICloneable
{
public int Id { get; set; }
public List<string> Items { get; set; }
public object Clone()
{
return new Order
{
Id = this.Id,
Items = this.Items?.ToList() // поверхностное копирование для string
};
}
}
// Использование:
List<Order> orders = new List<Order> { /* ... */ };
List<Order> clonedOrders = orders.Select(o => (Order)o.Clone()).ToList();
Способ 4: Сериализация (для самых сложных случаев)
Когда объекты имеют сложную иерархию и вложенные ссылки, используйте JSON-сериализацию (нужен Newtonsoft.Json или System.Text.Json):
using System.Text.Json;
public static T DeepClone<T>(T source)
{
string json = JsonSerializer.Serialize(source);
return JsonSerializer.Deserialize<T>(json);
}
// Использование:
List<Person> cloned = DeepClone(people);
Важно: Сериализация работает с публичными свойствами и требует атрибутов для приватных полей. Не подходит для объектов с циклическими ссылками.
Способ 5: Рефлексия (для универсального решения)
Библиотека AutoMapper или ручная рефлексия — но это уже оверхед для простых задач:
// Пример с AutoMapper (установить через NuGet)
var config = new MapperConfiguration(cfg => cfg.CreateMap<Person, Person>());
var mapper = config.CreateMapper();
List<Person> cloned = people.Select(p => mapper.Map<Person>(p)).ToList();
Рекомендация
- Для простых списков (int, string, struct) —
new List<T>(original)илиToList(). - Для списков объектов с простыми полями —
Select() + new. - Для сложных иерархий — сериализация или реализация
ICloneable.
Главное — помните: в C# нет встроенного глубокого клонирования для произвольных объектов. Выбирайте способ под свою задачу.
<<Альтернативный подход: Использование MemberwiseClone для базового класса
Если вы часто клонируете списки объектов, можно создать базовый класс с методом Clone(), использующим MemberwiseClone() для поверхностного копирования, а затем дорабатывать в наследниках для глубокого копирования:
public abstract class CloneableBase<T> where T : class
{
public T ShallowClone()
{
return (T)this.MemberwiseClone();
}
}
public class Employee : CloneableBase<Employee>
{
public string Name { get; set; }
public List<string> Skills { get; set; }
public Employee DeepClone()
{
var clone = this.ShallowClone();
clone.Skills = this.Skills?.ToList(); // глубокое копирование списка
return clone;
}
}
// Использование:
List<Employee> employees = new List<Employee>();
List<Employee> cloned = employees.Select(e => e.DeepClone()).ToList();
Ограничения этого подхода
MemberwiseClone()создает поверхностную копию — все ссылочные поля копируются как ссылки.- Требуется ручное копирование вложенных списков и объектов.
- Не подходит для sealed классов или структур.
Этот метод хорош, когда у вас есть иерархия классов и нужно переиспользовать логику клонирования. Для разовых задач проще использовать Select() + new или сериализацию.
Ответов пока нет.
Войдите чтобы оставить ответ