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

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

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

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

orip 23.11.2008 13:25

Что такое глубокие и мелкие копии?

Colonel Panic 27.09.2012 15:03

@ColonelPanic en.wikipedia.org/wiki/Object_copy#Shallow_copy

Nathan Koop 14.10.2012 00:24

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

Chris 19.12.2012 00:51

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

orip 19.12.2012 02:15

Где глубокая копия будет новым списком с новыми элементами, но с тем же содержанием.

user4843530 24.11.2015 19:00

Отметьте этот Отвечать: stackoverflow.com/a/52097307/4707576 about: Клонирование объектов без сериализации

Ahmed Sabry 30.08.2018 15:40
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
645
7
691 113
27
Перейти к ответу Данный вопрос помечен как решенный

Ответы 27

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

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

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

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

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

Arkiliknam 16.02.2012 18:58

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

Dan Bechard 25.10.2012 22:27

@DanBechard Спустя годы, но я предпочитаю ToList, поскольку он избегает всех дублирований - интересно, что на самом деле более производительно ... посмотрел. Похоже, список ToList вызывает new List<T>, который в конечном итоге будет использовать Array.CopyTo, так что примерно то же самое.

NetMage 20.11.2020 21:59

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

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.

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

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

MichaelGG 21.10.2008 21:42

Хорошая точка зрения. Должен признать, я все еще сам разбираюсь со всеми вызовами LINQ.

Jeff Yates 22.10.2008 19:57

+1 Таким образом, невозможно обеспечить функцию глубокого клонирования для Generic.List. Это правильно?

Dimitri C. 09.09.2010 14:12

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

Jeff Yates 10.09.2010 18:36

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

phoog 21.12.2010 19:00

@phoog: Конечно, это еще один вариант. Это не имеет большого значения.

Jeff Yates 21.12.2010 21:04

@ Джефф Йейтс: имеет значение, если аргумент коллекции для AddRange реализует ICollection <T>. В этом случае список использует свойство count коллекции, поэтому ему не нужно увеличивать его емкость более одного раза. Это (потенциально) экономит выделение памяти. Конечно, в большинстве случаев экономия незначительна, так что по большей части вы правы, это не имеет большого значения.

phoog 21.12.2010 22:43

@phoog: Я бы избегал такой оптимизации, если только профилирование не показало, что это необходимо.

Jeff Yates 21.12.2010 23:36

@ Джефф Йейтс: Достаточно честно. Я работал с приложением, которое использует огромные списки, массивы которых достаточно велики, чтобы находиться в куче больших объектов, так что в этом случае это полезная оптимизация. Мне любопытно, почему вы этого избегаете.

phoog 22.12.2010 01:37

@phoog: Я думаю, что при сканировании кода он немного менее читабелен / понятен, вот и все. Для меня удобочитаемость.

Jeff Yates 22.12.2010 18:18

почему бы не использовать ToArray () - который действительно создает глубокую копию - он эффективно использует memmove

markmnl 30.05.2011 08:21

@Feanor: Это не совсем так. Он копирует значения непосредственных элементов, но как насчет дочерних значений? Как он узнает, должна ли каждая ссылка в моем типе быть собственной уникальной копией или общим экземпляром? Это не так. Глубокая копия - это копия, которая просматривает весь граф объектов для экземпляра и гарантирует, что все элементы, которые он содержит или ссылки, также являются копиями, а их ссылки являются копиями и т. д. И т. Д.

Jeff Yates 30.05.2011 23:46

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

markmnl 31.05.2011 05:08

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

supercat 24.09.2013 01:25

@supercat: Действительно, вам решать, что нужно клонировать при клонировании объекта, что является еще одной причиной, по которой клонирование может быть настолько трудным.

Jeff Yates 24.09.2013 19:54

@JeffYates: Прискорбно то, что правила не сложные - они просто зависят от чего-то, что система типов не имеет стандартного способа выражения. Если бы система типов могла украсить типы мест хранения парой атрибутов флагов, чтобы указать, инкапсулируют ли они идентичность, изменяемое состояние, оба или ни то, ни другое, эффективное и надежное глубокое клонирование было бы простым в 99% случаев, даже для объектов, которые по своей сути не сериализуемы. Обратите внимание, что это должны быть черты типов мест хранения, а не типы кучи и не поля.

supercat 24.09.2013 21:06

Если Identity<T> рассматривался как ссылка на T, используемый только для инкапсуляции идентичности, а MutableState<T> использовался для ссылки на тот, который инкапсулирует изменяемое состояние, но не идентичность, копия MutableState<List<Identity<T>>> должна содержать ссылку на новый список со ссылками на одинаковые предметы; копия MutableState<List<MutableState<T>>> должна содержать ссылку на новый список с копиями элементов в нем.

supercat 24.09.2013 21:22

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

Jeff Yates 24.09.2013 22:07

Мне просто нужно было напомнить о первой строке - поэтому я предпочитаю этот ответ, спасибо @JeffYates!

Ian Grainger 02.05.2014 15:20

Я согласен с @phoog, но если вы думаете, что AddRange с Linq менее читабельны, то почему бы просто не использовать foreach. ForEachсложнее понять, сложнее отлаживать и вводит семантику закрытия

juharr 10.09.2015 16:12

Я думаю, что продолжение @supercat состоит в том, что в вашей текущей таксономии есть дыра. У вас есть типы значений и ссылочные типы, которые вы можете изменить, но не ссылочные типы, для которых уместны мелкие копии. Возможно: «Если ваши элементы являются типами значений[или ссылочные типы, где вас беспокоит только список членство], тогда вы можете просто сделать [мелкий конструктор клонов]». С ссылочными типами вы можете захотеть отредактировать членство newList, не влияя на членство в oldList, хотя вы хотите редактировать общие элементы. TL; DR: Может быть, явным образом сделать мелкие и глубокие варианты использования с ссылочными типами.

ruffin 17.03.2018 17:32

Если вас интересуют только типы значений ...

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

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

Это не клонирует элементы.

Jeff Yates 21.10.2008 20:57
Ответ принят как подходящий

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

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

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

MichaelGG 21.10.2008 21:43

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

IbrarMumtaz 17.08.2014 19:42

@IbrarMumtaz: это то же самое, что и var clonedList = new List <string> (ListOfStrings);

Brandon Arnold 16.09.2014 18:00

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

Plutoz 15.05.2015 10:02

@BrandonArnold, вы ошибаетесь, а что, если T - объект, а не тип значения?

Brian Ogden 08.08.2016 05:34

@BrianOgden Разве делегат ConvertAll в его примере также не сохраняет ссылочные типы? Стрелочной функции потребуется конструктор копирования или сериализатор клонирования (например, p => new PType (p)).

Brandon Arnold 08.08.2016 05:45

Извините, @BrandonArnold, вы правы, я думал, что ваш комментарий относится к ответу, вы комментируете комментарий от IbrarMumtaz, мое плохое, продолжайте, как вы были :)

Brian Ogden 08.08.2016 18:01

Разве это не половина ответа, поскольку он основан на реализации ICloneable, что было бы важной частью вопроса?

Everts 16.12.2019 10:19

Я согласен, что это только половина ответа, поскольку в нем отсутствует часть IClonable.

AH. 01.03.2021 15:55
public static object DeepClone(object obj) 
{
    object objResult = null;

    using (var ms = new MemoryStream())
    {
        var bf = new BinaryFormatter();
        bf.Serialize(ms, obj);

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

     return objResult;
}

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

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

slugster 02.03.2010 14:52

Мне это нравится. Хотя делать что-то «правильно» приятно, быстро и грязно часто бывает кстати.

Odrade 15.12.2010 21:11

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

raiserle 12.12.2013 15:54

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

RandallTo 29.05.2015 06:14

Мне нравится гадкое и грязное ^^ Это похоже на JSON.parse (JSON.stringify (obj)). Блестяще!

Roman 17.06.2015 21:17

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

Tuukka Haapaniemi 04.09.2015 12:38
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;
}

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

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.

Bence Végert 23.11.2017 14:28

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

Matt 22.02.2018 17:53
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;
  }
}

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

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

Mapper.CreateMap<YourType, YourType>();

Творите волшебство:

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

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

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

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

Я сделал для себя какое-то расширение, которое преобразует 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();
    }
}

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

George Birbilis 14.06.2015 16:35

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

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

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

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

Jonathon Cwik 04.02.2015 19:41

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

Developer63 07.11.2015 10:01

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

http://automapper.codeplex.com/

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

Это не работает; изменения значений в клонированном массиве ВСЕ ЕЩЕ изменяют значения в исходном списке.

Bernoulli Lizard 02.02.2017 18:34

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

zainul 13.02.2017 10:01

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

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

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

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

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

Seidleroni 19.11.2015 18:30

@Seidleroni, вы ошибаетесь. Изменения, внесенные в список itens, влияют на другой список, а изменения в самом списке - нет.

Wellington Zanelli 19.04.2016 20:53

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

Elliot Chen 05.08.2016 20:58

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

mko 27.09.2018 14:41

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

Nick Gallimore 17.12.2019 01:13

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

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

// 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;
}

Другое дело: вы можете использовать отражение. Если вы правильно кэшируете это, то он клонирует 1000000 объектов за 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.

P.S .: для простоты чтения я использовал здесь только auto-property. Я мог бы обновить с помощью FieldInfo, или вы должны легко реализовать это самостоятельно.

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

Serializer.DeepClone(personList);

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

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

public static List<T> Clone<T>(this List<T> oldList)
{
    var newList = new List<T>(oldList.Capacity);
    newList.AddRange(oldList);
    return newList;
}
    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;
        }
    }

Нет необходимости отмечать классы как 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);
}

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

himanshupareek66 22.11.2017 18:08

Грегор Мартинович?

ΩmegaMan 30.12.2020 18:45

Существует простой способ клонировать объекты в 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;

Чтобы клонировать список, просто вызовите .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 }
> 

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

Mark G 02.01.2018 16:08
 //try this
 List<string> ListCopy= new List<string>(OldList);
 //or try
 List<T> ListCopy=OldList.ToList();

Мне повезет, если кто-нибудь когда-нибудь это прочитает ... но, чтобы не возвращать список типов объектов в моих методах 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();
}

Кому-нибудь это нравится? Есть улучшения?

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

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

Для глубокой копии 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 делает его кратким и понятным. Точно так же вы могли бы сделать, как предлагали другие ответы, и создать методы расширения и т.д., но ничего из этого не требуется.

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

kenno 01.12.2019 16:36

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

ztorstri 04.12.2019 21:06

Для глубокого клона я использую отражение следующим образом:

public List<T> CloneList<T>(IEnumerable<T> listToClone) {
    Type listType = listToClone.GetType();
    Type elementType = listType.GetGenericArguments()[0];
    List<T> listCopy = new List<T>();
    foreach (T item in listToClone) {
        object itemCopy = Activator.CreateInstance(elementType);
        foreach (PropertyInfo property in elementType.GetProperties()) {
            elementType.GetProperty(property.Name).SetValue(itemCopy, property.GetValue(item));
        }
        listCopy.Add((T)itemCopy);
    }
    return listCopy;
}

Вы можете использовать List или IEnumerable как взаимозаменяемые.

Другие вопросы по теме