Список клонирования <T> - PullRequest
23 голосов
/ 06 февраля 2009

Я думал, что для клонирования списка вы бы просто позвонили:

List<int> cloneList = new List<int>(originalList);

Но я попробовал это в своем коде и, похоже, получаю эффекты, которые подразумевают, что вышеперечисленное просто делает:

cloneList = originalList ... потому что изменения в cloneList, похоже, влияют на originalList.

Так как же клонировать Список?

EDIT:

Я думаю сделать что-то вроде этого:

public static List<T> Clone<T>(this List<T> originalList) where T : ICloneable
{
    return originalList.ConvertAll(x => (T) x.Clone());
}

EDIT2:

Я взял код глубокой копии, предложенный Binoj Antony, и создал этот метод расширения:

public static T DeepCopy<T>(this T original) where T : class
{
    using (MemoryStream memoryStream = new MemoryStream())
    {
        BinaryFormatter binaryFormatter = new BinaryFormatter();
        binaryFormatter.Serialize(memoryStream, original);
        memoryStream.Seek(0, SeekOrigin.Begin);
        return (T)binaryFormatter.Deserialize(memoryStream);
    }
}

EDIT3:

Теперь, скажем, элементы в Списке являются структурами. Что тогда будет, если я позвоню?:

List<StructType> cloneList = new List<StructType>(originalList);

Я почти уверен, что получу Список, заполненный новыми уникальными предметами, верно?

Ответы [ 8 ]

21 голосов
/ 06 февраля 2009

Это будет работать ...

List<Foo> cloneList = new List<Foo>(originalList);

Когда вы говорите «потому что изменения в cloneList, похоже, влияют на originalList». - Вы имеете в виду изменения в списке или изменения пунктов ...

Добавление / удаление / замена элементов приводит к изменению списка - так что если мы сделаем:

cloneList.Add(anotherItem);

вы должны обнаружить, что cloneList длиннее originalList. Однако, если содержимое относится к ссылочным типам (классам), тогда оба списка по-прежнему указывают на одни и те же базовые объекты - так что если мы сделаем:

cloneList[0].SomeObjectProperty = 12345;

тогда это также покажет против originalList[0].SomeObjectProperty - существует только один объект (общий для обоих списков).

Если это проблема, вам нужно будет клонировать объекты в списке - и тогда вы столкнетесь с проблемой глубокого против мелкого ... это проблема?

Для мелкой копии вы можете использовать что-то очень похожее на ответ здесь - просто с TTo = TFrom (возможно, упростить до одного T).

16 голосов
/ 06 февраля 2009

Вы можете использовать приведенный ниже код для создания глубокой копии списка или любого другого объекта, поддерживающего сериализацию:

Также вы можете использовать это для любой версии .NET Framework от v 2.0 и выше, и аналогичный метод может быть применен (исключая использование обобщений) и использован в 1.1 также

public static class GenericCopier<T>
{
    public static T DeepCopy(object objectToCopy)
    {
        using (MemoryStream memoryStream = new MemoryStream())
        {
            BinaryFormatter binaryFormatter = new BinaryFormatter();
            binaryFormatter.Serialize(memoryStream, objectToCopy);
            memoryStream.Seek(0, SeekOrigin.Begin);
            return (T) binaryFormatter.Deserialize(memoryStream);
        }
    }
}

Вы можете позвонить с помощью

List<int> deepCopiedList = GenericCopier<List<int>>.DeepCopy(originalList);

Полный код для проверки работоспособности:

static void Main(string[] args)
{
    List<int> originalList = new List<int>(5);
    Random random = new Random();
    for(int i = 0; i < 5; i++)
    {
        originalList.Add(random.Next(1, 100));
        Console.WriteLine("List[{0}] = {1}", i, originalList[i]);
    }
    List<int> deepCopiedList = GenericCopier<List<int>>.DeepCopy(originalList);
    for (int i = 0; i < 5; i++)
        Console.WriteLine("deepCopiedList[{0}] value is {1}", i, deepCopiedList[i]);
}
8 голосов
/ 06 февраля 2009

Я сомневаюсь, что у вашего фактического примера будут проблемы, потому что int является типом значения. Например:

using System;
using System.Collections.Generic;

class Test
{
    static void Main()
    {
        List<int> originalList = new List<int> { 5, 6, 7 };
        List<int> cloneList = new List<int>(originalList);

        cloneList.Add(8);
        cloneList[0] = 2;
        Console.WriteLine(originalList.Count); // Still 3
        Console.WriteLine(originalList[0]); // Still 5
    }
}

Однако, как говорит Марк, если ваш список содержит изменяемые ссылочные типы, клонирование списка займет только мелкую копию - если вы измените объекты, на которые ссылаются списки, эти изменения будут видны через оба списка. Замена элементов в одном списке не изменит эквивалентного элемента в другом списке:

using System;
using System.Collections.Generic;

class Dummy
{
    public int Value { get; set; }

    public Dummy (int value)
    {
        this.Value = value;
    }
}

class Test
{
    static void Main()
    {
        List<Dummy> originalList = new List<Dummy> 
        {
            new Dummy(5),
            new Dummy(6),
            new Dummy(7)
        };

        List<Dummy> cloneList = new List<Dummy>(originalList);

        cloneList[0].Value = 1;
        cloneList[1] = new Dummy(2);
        Console.WriteLine(originalList[0].Value); // Changed to 1
        Console.WriteLine(originalList[1].Value); // Still 6
    }
}

Чтобы получить «глубокий клон» списка, в котором тип элемента реализует ICloneable, используйте:

List<Foo> cloneList = originalList.ConvertAll(x => (Foo) x.Clone());

Однако реальная глубина этого клона будет зависеть от реализации ICloneable в типе элемента - ICloneable обычно считается плохой вещью, поскольку его контракт настолько расплывчатый.

2 голосов
/ 06 февраля 2009

Я голосую, чтобы не полагаться на сериализацию объекта. Это дорогостоящая и плохая практика.

public static TObj CloneObject<TObj>(this TObj obj)
    where TObj : ICloneable
{
    return (TObj)obj.Clone();
}

Вышеуказанный метод намного более элегантен, и вам действительно нужно реализовать клонируемый интерфейс, если он вам нужен. Вы также можете сделать его общим.

public interface ICloneable<T> : IClonable
{
    T CloneObject();
}

При желании вы можете отказаться от использования интерфейса IClonable в качестве базового типа, так как он плохо поддерживается. Имя метода должно измениться, потому что вы не можете делать перегрузки для возвращаемых типов.

public static List<T> CloneList(this List<T> source)
    where TObj : ICloneable
{
    return source.Select(x=>x.CloneObject()).ToList();
}

Это так просто.

Возможно, вашу проблему можно решить, используя вместо этого типы значений. Они всегда проходят мимо копии. Таким образом, вам никогда не придется ничего клонировать, если ваша структура данных по значению.

2 голосов
/ 06 февраля 2009

Использование конструктора List с исходным списком в качестве параметра будет работать, если базовый тип списка имеет тип значения . Для справочного типа элементы списка, я думаю, вы хотите глубокое копирование их.

Вы могли бы сделать что-то вроде этого:

(Предполагая, что базовый тип реализует ICloneable)

originalList.ForEach((item) =>
                       {
                         cloneList.Add((ICloneable)item.Clone());
                       }
                     );

Или используя LINQ :

var cloneList = originalList.Select(item => (ICloneable)item.Clone()).ToList();
1 голос
/ 06 февраля 2009

Здесь конкретно указано здесь , что элементы копируются в ваш новый список. Так что да, это должно работать. С типами значений вы получите полную независимость. Но помните, что со ссылочными типами списки будут независимыми, но они будут указывать на одни и те же объекты. Вам нужно будет глубоко скопировать список.

0 голосов
/ 06 февраля 2009

Я должен добавить: если вы собираетесь использовать сериализацию для облегчения глубокого копирования, зачем вам клонировать каждый отдельный элемент? Просто начните с клонирования всего оригинального списка.

Если у вас нет логики, согласно которой вы клонируете только узлы, которые удовлетворяют определенным критериям, то делаете это узел за узлом.

0 голосов
/ 06 февраля 2009

            List list = new List ();
            List clone = new List (list);
            list.Add (new int ());
            Debug.Assert (list != clone);
            Debug.Assert (list.Count == 1);
            Debug.Assert (clone.Count == 0);

Этот код отлично работает, как и задумывалось для меня. Возможно, вы меняете объекты в списке? Элементы списка не будут клонированы как new List(oldList).

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...