Подтвердить что ты не робот

Список клонирования <T>

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

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:

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

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);

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

4b9b3361

Ответ 1

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

Также вы можете использовать это для любой версии .NET framework от версии 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]);
}

Ответ 2

Это сработает...

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

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

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

cloneList.Add(anotherItem);

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

cloneList[0].SomeObjectProperty = 12345;

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

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

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

Ответ 3

Я сомневаюсь, что ваш фактический пример будет иметь проблемы, потому что 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 обычно рассматривается как Bad Thing, потому что его контракт настолько неопределен.

Ответ 4

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

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

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

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

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

var cloneList = originalList.Select(item => (ICloneable)item.Clone()).ToList();

Ответ 5

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

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();
}

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

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

Ответ 6

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

Ответ 7


            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).

Ответ 8

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

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