Как мне клонировать общий список в C #?


593

У меня есть общий список объектов в C #, и я хочу клонировать этот список. Элементы в списке являются клонируемыми, но, похоже, нет возможности сделать это list.Clone().

Есть ли простой способ обойти это?


44
Вы должны сказать, если вы ищете глубокую копию или мелкую копию
orip

10
Что такое глубокие и мелкие копии?
Полковник Паник


3
@orip clone()по определению не глубокая копия? В C # вы можете легко передавать указатели с помощью =, подумал я.
Крис

13
@ Кривая мелкая копия копирует на один уровень глубже, чем копия указателя. Например, мелкая копия списка будет иметь те же элементы, но будет другим списком.
orip

Ответы:


385

Вы можете использовать метод расширения.

static class Extensions
{
    public static IList<T> Clone<T>(this IList<T> listToClone) where T: ICloneable
    {
        return listToClone.Select(item => (T)item.Clone()).ToList();
    }
}

71
Я думаю, что List.ConvertAll может сделать это быстрее, так как он может предварительно выделить весь массив для списка, вместо того, чтобы постоянно изменять размер.
MichaelGG

2
@MichaelGG, что если вы не хотите конвертировать, а просто клонировать / дублировать элементы в списке? Будет ли это работать? || var clonedList = ListOfStrings.ConvertAll (p => p);
ИбрарМумтаз

29
@IbrarMumtaz: то же самое, что и var clonedList = new List <string> (ListOfStrings);
Брэндон Арнольд

4
Отличное решение! Кстати, я предпочитаю публичные статические List <T> CLone <T> ... Это более полезно в подобных случаях, потому что дальнейшее приведение не требуется: List <MyType> cloned = listToClone.Clone ();
Плутос

2
это глубокое клонирование
Джордж Бирбилис

513

Если ваши элементы являются типами значений, то вы можете просто сделать:

List<YourType> newList = new List<YourType>(oldList);

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

List<ICloneable> oldList = new List<ICloneable>();
List<ICloneable> newList = new List<ICloneable>(oldList.Count);

oldList.ForEach((item) =>
    {
        newList.Add((ICloneable)item.Clone());
    });

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

Если ваш тип элемента не поддерживает, ICloneableно имеет конструктор копирования, вы можете сделать это вместо этого:

List<YourType> oldList = new List<YourType>();
List<YourType> newList = new List<YourType>(oldList.Count);

oldList.ForEach((item)=>
    {
        newList.Add(new YourType(item));
    });

Лично я бы избежал ICloneableиз-за необходимости гарантировать глубокую копию всех участников. Вместо этого я бы предложил конструктор копирования или такой метод фабрики, YourType.CopyFrom(YourType itemToCopy)который возвращает новый экземпляр YourType.

Любой из этих параметров может быть заключен в метод (расширение или иным образом).


1
Я думаю, что List <T> .ConvertAll может выглядеть лучше, чем создание нового списка и выполнение foreach + add.
MichaelGG

2
@Dimitri: Нет, это не правда. Проблема в том, что когда оно ICloneableбыло определено, определение никогда не указывало, был ли клон глубоким или неглубоким, поэтому вы не можете определить, какой тип операции клонирования будет выполняться, когда объект реализует ее. Это означает, что если вы хотите сделать глубокий клон List<T>, вам придется делать это, не ICloneableбудучи уверенным, что это глубокая копия.
Джефф Йейтс

5
Почему бы не использовать метод AddRange? ( newList.AddRange(oldList.Select(i => i.Clone())или newList.AddRange(oldList.Select(i => new YourType(i))
фото

5
@phoog: я думаю, что это немного менее читаемо / понятно при сканировании кода, вот и все. Читаемость выигрывает для меня.
Джефф Йейтс

1
@JeffYates: Одна недостаточно продуманная проблема заключается в том, что вещи, как правило, нужно копировать только в том случае, если существует какой-либо путь выполнения, который может их изменить. Это очень распространено иметь неизменные типы содержат ссылку на экземпляр изменяемого типа, но никогда не подвергать этот экземпляр к чему - либо , что будет мутировать его. Излишнее копирование того, что никогда не изменится, иногда может привести к значительному снижению производительности, увеличивая использование памяти на порядок.
суперкат

84

Для поверхностной копии вы можете вместо этого использовать метод GetRange универсального класса List.

List<int> oldList = new List<int>( );
// Populate oldList...

List<int> newList = oldList.GetRange(0, oldList.Count);

Цитируется из: Рецепты дженериков


43
Вы также можете достичь этого, используя конструктор List <T>, чтобы указать List <T>, из которого копировать. например, var shallowClonedList = new List <MyObject> (originalList);
Arkiliknam

9
Я часто использую List<int> newList = oldList.ToList(). Тот же эффект. Однако, на мой взгляд, решение Arkiliknam лучше всего подходит для удобства чтения.
Дэн Бешард

82
public static object DeepClone(object obj) 
{
  object objResult = null;
  using (MemoryStream  ms = new MemoryStream())
  {
    BinaryFormatter  bf =   new BinaryFormatter();
    bf.Serialize(ms, obj);

    ms.Position = 0;
    objResult = bf.Deserialize(ms);
  }
  return objResult;
}

Это один из способов сделать это с C # и .NET 2.0. Ваш объект должен быть [Serializable()]. Цель состоит в том, чтобы потерять все ссылки и построить новые.


11
+1 - мне нравится этот ответ - он быстрый, грязный, противный и очень эффективный. Я использовал Silverlight и использовал DataContractSerializer, так как BinarySerializer был недоступен. Кому нужно писать страницы кода для клонирования объектов, когда вы можете просто сделать это? :)
Слагстер

3
Мне это нравится. Хотя хорошо делать вещи «правильно», быстрые и грязные часто оказываются полезными.
Одраде

3
Быстро! но: почему грязный?
Райзер

2
Это глубокие клоны и быстрые и простые. Осторожно обращайтесь с другими предложениями на этой странице. Я пробовал несколько, и они не глубокий клон.
Рэндалл до

2
Единственный негативный аспект, если вы можете это так называть, это то, что ваши классы должны быть помечены как Serializable, чтобы это работало.
Туукка Хаапаниеми,

30

Чтобы клонировать список, просто вызовите .ToList (). Это создает мелкую копию.

Microsoft (R) Roslyn C# Compiler version 2.3.2.62116
Loading context from 'CSharpInteractive.rsp'.
Type "#help" for more information.
> var x = new List<int>() { 3, 4 };
> var y = x.ToList();
> x.Add(5)
> x
List<int>(3) { 3, 4, 5 }
> y
List<int>(2) { 3, 4 }
> 


29
Небольшое предупреждение, это мелкая копия ... Это создаст два объекта списка, но объекты внутри будут одинаковыми. Т.е. изменение одного свойства изменит тот же объект / свойство в исходном списке.
Марк Г

22

После небольшой модификации вы также можете клонировать:

public static T DeepClone<T>(T obj)
{
    T objResult;
    using (MemoryStream ms = new MemoryStream())
    {
        BinaryFormatter bf = new BinaryFormatter();
        bf.Serialize(ms, obj);
        ms.Position = 0;
        objResult = (T)bf.Deserialize(ms);
    }
    return objResult;
}

Не забудьте, что T должен быть сериализуемым, иначе вы получите System.Runtime.Serialization.SerializationException.
Бенс Вегерт

Хороший ответ. Один совет: вы можете добавить if (!obj.GetType().IsSerializable) return default(T);в качестве первого утверждения, которое предотвращает исключение. И если вы измените его на метод расширения, вы можете даже использовать оператор Элвиса, как var b = a?.DeepClone();( var a = new List<string>() { "a", "b" }; например, приведенный).
Мэтт

15

Если вам не нужен фактический клон каждого отдельного объекта внутри List<T>, лучший способ клонировать список - создать новый список со старым списком в качестве параметра коллекции.

List<T> myList = ...;
List<T> cloneOfMyList = new List<T>(myList);

Изменения, myListтакие как вставка или удаление, не влияют cloneOfMyListи наоборот.

Однако фактические объекты, содержащиеся в двух списках, остаются прежними.


Я согласен с user49126, я вижу, что это мелкая копия и изменения, внесенные в один список, отражаются в другом списке.
Seidleroni

1
@ Seidleroni, ты не прав. Изменения, внесенные в список, изменяются в другом списке, изменения в самом списке - нет.
Веллингтон Занелли

Это мелкая копия.
Эллиот Чен

Как это мелкая копия?
mko

2
@WellingtonZanelli Только что подтвердил, что удаление элемента из myList удаляет его и из cloneOfMyList.
Ник

13

Использовать AutoMapper (или любую другую библиотеку картирования, которую вы предпочитаете), чтобы клонировать, просто и много поддерживаемо.

Определите ваше отображение:

Mapper.CreateMap<YourType, YourType>();

Сделай магию:

YourTypeList.ConvertAll(Mapper.Map<YourType, YourType>);

13

Если вы заботитесь только о типах значений ...

И вы знаете тип:

List<int> newList = new List<int>(oldList);

Если вы не знаете тип раньше, вам понадобится вспомогательная функция:

List<T> Clone<T>(IEnumerable<T> oldList)
{
    return newList = new List<T>(oldList);
}

Просто:

List<string> myNewList = Clone(myOldList);

15
Это не клонирует элементы.
Джефф Йейтс

10

Если вы уже ссылались на Newtonsoft.Json в своем проекте и ваши объекты сериализуемы, вы всегда можете использовать:

List<T> newList = JsonConvert.DeserializeObject<T>(JsonConvert.SerializeObject(listToCopy))

Возможно, это не самый эффективный способ сделать это, но если вы не сделаете это сотни тысяч раз, вы можете даже не заметить разницу в скорости.


4
Дело не в разнице в скорости, а в удобочитаемости. Если бы я пришел к этой строке кода, я бы ударил себя по голове и удивился, почему они представили стороннюю библиотеку для сериализации, а затем десериализации объекта, который я бы не знал, почему это происходит. Кроме того, это не будет работать для списка моделей с объектами, которые имеют круглую структуру.
Джонатон Цвик,

1
Этот код отлично работал для меня для глубокого клонирования. Приложение переносит шаблонный документ с Dev на QA на Prod. Каждый объект представляет собой пакет из нескольких объектов шаблона документа, и каждый документ, в свою очередь, состоит из списка объектов абзаца. Этот код позволяет мне сериализовать «исходные» объекты .NET и немедленно десериализовать их в новые «целевые» объекты, которые затем сохраняются в базе данных SQL в другой среде. После множества исследований я нашел много вещей, большая часть которых была слишком громоздкой, и решил попробовать это. Этот короткий и гибкий подход был «просто правильным»!
Developer63

3
public static Object CloneType(Object objtype)
{
    Object lstfinal = new Object();

    using (MemoryStream memStream = new MemoryStream())
    {
        BinaryFormatter binaryFormatter = new BinaryFormatter(null, new StreamingContext(StreamingContextStates.Clone));
        binaryFormatter.Serialize(memStream, objtype); memStream.Seek(0, SeekOrigin.Begin);
        lstfinal = binaryFormatter.Deserialize(memStream);
    }

    return lstfinal;
}

3
public class CloneableList<T> : List<T>, ICloneable where T : ICloneable
{
  public object Clone()
  {
    var clone = new List<T>();
    ForEach(item => clone.Add((T)item.Clone()));
    return clone;
  }
}

3
    public List<TEntity> Clone<TEntity>(List<TEntity> o1List) where TEntity : class , new()
    {
        List<TEntity> retList = new List<TEntity>();
        try
        {
            Type sourceType = typeof(TEntity);
            foreach(var o1 in o1List)
            {
                TEntity o2 = new TEntity();
                foreach (PropertyInfo propInfo in (sourceType.GetProperties()))
                {
                    var val = propInfo.GetValue(o1, null);
                    propInfo.SetValue(o2, val);
                }
                retList.Add(o2);
            }
            return retList;
        }
        catch
        {
            return retList;
        }
    }

3

Мой друг Грегор Мартинович и я придумали это простое решение с использованием JavaScript-сериализатора. Нет необходимости помечать классы как Serializable, и в наших тестах использование Newtonsoft JsonSerializer даже быстрее, чем использование BinaryFormatter. С методами расширения, применимыми к каждому объекту.

Стандартная опция .NET JavascriptSerializer:

public static T DeepCopy<T>(this T value)
{
    JavaScriptSerializer js = new JavaScriptSerializer();

    string json = js.Serialize(value);

    return js.Deserialize<T>(json);
}

Более быстрый вариант с использованием Newtonsoft JSON :

public static T DeepCopy<T>(this T value)
{
    string json = JsonConvert.SerializeObject(value);

    return JsonConvert.DeserializeObject<T>(json);
}

2
Закрытые члены не клонируются с использованием метода JSON. stackoverflow.com/a/78612/885627
himanshupareek66

3
 //try this
 List<string> ListCopy= new List<string>(OldList);
 //or try
 List<T> ListCopy=OldList.ToList();

3

Я буду счастлив, если кто-нибудь когда-нибудь прочтет это ... но чтобы не возвращать список объектов типа в моих методах Clone, я создал интерфейс:

public interface IMyCloneable<T>
{
    T Clone();
}

Затем я указал расширение:

public static List<T> Clone<T>(this List<T> listToClone) where T : IMyCloneable<T>
{
    return listToClone.Select(item => (T)item.Clone()).ToList();
}

И вот реализация интерфейса в моем программном обеспечении маркировки A / V. Я хотел, чтобы мой метод Clone () возвращал список VidMark (в то время как интерфейс ICloneable хотел, чтобы мой метод возвращал список объектов):

public class VidMark : IMyCloneable<VidMark>
{
    public long Beg { get; set; }
    public long End { get; set; }
    public string Desc { get; set; }
    public int Rank { get; set; } = 0;

    public VidMark Clone()
    {
        return (VidMark)this.MemberwiseClone();
    }
}

И, наконец, использование расширения внутри класса:

private List<VidMark> _VidMarks;
private List<VidMark> _UndoVidMarks;

//Other methods instantiate and fill the lists

private void SetUndoVidMarks()
{
    _UndoVidMarks = _VidMarks.Clone();
}

Кто-нибудь любит это? Какие-нибудь улучшения?


2

Вы также можете просто преобразовать список в массив с помощью ToArray, а затем клонировать массив с помощью Array.Clone(...). В зависимости от ваших потребностей методы, включенные в класс Array, могут удовлетворить ваши потребности.


Это не работает; изменения значений в клонированном массиве все еще изменяют значения в исходном списке.
Ящерица Бернулли

Вы можете использовать var clonedList = ListOfStrings.ConvertAll (p => p); как дано @IbrarMumtaz .... Работает эффективно ... Изменения в одном списке хранятся при себе и не отражаются в другом
zainul

2

Вы можете использовать метод расширения:

namespace extension
{
    public class ext
    {
        public static List<double> clone(this List<double> t)
        {
            List<double> kop = new List<double>();
            int x;
            for (x = 0; x < t.Count; x++)
            {
                kop.Add(t[x]);
            }
            return kop;
        }
   };

}

Вы можете клонировать все объекты, используя их членов типа значения, например, рассмотрим этот класс:

public class matrix
{
    public List<List<double>> mat;
    public int rows,cols;
    public matrix clone()
    { 
        // create new object
        matrix copy = new matrix();
        // firstly I can directly copy rows and cols because they are value types
        copy.rows = this.rows;  
        copy.cols = this.cols;
        // but now I can no t directly copy mat because it is not value type so
        int x;
        // I assume I have clone method for List<double>
        for(x=0;x<this.mat.count;x++)
        {
            copy.mat.Add(this.mat[x].clone());
        }
        // then mat is cloned
        return copy; // and copy of original is returned 
    }
};

Примечание: если вы внесете какие-либо изменения в копию (или клон), это не повлияет на исходный объект.


2

Если вам нужен клонированный список с той же емкостью, вы можете попробовать это:

public static List<T> Clone<T>(this List<T> oldList)
{
    var newList = new List<T>(oldList.Capacity);
    newList.AddRange(oldList);
    return newList;
}

1

Я сделал для себя какое-то расширение, которое конвертирует ICollection элементов, которые не реализуют IClonable

static class CollectionExtensions
{
    public static ICollection<T> Clone<T>(this ICollection<T> listToClone)
    {
        var array = new T[listToClone.Count];
        listToClone.CopyTo(array,0);
        return array.ToList();
    }
}

Кажется, некоторые коллекции (например, SelectedItems DataGrid в Silverlight) пропускают реализацию CopyTo, что является проблемой при таком подходе
Джордж Бирбилис

1

Я использую Autopper для копирования объекта. Я просто настроил отображение, которое отображает один объект на себя. Вы можете обернуть эту операцию любым удобным вам способом.

http://automapper.codeplex.com/


1

Использование приведения может быть полезно, в этом случае, для мелкой копии:

IList CloneList(IList list)
{
    IList result;
    result = (IList)Activator.CreateInstance(list.GetType());
    foreach (object item in list) result.Add(item);
    return result;
}

применяется к общему списку:

List<T> Clone<T>(List<T> argument) => (List<T>)CloneList(argument);

1

Для глубокой копии ICloneable является правильным решением, но вот аналогичный подход к ICloneable с использованием конструктора вместо интерфейса ICloneable.

public class Student
{
  public Student(Student student)
  {
    FirstName = student.FirstName;
    LastName = student.LastName;
  }

  public string FirstName { get; set; }
  public string LastName { get; set; }
}

// wherever you have the list
List<Student> students;

// and then where you want to make a copy
List<Student> copy = students.Select(s => new Student(s)).ToList();

вам понадобится следующая библиотека, где вы делаете копию

using System.Linq

Вы также можете использовать цикл for вместо System.Linq, но Linq делает его лаконичным и понятным. Точно так же вы могли бы делать, как предлагали другие ответы, и делать методы расширения и т. Д., Но ничего из этого не требуется.


Это называется «конструктор копирования». Это подход, подверженный ошибкам, всякий раз, когда вы добавляете новое поле в Student, вы должны помнить, чтобы добавить его в конструктор копирования. Основная идея «клона» состоит в том, чтобы избежать этой проблемы.
Кенно

2
Даже с ICloneable у вас должен быть метод «Клон» в вашем классе. Если вы не используете отражение (которое вы могли бы также использовать в вышеупомянутом подходе), этот метод Clone будет выглядеть очень похоже на подход конструктора копирования, описанный выше, и будет страдать от той же проблемы необходимости обновления для новых / измененных полей. Но это говорит о том, что «класс должен обновляться при изменении полей класса». Конечно, это так;)
ztorstri

0

Следующий код должен перейти в список с минимальными изменениями.

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

// Example Usage
int[] indexes = getRandomUniqueIndexArray(selectFrom.Length, toSet.Length);

for(int i = 0; i < toSet.Length; i++)
    toSet[i] = selectFrom[indexes[i]];


private int[] getRandomUniqueIndexArray(int length, int count)
{
    if(count > length || count < 1 || length < 1)
        return new int[0];

    int[] toReturn = new int[count];
    if(count == length)
    {
        for(int i = 0; i < toReturn.Length; i++) toReturn[i] = i;
        return toReturn;
    }

    Random r = new Random();
    int startPos = count - 1;
    for(int i = startPos; i >= 0; i--)
    {
        int index = r.Next(length - i);
        for(int j = startPos; j > i; j--)
            if(toReturn[j] >= index)
                toReturn[j]++;
        toReturn[i] = index;
    }

    return toReturn;
}

0

Другое дело: вы могли бы использовать отражение. Если вы кешируете это правильно, то он клонирует 1 000 000 объектов за 5,6 секунды (к сожалению, 16,4 секунды с внутренними объектами).

[ProtoContract(ImplicitFields = ImplicitFields.AllPublic)]
public class Person
{
       ...
      Job JobDescription
       ...
}

[ProtoContract(ImplicitFields = ImplicitFields.AllPublic)]
public class Job
{...
}

private static readonly Type stringType = typeof (string);

public static class CopyFactory
{
    static readonly Dictionary<Type, PropertyInfo[]> ProperyList = new Dictionary<Type, PropertyInfo[]>();

    private static readonly MethodInfo CreateCopyReflectionMethod;

    static CopyFactory()
    {
        CreateCopyReflectionMethod = typeof(CopyFactory).GetMethod("CreateCopyReflection", BindingFlags.Static | BindingFlags.Public);
    }

    public static T CreateCopyReflection<T>(T source) where T : new()
    {
        var copyInstance = new T();
        var sourceType = typeof(T);

        PropertyInfo[] propList;
        if (ProperyList.ContainsKey(sourceType))
            propList = ProperyList[sourceType];
        else
        {
            propList = sourceType.GetProperties(BindingFlags.Public | BindingFlags.Instance);
            ProperyList.Add(sourceType, propList);
        }

        foreach (var prop in propList)
        {
            var value = prop.GetValue(source, null);
            prop.SetValue(copyInstance,
                value != null && prop.PropertyType.IsClass && prop.PropertyType != stringType ? CreateCopyReflectionMethod.MakeGenericMethod(prop.PropertyType).Invoke(null, new object[] { value }) : value, null);
        }

        return copyInstance;
    }

Я измерил это простым способом, используя класс Watcher.

 var person = new Person
 {
     ...
 };

 for (var i = 0; i < 1000000; i++)
 {
    personList.Add(person);
 }
 var watcher = new Stopwatch();
 watcher.Start();
 var copylist = personList.Select(CopyFactory.CreateCopyReflection).ToList();
 watcher.Stop();
 var elapsed = watcher.Elapsed;

РЕЗУЛЬТАТ: С внутренним объектом PersonInstance - 16.4, PersonInstance = null - 5.6

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

Я еще не тестировал сериализацию, но сомневаюсь в улучшении с миллионом классов. Я попробую что-нибудь быстрое protobuf / newton.

PS: для простоты чтения я использовал здесь только авто-свойства. Я мог бы обновить с FieldInfo, или вы должны легко реализовать это самостоятельно.

Я недавно протестировал сериализатор Protocol Buffers с функцией DeepClone из коробки. Он выигрывает за 4,2 секунды на миллионе простых объектов, но когда дело доходит до внутренних объектов, он выигрывает с результатом 7,4 секунды.

Serializer.DeepClone(personList);

РЕЗЮМЕ: Если у вас нет доступа к классам, это поможет. В противном случае это зависит от количества объектов. Я думаю, что вы могли бы использовать отражение до 10 000 объектов (возможно, немного меньше), но для большего, чем этот, сериализатор Protocol Buffers будет работать лучше.


0

Существует простой способ клонирования объектов в C # с использованием сериализатора и десериализатора JSON.

Вы можете создать класс расширения:

using Newtonsoft.Json;

static class typeExtensions
{
    [Extension()]
    public static T jsonCloneObject<T>(T source)
    {
    string json = JsonConvert.SerializeObject(source);
    return JsonConvert.DeserializeObject<T>(json);
    }
}

Чтобы клонировать и возражать:

obj clonedObj = originalObj.jsonCloneObject;
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.