Объекты глубокого клонирования

Я хочу сделать что-то вроде:

MyObject myObj = GetMyObj(); // Create and fill a new object
MyObject newObj = myObj.Clone();

А затем внесите в новый объект изменения, которые не отражаются в исходном объекте.

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

Как я могу клонировать или глубоко скопировать объект, чтобы клонированный объект можно было изменить без отражения каких-либо изменений в исходном объекте?

Может быть полезно: «Почему копирование объекта - ужасное занятие?» agiledeveloper.com/articles/cloning072002.htm

Pedro77 07.12.2011 15:56
stackoverflow.com/questions/8025890/… Another solution...
Felix K. 16.03.2012 20:39

Вам стоит взглянуть на AutoMapper

Daniel Little 19.12.2012 04:36

Ваше решение намного сложнее, я потерялся, читая его ... хе-хе-хе. Я использую интерфейс DeepClone. открытый интерфейс IDeepCloneable <T> {T DeepClone (); }

Pedro77 09.08.2013 18:12

@ Pedro77: Меня беспокоит IDeepCloneable, что не все коллекции ссылок на вещи, которые можно глубоко клонировать, должны быть; правильное поведение при клонировании List<T> зависит не только от T, но и от назначения списков. Если ни один из элементов в списках никогда не подвергнется воздействию чего-либо, что могло бы их изменить, то даже если элементы в списках можно клонировать, было бы лучше скопировать ссылки напрямую.

supercat 28.04.2014 21:26

На этот вопрос также дан ответ здесь stackoverflow.com/q/129389/235715

Alex Burtsev 29.05.2014 12:30

@ Pedro77 - Хотя, что интересно, в этой статье говорится, что нужно создать метод clone в классе, а затем вызвать внутренний частный конструктор, которому передается this. Так что копирование ужасно [sic], а вот копировать осторожно (а статью определенно стоит прочитать) - нет. ; ^)

ruffin 05.09.2014 20:54

Если вам это нужно, возможно, у вас неправильная реализация. А если вы используете инъекцию зависимостей, это вообще не имеет смысла.

Mertuarez 29.04.2016 14:57

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

C Perkins 30.04.2017 10:27

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

Szabolcs Páll 21.08.2017 11:48

Вызовите метод MemberwiseClone, чтобы создать неглубокую копию объекта, а затем назначьте новые объекты, значения которых совпадают со значениями исходного объекта, любым свойствам или полям, значения которых являются ссылочными типами. Метод DeepCopy в примере иллюстрирует этот подход. msdn.microsoft.com/en-us/library/…

himanshupareek66 22.11.2017 18:27

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

Ahmed Sabry 30.08.2018 15:41
Стоит ли изучать 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 называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
2 355
12
860 106
49
Перейти к ответу Данный вопрос помечен как решенный

Ответы 49

Как правило, вы реализуете интерфейс ICloneable и выполняете Clone самостоятельно. Объекты C# имеют встроенный метод MemberwiseClone, который выполняет неглубокую копию, которая может помочь вам для всех примитивов.

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

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

Karg 17.09.2008 04:12
  1. В основном вам нужно реализовать интерфейс ICloneable, а затем реализовать копирование структуры объекта.
  2. Если это глубокая копия всех членов, вам необходимо обеспечить (не относящееся к выбранному вами решению), что все дочерние элементы также являются клонируемыми.
  3. Иногда вам нужно знать о некоторых ограничениях во время этого процесса, например, если вы копируете объекты ORM, большинство фреймворков разрешают только один объект, прикрепленный к сеансу, и вы НЕ ДОЛЖНЫ делать клоны этого объекта, или, если это возможно, вам нужно заботиться о сессионном прикреплении этих объектов.

Ваше здоровье.

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

Karg 17.09.2008 04:13

Наилучшие ответы - простые и краткие.

DavidGuaita 20.04.2018 03:45

Я предпочитаю конструктор копирования клону. Намерение яснее.

.Net не имеет конструкторов копирования.

Pop Catalin 17.09.2008 04:45

Конечно, это так: новый MyObject (objToCloneFrom) Просто объявите ctor, который принимает объект для клонирования в качестве параметра.

Nick 17.09.2008 15:49

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

Dave Van den Eynde 04.06.2009 12:01

+1 за копию ctor. Вам также нужно вручную написать функцию clone () для каждого типа объекта, и удачи вам с этим, когда ваша иерархия классов станет глубже на несколько уровней.

Andrew Grant 15.09.2009 04:50

Однако с конструкторами копирования вы теряете иерархию. agiledeveloper.com/articles/cloning072002.htm

Will 07.11.2011 01:10

Мне нравится конструктор копирования; но предположим, что у вас есть AbstractBaseType с 3 производными типами, каждый из которых вложен, так что Type1:AbstractBaseType имеет член Type2:AbstractBaseType, который имеет член Type3:AbstractBaseType. Теперь вам нужно проверить типы.

IAbstract 01.02.2012 02:19

@IAbstract: Зачем нужно проверять типы? Просто положитесь на наследование: pastebin.com/XWGQFBhr ...

C.B. 31.10.2013 21:33

Потомкам может потребоваться другая логика, а также некоторая логика if-then или switch. В противном случае вам необходимо предоставить соответствующий базовый конструктор для глубокого клонирования. Конструкторы клонов просто беспорядочные.

IAbstract 31.10.2013 21:52

@IAbstract: <s> Зачем нужно проверять типы? Просто положитесь на наследование: pastebin.com/XWGQFBhr ... </s> Неважно, после прочтения ссылки Уилла я понимаю проблему, которую вы пытались выразить: члены типа AbstractBaseType не могут быть клонированы с помощью некоторого конструктора копирования в ABT, их копии нужно будет создать с помощью конструктора копирования их конкретного типа ...

C.B. 31.10.2013 21:54

Проблема с рукописными методами Copy Constructor и Clone / Copy заключается в обслуживании. Обслуживание, подверженное ошибкам. Представьте DataContract с более чем 50 полями. Представьте, что вы добавляете новое поле или поля. Он может очень быстро превратиться в херню кластера. Любые изменения, которые вы вносите в класс, вы также должны не забывать вносить в конструктор копирования. Вот почему лучше оставить копирование чем-то вроде сериализации или отражения. Было бы неплохо, если бы C# добавил функциональность для самой Deep Copy, но я считаю, что они ее оставили, потому что определение того, что такое Deep Copy, не вырезано и не высушено.

crush 28.05.2014 23:08

Использование конструктора означает, что копирование / клонирование не может быть частью интерфейса.

wal5hy 28.08.2015 17:43

@DaveVandenEynde в C++ тоже нужно добавить вручную.

Konrad 05.09.2018 14:32

Короткий ответ: вы наследуете интерфейс ICloneable, а затем реализуете функцию .clone. Клонирование должно выполнять поэлементное копирование и выполнять глубокое копирование для любого члена, который требует этого, а затем возвращать полученный объект. Это рекурсивная операция (она требует, чтобы все члены класса, который вы хотите клонировать, были либо типами значений, либо реализовали ICloneable, и чтобы их члены были либо типами значений, либо реализовали ICloneable и т. д.).

Более подробное объяснение клонирования с использованием ICloneable можно найти в Эта статья.

Ответ длинный - «это зависит». Как упоминалось другими, ICloneable не поддерживается универсальными шаблонами, требует особого внимания к циклическим ссылкам на классы и фактически рассматривается некоторыми как "ошибка" в .NET Framework. Метод сериализации зависит от сериализуемости ваших объектов, а это может не быть, и вы можете не контролировать их. В сообществе до сих пор ведутся споры о том, какая практика является «лучшей». На самом деле, ни одно из решений не является универсальным для всех передовых практик для всех ситуаций, как изначально интерпретировалось ICloneable.

См. Этот Статья в Уголке разработчика для еще нескольких опций (кредит Яна).

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

Karg 17.09.2008 04:15

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

Pop Catalin 17.09.2008 04:46

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

Zach Burlingame 17.09.2008 04:56
Ответ принят как подходящий

Принимая во внимание, что один из подходов - реализовать интерфейс ICloneable (описанный здесь, поэтому я не буду отрыгаться), вот хороший копировщик объектов глубокого клонирования, который я нашел на Кодовый проект некоторое время назад и включил его в наш код. Как упоминалось в другом месте, для этого требуется, чтобы ваши объекты были сериализуемыми.

using System;
using System.IO;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;

/// <summary>
/// Reference Article http://www.codeproject.com/KB/tips/SerializedObjectCloner.aspx
/// Provides a method for performing a deep copy of an object.
/// Binary Serialization is used to perform the copy.
/// </summary>
public static class ObjectCopier
{
    /// <summary>
    /// Perform a deep copy of the object via serialization.
    /// </summary>
    /// <typeparam name = "T">The type of object being copied.</typeparam>
    /// <param name = "source">The object instance to copy.</param>
    /// <returns>A deep copy of the object.</returns>
    public static T Clone<T>(T source)
    {
        if (!typeof(T).IsSerializable)
        {
            throw new ArgumentException("The type must be serializable.", nameof(source));
        }

        // Don't serialize a null object, simply return the default for that object
        if (ReferenceEquals(self, null)) return default;

        using var Stream stream = new MemoryStream();
        IFormatter formatter = new BinaryFormatter();
        formatter.Serialize(stream, source);
        stream.Seek(0, SeekOrigin.Begin);
        return (T)formatter.Deserialize(stream);
    }
}

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

Если вы предпочитаете использовать новый методы расширения C# 3.0, измените метод так, чтобы он имел следующую сигнатуру:

public static T Clone<T>(this T source)
{
   // ...
}

Теперь вызов метода просто становится objectBeingCloned.Clone();.

РЕДАКТИРОВАТЬ (10 января 2015 г.) Подумал, что еще раз вернусь к этому, чтобы упомянуть, что недавно я начал использовать (Newtonsoft) Json для этого, он более легкий должно быть и позволяет избежать накладных расходов на теги [Serializable]. (NB @atconway указал в комментариях, что частные члены не клонируются с использованием метода JSON)

/// <summary>
/// Perform a deep Copy of the object, using Json as a serialization method. NOTE: Private members are not cloned using this method.
/// </summary>
/// <typeparam name = "T">The type of object being copied.</typeparam>
/// <param name = "source">The object instance to copy.</param>
/// <returns>The copied object.</returns>
public static T CloneJson<T>(this T source)
{            
    // Don't serialize a null object, simply return the default for that object
    if (ReferenceEquals(self, null)) return default;

    // initialize inner objects individually
    // for example in default constructor some list property initialized with some values,
    // but in 'source' these items are cleaned -
    // without ObjectCreationHandling.Replace default constructor values will be added to result
    var deserializeSettings = new JsonSerializerSettings {ObjectCreationHandling = ObjectCreationHandling.Replace};

    return JsonConvert.DeserializeObject<T>(JsonConvert.SerializeObject(source), deserializeSettings);
}
stackoverflow.com/questions/78536/cloning-objects-in-c/… has a link to the code above [and references two other such implementations, one of which is more appropriate in my context]
Ruben Bartelink 04.02.2009 16:13

Сериализация / десериализация связана со значительными накладными расходами, в которых нет необходимости. См. Интерфейс ICloneable и методы клонирования .MemberWise () в C#.

3Dave 28.01.2010 20:28

@David, разрешено, но если объекты легкие, а производительность при их использовании не слишком высока для ваших требований, то это полезный совет. Признаюсь, я не использовал его интенсивно с большими объемами данных в цикле, но я никогда не видел ни одной проблемы с производительностью.

johnc 29.01.2010 03:21

@johnc Мне нравится ваш ответ, потому что он будет работать почти каждый раз, но, как специалист по встроенным системам и помешанный на графике ассемблера x86 середины 90-х, я всегда думаю об оптимизации. Просто помните, что десериализация включает в себя дорогостоящую обработку и отражение строк и что некоторые типы свойств, такие как словари, не могут быть сериализованы / десериализованы. Тем не менее, очень хороший ответ, за который я проголосовал. («Просто помни» - это арахисовая галерея).

3Dave 29.01.2010 07:32

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

Peder Rice 13.12.2010 00:57

Вместо «if (! Typeof (T) .IsSerializable)» вы можете написать «public static T Clone <T> (T source), где T: ISerializable

Amir Rezaei 11.02.2011 16:36

@Amir: на самом деле, нет: typeof(T).IsSerializable также верно, если тип был помечен атрибутом [Serializable]. Необязательно реализовывать интерфейс ISerializable.

Daniel Gehriger 03.06.2011 19:25

Я рекомендую эту сигнатуру метода: public static T Copy <T> (этот элемент T), где T: ISerializable

Jerry Nixon 23.06.2011 19:46

Как уже упоминалось, здесь, вам нужно будет пометить несериализуемые частные поля / события как [NonSerialized] (или [field: NonSerialized]), чтобы это работало.

epalm 13.07.2011 19:19

@epalm, конечно, требуется сносное знание сериализации, но это довольно легкая кривая обучения

johnc 14.07.2011 02:11

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

Alex Norcliffe 17.10.2011 15:35
stackoverflow.com/questions/8025890/… My solution.
Felix K. 07.03.2012 16:16

@David Lively, хотя я знаю, что это не быстрее: если вы сериализуете в JSON вместо этого, вы действительно можете сериализовать словарь, я делаю это все время.

Kelly 12.06.2012 00:23

Джонк, к чести @RubenBartelink, правильным номером будет 8 (I предполагать), а не 78611 (хотя мне понравилась умная ссылка на собственный пост). Но очевидно, что вы писали этот очень элегантный ответ, в то время как другие люди давали одни лайнеры и ссылки и, даже 3 ссылки на такую ​​тему - это уже довольно большое количество, чтобы прочитать.

cregox 26.09.2012 16:06

@Cawas Ах, теперь я наконец-то это вижу! Я понятия не имею, были ли голоса так искажены в тот день, но я по-прежнему предпочитаю ответ Яна П, поскольку он указывает на источник (изначально я не предполагал), и я готов потратить 2 минуты выбрать стратегию, когда в цитируемой статье содержится очень глубокий анализ различных способов ее реализации.

Ruben Bartelink 26.09.2012 23:17

@RubenBartelink, а как насчет мой новый ответ? :П

cregox 27.09.2012 00:18

@PederRice По этой причине также может быть невозможно пометить класс как Serializable. В этом случае вам понадобится решение, использующее отражение.

crush 28.05.2014 23:03

BinaryFormatter небезопасен, посмотрите официальные документы: docs.microsoft.com/en-us/dotnet/api/…

Arnold Vakaria 26.01.2021 22:07

Частные члены не клонируются с использованием метода JSON, этого можно избежать с помощью пакета nuget PrivateSetterContractResolver

Andrea Falappi - Polipo 18.02.2021 14:18

Причина не использовать ICloneable - нет, потому что у него нет универсального интерфейса. Причина не использовать его в том, что он расплывчатый. Неясно, получаете ли вы поверхностную или глубокую копию; это зависит от разработчика.

Да, MemberwiseClone является поверхностной копией, но противоположность MemberwiseClone - это не Clone; это был бы, возможно, DeepClone, которого не существует. Когда вы используете объект через его интерфейс ICloneable, вы не можете знать, какой вид клонирования выполняет базовый объект. (И комментарии XML не проясняют это, потому что вы получите комментарии к интерфейсу, а не к методу Clone объекта.)

Обычно я просто создаю метод Copy, который делает именно то, что я хочу.

Я не понимаю, почему ICloneable считается расплывчатым. Учитывая такой тип, как Dictionary (Of T, U), я бы ожидал, что ICloneable.Clone должен выполнять любой уровень глубокого и неглубокого копирования, необходимый для того, чтобы новый словарь был независимым словарем, содержащим те же T и U (содержимое структуры, и / или ссылки на объекты) в качестве оригинала. Где двусмысленность? Конечно, общий ICloneable (Of T), который унаследовал ISelf (Of T), который включал метод «Self», был бы намного лучше, но я не вижу двусмысленности в отношении глубокого и поверхностного клонирования.

supercat 12.01.2011 21:35

Ваш пример иллюстрирует проблему. Предположим, у вас есть Dictionary <string, Customer>. Должен ли клонированный Словарь содержать объекты Заказчика одно и тоже в качестве исходных или копии этих объектов Заказчика? Для любого из них есть разумные варианты использования. Но ICloneable не дает понять, какой из них вы получите. Вот почему это бесполезно.

Ryan Lundy 12.01.2011 21:53

@Kyralessa В статье Microsoft MSDN на самом деле говорится об этой самой проблеме незнания, запрашиваете ли вы глубокую или неглубокую копию.

crush 28.05.2014 23:05

Ответ от дубликата stackoverflow.com/questions/129389/… описывает расширение Copy, основанное на рекурсивном MembershipClone

Michael Freidgeim 23.01.2018 15:15

Я придумал это, чтобы преодолеть недостаток .СЕТЬ, когда нужно вручную глубоко копировать List <T>.

Я использую это:

static public IEnumerable<SpotPlacement> CloneList(List<SpotPlacement> spotPlacements)
{
    foreach (SpotPlacement sp in spotPlacements)
    {
        yield return (SpotPlacement)sp.Clone();
    }
}

И в другом месте:

public object Clone()
{
    OrderItem newOrderItem = new OrderItem();
    ...
    newOrderItem._exactPlacements.AddRange(SpotPlacement.CloneList(_exactPlacements));
    ...
    return newOrderItem;
}

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

Еще лучше использовать общий клонер List <T>:

class Utility<T> where T : ICloneable
{
    static public IEnumerable<T> CloneList(List<T> tl)
    {
        foreach (T t in tl)
        {
            yield return (T)t.Clone();
        }
    }
}

У меня были проблемы с использованием ICloneable в Silverlight, но мне понравилась идея серализации, я могу серализовать XML, поэтому я сделал следующее:

static public class SerializeHelper
{
    //Michael White, Holly Springs Consulting, 2009
    //[email protected]
    public static T DeserializeXML<T>(string xmlData) where T:new()
    {
        if (string.IsNullOrEmpty(xmlData))
            return default(T);

        TextReader tr = new StringReader(xmlData);
        T DocItms = new T();
        XmlSerializer xms = new XmlSerializer(DocItms.GetType());
        DocItms = (T)xms.Deserialize(tr);

        return DocItms == null ? default(T) : DocItms;
    }

    public static string SeralizeObjectToXML<T>(T xmlObject)
    {
        StringBuilder sbTR = new StringBuilder();
        XmlSerializer xmsTR = new XmlSerializer(xmlObject.GetType());
        XmlWriterSettings xwsTR = new XmlWriterSettings();

        XmlWriter xmwTR = XmlWriter.Create(sbTR, xwsTR);
        xmsTR.Serialize(xmwTR,xmlObject);

        return sbTR.ToString();
    }

    public static T CloneObject<T>(T objClone) where T:new()
    {
        string GetString = SerializeHelper.SeralizeObjectToXML<T>(objClone);
        return SerializeHelper.DeserializeXML<T>(GetString);
    }
}

Я тоже видел, как это реализовано через отражение. По сути, существовал метод, который перебирал элементы объекта и соответствующим образом копировал их в новый объект. Когда он достиг ссылочных типов или коллекций, я думаю, он сам себя рекурсивно вызвал. Отражение дорогое, но сработало неплохо.

Простой метод расширения для копирования всех общедоступных свойств. Работает для любых объектов и не требует, чтобы класс был [Serializable]. Может быть расширен на другой уровень доступа.

public static void CopyTo( this object S, object T )
{
    foreach( var pS in S.GetType().GetProperties() )
    {
        foreach( var pT in T.GetType().GetProperties() )
        {
            if ( pT.Name != pS.Name ) continue;
            ( pT.GetSetMethod() ).Invoke( T, new object[] 
            { pS.GetGetMethod().Invoke( S, null ) } );
        }
    };
}

Это, к сожалению, ошибочно. Это эквивалентно вызову objectOne.MyProperty = objectTwo.MyProperty (т.е. он просто скопирует ссылку). Он не будет клонировать значения свойств.

Alex Norcliffe 18.10.2011 04:59

to Alex Norcliffe: автор вопроса, заданного о «копировании каждого свойства», а не о клонировании. в большинстве случаев точное дублирование свойств не требуется.

Konstantin Salavatov 28.03.2012 13:41

Я думаю об использовании этого метода, но с рекурсией. поэтому, если значение свойства является ссылкой, создайте новый объект и снова вызовите CopyTo. Я просто вижу одну проблему: все используемые классы должны иметь конструктор без параметров. Кто-нибудь уже пробовал это? Мне также интересно, действительно ли это будет работать со свойствами, содержащими классы .net, такие как DataRow и DataTable?

Koryu 25.07.2013 13:22

Вот реализация глубокой копии:

public static object CloneObject(object opSource)
{
    //grab the type and create a new instance of that type
    Type opSourceType = opSource.GetType();
    object opTarget = CreateInstanceOfType(opSourceType);

    //grab the properties
    PropertyInfo[] opPropertyInfo = opSourceType.GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);

    //iterate over the properties and if it has a 'set' method assign it from the source TO the target
    foreach (PropertyInfo item in opPropertyInfo)
    {
        if (item.CanWrite)
        {
            //value types can simply be 'set'
            if (item.PropertyType.IsValueType || item.PropertyType.IsEnum || item.PropertyType.Equals(typeof(System.String)))
            {
                item.SetValue(opTarget, item.GetValue(opSource, null), null);
            }
            //object/complex types need to recursively call this method until the end of the tree is reached
            else
            {
                object opPropertyValue = item.GetValue(opSource, null);
                if (opPropertyValue == null)
                {
                    item.SetValue(opTarget, null, null);
                }
                else
                {
                    item.SetValue(opTarget, CloneObject(opPropertyValue), null);
                }
            }
        }
    }
    //return the new item
    return opTarget;
}

Это похоже на поэлементное клонирование, потому что не знает свойств ссылочного типа

sll 06.11.2011 14:17

Если вам нужна невероятно высокая производительность, не выбирайте эту реализацию: она использует отражение, поэтому она будет не такой быстрой. И наоборот, «преждевременная оптимизация - это все зло», поэтому игнорируйте сторону производительности, пока не запустите профилировщик.

Contango 30.12.2011 21:30

CreateInstanceOfType не определяется?

MonsterMMORPG 14.10.2015 22:40

Он не работает с целым числом: «Нестатический метод требует цели».

Mr.B 17.05.2016 12:08

Следуй этим шагам:

  • Определите ISelf<T> со свойством Self, доступным только для чтения, которое возвращает T, и ICloneable<out T>, которое является производным от ISelf<T> и включает метод T Clone().
  • Затем определите тип CloneBase, который реализует приведение protected virtual generic VirtualClone к MemberwiseClone к переданному типу.
  • Каждый производный тип должен реализовывать VirtualClone, вызывая базовый метод клонирования, а затем делая все необходимое для правильного клонирования тех аспектов производного типа, которые родительский метод VirtualClone еще не обработал.

Для максимальной универсальности наследования классы, предоставляющие общедоступные функции клонирования, должны быть sealed, но производными от базового класса, который в остальном идентичен, за исключением отсутствия клонирования. Вместо того, чтобы передавать переменные явного клонируемого типа, возьмите параметр типа ICloneable<theNonCloneableType>. Это позволит подпрограмме, которая ожидает, что клонируемая производная Foo будет работать с клонируемой производной DerivedFoo, но также позволит создавать неклонируемые производные Foo.

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

Поэтому я просто скопирую здесь соответствующие части из этих двух ссылок. Таким образом мы можем получить:

Лучшее, что можно сделать для клонирования объектов в C-Sharp!

В первую очередь, это все наши варианты:

В article Fast Deep Copy по деревьям выражений также есть сравнение производительности клонирования с помощью деревьев сериализации, отражения и выражений.

Почему я выбираю ICloneable (т.е. вручную)

Г-н Венкат Субраманиам (дублирующая ссылка здесь) подробно объясняет, почему.

Вся его статья вращается вокруг примера, который пытается быть применимым для большинства случаев, используя 3 объекта: Человек, Мозг и Город. Мы хотим клонировать человека, у которого будет свой мозг, но тот же город. Вы можете представить себе все проблемы, которые может решить любой из вышеперечисленных методов, или прочитать статью.

Это моя слегка измененная версия его вывода:

Copying an object by specifying New followed by the class name often leads to code that is not extensible. Using clone, the application of prototype pattern, is a better way to achieve this. However, using clone as it is provided in C# (and Java) can be quite problematic as well. It is better to provide a protected (non-public) copy constructor and invoke that from the clone method. This gives us the ability to delegate the task of creating an object to an instance of a class itself, thus providing extensibility and also, safely creating the objects using the protected copy constructor.

Надеюсь, эта реализация прояснит ситуацию:

public class Person : ICloneable
{
    private final Brain brain; // brain is final since I do not want 
                // any transplant on it once created!
    private int age;
    public Person(Brain aBrain, int theAge)
    {
        brain = aBrain; 
        age = theAge;
    }
    protected Person(Person another)
    {
        Brain refBrain = null;
        try
        {
            refBrain = (Brain) another.brain.clone();
            // You can set the brain in the constructor
        }
        catch(CloneNotSupportedException e) {}
        brain = refBrain;
        age = another.age;
    }
    public String toString()
    {
        return "This is person with " + brain;
        // Not meant to sound rude as it reads!
    }
    public Object clone()
    {
        return new Person(this);
    }
    …
}

Теперь рассмотрим, есть ли у нас класс, унаследованный от Person.

public class SkilledPerson extends Person
{
    private String theSkills;
    public SkilledPerson(Brain aBrain, int theAge, String skills)
    {
        super(aBrain, theAge);
        theSkills = skills;
    }
    protected SkilledPerson(SkilledPerson another)
    {
        super(another);
        theSkills = another.theSkills;
    }

    public Object clone()
    {
        return new SkilledPerson(this);
    }
    public String toString()
    {
        return "SkilledPerson: " + super.toString();
    }
}

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

public class User
{
    public static void play(Person p)
    {
        Person another = (Person) p.clone();
        System.out.println(p);
        System.out.println(another);
    }
    public static void main(String[] args)
    {
        Person sam = new Person(new Brain(), 1);
        play(sam);
        SkilledPerson bob = new SkilledPerson(new SmarterBrain(), 1, "Writer");
        play(bob);
    }
}

Полученный результат будет:

This is person with Brain@1fcc69
This is person with Brain@253498
SkilledPerson: This is person with SmarterBrain@1fef6f
SkilledPerson: This is person with SmarterBrain@209f4e

Обратите внимание: если мы будем вести счет количества объектов, реализованный здесь клон будет вести правильный счет количества объектов.

MS рекомендует не использовать ICloneable для публичных участников. «Поскольку вызывающие объекты Clone не могут зависеть от метода, выполняющего предсказуемую операцию клонирования, мы не рекомендуем реализовывать ICloneable в общедоступных API». msdn.microsoft.com/en-us/library/… Однако, основываясь на объяснении, данном Венкатом Субраманиамом в вашей связанной статье, я думаю, что в этой ситуации имеет смысл использовать до тех пор, пока создатели объектов ICloneable имеют глубокое понимание того, какие свойства должны быть глубокими, а какие - неглубокими копиями. (т.е. глубокая копия Brain, мелкая копия City)

BateTech 09.01.2015 19:57

Во-первых, я далек от эксперта в этой теме (публичные API). Я считать на этот раз, что замечание MS имеет большой смысл. И я не думаю, что можно безопасно предполагать, что пользователи этого API будет иметь такое глубокое понимание. Таким образом, имеет смысл реализовать его на общедоступный API только в том случае, если это действительно не имеет значения для тех, кто собирается его использовать. Я Угадай, имеющий какой-то UML, очень явно делающий различие по каждому свойству, мог бы помочь. Но я бы хотел получить известие от кого-то более опытного. :П

cregox 10.01.2015 06:45

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

Toxantron 09.06.2016 23:53

Реализация промежуточного языка полезна

Michael Freidgeim 23.01.2018 15:35

В C# нет final

Konrad 05.09.2018 14:31

Спасибо за сравнение производительности разных подходов из статьи codeproject.com/Articles/1111658/…

Artemious 15.12.2018 01:15

@BateTech Это противоречит принципу, согласно которому люди не должны «просто запоминать» вещи. Вместо использования ICloneable имеет смысл определить свой собственный интерфейс и методы, чтобы делать именно то, что вы хотите. Затем вы можете четко указать в комментариях XML, какое именно клонирование / копирование выполняется. Между прочим, за 15 лет использования .NET в самых разных сферах и сферах деятельности мне почти никогда не приходилось ничего клонировать / копировать.

Ryan Lundy 06.11.2019 11:54

@RyanLundy Я согласен до некоторой степени, но я бы сказал, что огромный процент успешного программирования - это «просто запоминание / знание» вещей (включая не забывать добавлять XML-комментарии, какие интерфейсы использовать, анит-шаблоны и т. д.). Независимо от того, используете ли вы iCloneable или iMyCustomDeepClone, вам все равно придется иметь дело с тем, какие свойства должны быть глубокими или мелкими копиями в реализации этого интерфейса, bc во многих случаях он не будет полностью глубоким или полностью неглубоким в свойстве уровень. Цель моего комментария заключалась в том, что тот, кто реализует интерфейс, нуждается в этом понимании.

BateTech 06.11.2019 17:08

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

Cubelaster 22.11.2020 10:03

Если вы уже используете стороннее приложение, такое как ValueInjecter или Automapper, вы можете сделать что-то вроде этого:

MyObject oldObj; // The existing object to clone

MyObject newObj = new MyObject();
newObj.InjectFrom(oldObj); // Using ValueInjecter syntax

Используя этот метод, вам не нужно внедрять ISerializable или ICloneable в свои объекты. Это обычное дело для шаблона MVC / MVVM, поэтому были созданы такие простые инструменты.

см. пример глубокого клонирования ValueInjecter на GitHub.

Я хотел клонировать очень простые объекты, в основном примитивы и списки. Если ваш объект не поддерживает сериализацию JSON, то этот метод поможет. Это не требует модификации или реализации интерфейсов в клонированном классе, только сериализатор JSON, такой как JSON.NET.

public static T Clone<T>(T source)
{
    var serialized = JsonConvert.SerializeObject(source);
    return JsonConvert.DeserializeObject<T>(serialized);
}

Также вы можете использовать этот метод расширения

public static class SystemExtension
{
    public static T Clone<T>(this T source)
    {
        var serialized = JsonConvert.SerializeObject(source);
        return JsonConvert.DeserializeObject<T>(serialized);
    }
}

решение даже быстрее, чем решение BinaryFormatter, Сравнение производительности сериализации .NET

esskar 12.03.2014 14:25

Спасибо за это. По сути, я смог сделать то же самое с сериализатором BSON, который поставляется с драйвером MongoDB для C#.

Mark Ewer 18.06.2014 04:58

Для меня это лучший способ, но я использую Newtonsoft.Json.JsonConvert, но он такой же

Pierre 04.02.2015 15:20

Подход JSON подходит для небольших плоских объектов, но исходный вопрос касался любого объекта, включая глубокие. Сериализатор NFX.Slim работает на порядки быстрее с любым типом .NET, если у него нет делегатов и неуправляемых указателей, вот источник, который работает очень похоже на BinaryFormnatter, только как минимум в 5 раз быстрее: github.com/aumcode/nfx/blob/master/Source/NFX/Serialization/‌… Набор тестов: github.com/aumcode/serbench доказывает, что единственный серийный номер, который работает быстрее, - это Protobuf, которому не хватает динамизма типов и ссылок

itadapter DKh 13.07.2015 04:54

Это не работает, если ваш объект является интерфейсом.

Jaylen 17.07.2016 00:55

Могу я добавить, что если вы хотите сериализовать пользовательские объекты, вам нужно будет украсить все свойства [DataMember], а класс - [DataContract]

Anya Hope 02.12.2016 12:37

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

radomeit 22.02.2018 13:03

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

mr5 02.01.2019 10:58

В DeserializeObject () вы должны добавить дополнительные параметры new JsonSerializerSettings {ObjectCreationHandling = ObjectCreationHandling.Replace}, потому что конструктор по умолчанию может изменить что-то, чего нет в исходном ?

marbel82 30.07.2019 12:31

Это скопирует все доступные для чтения и записи свойства объекта в другой.

 public class PropertyCopy<TSource, TTarget> 
                        where TSource: class, new()
                        where TTarget: class, new()
        {
            public static TTarget Copy(TSource src, TTarget trg, params string[] properties)
            {
                if (src==null) return trg;
                if (trg == null) trg = new TTarget();
                var fulllist = src.GetType().GetProperties().Where(c => c.CanWrite && c.CanRead).ToList();
                if (properties != null && properties.Count() > 0)
                    fulllist = fulllist.Where(c => properties.Contains(c.Name)).ToList();
                if (fulllist == null || fulllist.Count() == 0) return trg;

                fulllist.ForEach(c =>
                    {
                        c.SetValue(trg, c.GetValue(src));
                    });

                return trg;
            }
        }

и вот как вы его используете:

 var cloned = Utils.PropertyCopy<TKTicket, TKTicket>.Copy(_tmp, dbsave,
                                                            "Creation",
                                                            "Description",
                                                            "IdTicketStatus",
                                                            "IdUserCreated",
                                                            "IdUserInCharge",
                                                            "IdUserRequested",
                                                            "IsUniqueTicketGenerated",
                                                            "LastEdit",
                                                            "Subject",
                                                            "UniqeTicketRequestId",
                                                            "Visibility");

или скопировать все:

var cloned = Utils.PropertyCopy<TKTicket, TKTicket>.Copy(_tmp, dbsave);

Я только что создал проект Библиотека CloneExtensions. Он выполняет быстрое и глубокое клонирование с использованием простых операций присваивания, генерируемых компиляцией кода среды выполнения Expression Tree.

Как это использовать?

Вместо того, чтобы писать свои собственные методы Clone или Copy с тоном назначений между полями и свойствами, заставьте программу делать это самостоятельно, используя дерево выражений. Метод GetClone<T>(), помеченный как метод расширения, позволяет вам просто вызвать его в своем экземпляре:

var newInstance = source.GetClone();

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

var newInstance 
    = source.GetClone(CloningFlags.Properties | CloningFlags.CollectionItems);

Что можно клонировать?

  • Примитивные (int, uint, byte, double, char и т. д.), Известные неизменяемые типы (DateTime, TimeSpan, String) и делегаты (включая Action, Func и т. д.)
  • Обнуляемый
  • T [] массивы
  • Пользовательские классы и структуры, включая универсальные классы и структуры.

Следующие члены класса / структуры клонируются внутри:

  • Значения общедоступных полей, а не только для чтения
  • Значения общедоступных свойств с методами доступа get и set
  • Элементы коллекции для типов, реализующих ICollection

Насколько это быстро?

Решение быстрее, чем отражение, потому что информация об элементах должна быть собрана только один раз, прежде чем GetClone<T> будет использоваться впервые для данного типа T.

Это также быстрее, чем решение на основе сериализации, когда вы клонируете более пары экземпляров одного и того же типа T.

и больше...

Подробнее о сгенерированных выражениях читайте на документация.

Пример отладочного листинга выражения для List<int>:

.Lambda #Lambda1<System.Func`4[System.Collections.Generic.List`1[System.Int32],CloneExtensions.CloningFlags,System.Collections.Generic.IDictionary`2[System.Type,System.Func`2[System.Object,System.Object]],System.Collections.Generic.List`1[System.Int32]]>(
    System.Collections.Generic.List`1[System.Int32] $source,
    CloneExtensions.CloningFlags $flags,
    System.Collections.Generic.IDictionary`2[System.Type,System.Func`2[System.Object,System.Object]] $initializers) {
    .Block(System.Collections.Generic.List`1[System.Int32] $target) {
        .If ($source == null) {
            .Return #Label1 { null }
        } .Else {
            .Default(System.Void)
        };
        .If (
            .Call $initializers.ContainsKey(.Constant<System.Type>(System.Collections.Generic.List`1[System.Int32]))
        ) {
            $target = (System.Collections.Generic.List`1[System.Int32]).Call ($initializers.Item[.Constant<System.Type>(System.Collections.Generic.List`1[System.Int32])]
            ).Invoke((System.Object)$source)
        } .Else {
            $target = .New System.Collections.Generic.List`1[System.Int32]()
        };
        .If (
            ((System.Byte)$flags & (System.Byte).Constant<CloneExtensions.CloningFlags>(Fields)) == (System.Byte).Constant<CloneExtensions.CloningFlags>(Fields)
        ) {
            .Default(System.Void)
        } .Else {
            .Default(System.Void)
        };
        .If (
            ((System.Byte)$flags & (System.Byte).Constant<CloneExtensions.CloningFlags>(Properties)) == (System.Byte).Constant<CloneExtensions.CloningFlags>(Properties)
        ) {
            .Block() {
                $target.Capacity = .Call CloneExtensions.CloneFactory.GetClone(
                    $source.Capacity,
                    $flags,
                    $initializers)
            }
        } .Else {
            .Default(System.Void)
        };
        .If (
            ((System.Byte)$flags & (System.Byte).Constant<CloneExtensions.CloningFlags>(CollectionItems)) == (System.Byte).Constant<CloneExtensions.CloningFlags>(CollectionItems)
        ) {
            .Block(
                System.Collections.Generic.IEnumerator`1[System.Int32] $var1,
                System.Collections.Generic.ICollection`1[System.Int32] $var2) {
                $var1 = (System.Collections.Generic.IEnumerator`1[System.Int32]).Call $source.GetEnumerator();
                $var2 = (System.Collections.Generic.ICollection`1[System.Int32])$target;
                .Loop  {
                    .If (.Call $var1.MoveNext() != False) {
                        .Call $var2.Add(.Call CloneExtensions.CloneFactory.GetClone(
                                $var1.Current,
                                $flags,


                         $initializers))
                } .Else {
                    .Break #Label2 { }
                }
            }
            .LabelTarget #Label2:
        }
    } .Else {
        .Default(System.Void)
    };
    .Label
        $target
    .LabelTarget #Label1:
}

}

что имеет то же значение, что и следующий код C#:

(source, flags, initializers) =>
{
    if (source == null)
        return null;

    if (initializers.ContainsKey(typeof(List<int>))
        target = (List<int>)initializers[typeof(List<int>)].Invoke((object)source);
    else
        target = new List<int>();

    if ((flags & CloningFlags.Properties) == CloningFlags.Properties)
    {
        target.Capacity = target.Capacity.GetClone(flags, initializers);
    }

    if ((flags & CloningFlags.CollectionItems) == CloningFlags.CollectionItems)
    {
        var targetCollection = (ICollection<int>)target;
        foreach(var item in (ICollection<int>)source)
        {
            targetCollection.Add(item.Clone(flags, initializers));
        }
    }

    return target;
}

Разве это не похоже на то, как написать собственный метод Clone для List<int>?

Каковы шансы, что это попадет в NuGet? Вроде лучшее решение. Как это по сравнению с NClone?

crush 28.05.2014 23:56

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

nightcoder 28.07.2015 17:15

Вовсе нет, вы ошибаетесь насчет отражения, вы должны просто правильно кэшировать это. Отметьте мой ответ ниже stackoverflow.com/a/34368738/4711853

Roma Borodov 19.12.2015 12:32

Я создал версию принятого ответа, которая работает как с «[Serializable]», так и с «[DataContract]». Прошло много времени с тех пор, как я его написал, но, если я правильно помню, [DataContract] нуждался в другом сериализаторе.

Требуется Система, System.IO, System.Runtime.Serialization, System.Runtime.Serialization.Formatters.Binary, System.Xml;

public static class ObjectCopier
{

    /// <summary>
    /// Perform a deep Copy of an object that is marked with '[Serializable]' or '[DataContract]'
    /// </summary>
    /// <typeparam name = "T">The type of object being copied.</typeparam>
    /// <param name = "source">The object instance to copy.</param>
    /// <returns>The copied object.</returns>
    public static T Clone<T>(T source)
    {
        if (typeof(T).IsSerializable == true)
        {
            return CloneUsingSerializable<T>(source);
        }

        if (IsDataContract(typeof(T)) == true)
        {
            return CloneUsingDataContracts<T>(source);
        }

        throw new ArgumentException("The type must be Serializable or use DataContracts.", "source");
    }


    /// <summary>
    /// Perform a deep Copy of an object that is marked with '[Serializable]'
    /// </summary>
    /// <remarks>
    /// Found on http://stackoverflow.com/questions/78536/cloning-objects-in-c-sharp
    /// Uses code found on CodeProject, which allows free use in third party apps
    /// - http://www.codeproject.com/KB/tips/SerializedObjectCloner.aspx
    /// </remarks>
    /// <typeparam name = "T">The type of object being copied.</typeparam>
    /// <param name = "source">The object instance to copy.</param>
    /// <returns>The copied object.</returns>
    public static T CloneUsingSerializable<T>(T source)
    {
        if (!typeof(T).IsSerializable)
        {
            throw new ArgumentException("The type must be serializable.", "source");
        }

        // Don't serialize a null object, simply return the default for that object
        if (Object.ReferenceEquals(source, null))
        {
            return default(T);
        }

        IFormatter formatter = new BinaryFormatter();
        Stream stream = new MemoryStream();
        using (stream)
        {
            formatter.Serialize(stream, source);
            stream.Seek(0, SeekOrigin.Begin);
            return (T)formatter.Deserialize(stream);
        }
    }


    /// <summary>
    /// Perform a deep Copy of an object that is marked with '[DataContract]'
    /// </summary>
    /// <typeparam name = "T">The type of object being copied.</typeparam>
    /// <param name = "source">The object instance to copy.</param>
    /// <returns>The copied object.</returns>
    public static T CloneUsingDataContracts<T>(T source)
    {
        if (IsDataContract(typeof(T)) == false)
        {
            throw new ArgumentException("The type must be a data contract.", "source");
        }

        // ** Don't serialize a null object, simply return the default for that object
        if (Object.ReferenceEquals(source, null))
        {
            return default(T);
        }

        DataContractSerializer dcs = new DataContractSerializer(typeof(T));
        using(Stream stream = new MemoryStream())
        {
            using (XmlDictionaryWriter writer = XmlDictionaryWriter.CreateBinaryWriter(stream))
            {
                dcs.WriteObject(writer, source);
                writer.Flush();
                stream.Seek(0, SeekOrigin.Begin);
                using (XmlDictionaryReader reader = XmlDictionaryReader.CreateBinaryReader(stream, XmlDictionaryReaderQuotas.Max))
                {
                    return (T)dcs.ReadObject(reader);
                }
            }
        }
    }


    /// <summary>
    /// Helper function to check if a class is a [DataContract]
    /// </summary>
    /// <param name = "type">The type of the object to check.</param>
    /// <returns>Boolean flag indicating if the class is a DataContract (true) or not (false) </returns>
    public static bool IsDataContract(Type type)
    {
        object[] attributes = type.GetCustomAttributes(typeof(DataContractAttribute), false);
        return attributes.Length == 1;
    }

} 

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

T t = new T();
T t2 = (T)t;  //eh something like that

        List<myclass> cloneum;
        public void SomeFuncB(ref List<myclass> _mylist)
        {
            cloneum = new List<myclass>();
            cloneum = (List < myclass >) _mylist;
            cloneum.Add(new myclass(3));
            _mylist = new List<myclass>();
        }

мне кажется, работает

Пробовал преобразовать объект со свойствами с простыми типами и ссылочными типами. Сделал только неглубокую копию свойства ссылочного типа.

Simon Tewsi 10.06.2016 00:44

Чтобы клонировать объект класса, вы можете использовать метод Object.MemberwiseClone,

просто добавьте эту функцию в свой класс:

public class yourClass
{
    // ...
    // ...

    public yourClass DeepCopy()
    {
        yourClass othercopy = (yourClass)this.MemberwiseClone();
        return othercopy;
    }
}

затем, чтобы выполнить глубокую независимую копию, просто вызовите метод DeepCopy:

yourClass newLine = oldLine.DeepCopy();

надеюсь это поможет.

Метод MemberwiseClone создает неглубокую копию, а НЕ глубокую копию. msdn.microsoft.com/en-us/library/…

odyth 21.12.2014 04:38

@odyth важный комментарий в качестве фактического кода. Делайте неглубокую копию, здесь хорошая статья о клонировании и примеры для каждого типа geeksforgeeks.org/shallow-copy-and-deep-copy-in-c-sharp.

ahmed hamdy 27.05.2020 17:28

Обновлено: проект прекращен

Если вы хотите истинное клонирование неизвестных типов, вы можете взглянуть на fastclone.

Это клонирование на основе выражений работает примерно в 10 раз быстрее, чем двоичная сериализация, и поддерживает полную целостность графа объектов.

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

Нет необходимости в интерфейсах, атрибутах или каких-либо других модификациях клонируемых объектов.

Это кажется довольно полезным

LuckyLikey 20.04.2015 16:53

Начать работу с одного снимка кода проще, чем над системой в целом, особенно закрытой. Понятно, что ни одна библиотека не может решить все проблемы одним выстрелом. Следует немного расслабиться.

TarmoPikaro 24.04.2015 22:56

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

nightcoder 28.07.2015 18:25

Я попробовал, и у меня ничего не вышло. Вызывает исключение MemberAccess.

Michael Brown 16.11.2018 01:22

Он не работает с более новыми версиями .NET и больше не поддерживается

Michael Sander 19.11.2018 18:55

Мне нравятся такие копиконструкторы:

    public AnyObject(AnyObject anyObject)
    {
        foreach (var property in typeof(AnyObject).GetProperties())
        {
            property.SetValue(this, property.GetValue(anyObject));
        }
        foreach (var field in typeof(AnyObject).GetFields())
        {
            field.SetValue(this, field.GetValue(anyObject));
        }
    }

Если у вас есть еще что скопировать, добавьте их

Если ваше дерево объектов является сериализуемым, вы также можете использовать что-то вроде этого

static public MyClass Clone(MyClass myClass)
{
    MyClass clone;
    XmlSerializer ser = new XmlSerializer(typeof(MyClass), _xmlAttributeOverrides);
    using (var ms = new MemoryStream())
    {
        ser.Serialize(ms, myClass);
        ms.Position = 0;
        clone = (MyClass)ser.Deserialize(ms);
    }
    return clone;
}

имейте в виду, что это решение довольно простое, но оно не так эффективно, как другие решения.

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

Невероятно, сколько усилий вы можете потратить с интерфейсом IClonable, особенно если у вас сложная иерархия классов. Также MemberwiseClone работает как-то странно - он не клонирует даже обычные структуры типа List.

И, конечно же, самая интересная дилемма для сериализации - это сериализация обратных ссылок - например, иерархии классов, в которых у вас есть родительско-дочерние отношения. Сомневаюсь, что двоичный сериализатор сможет вам в этом случае помочь. (Это закончится рекурсивными циклами + переполнением стека).

Мне как-то понравилось предложенное здесь решение: Как сделать глубокую копию объекта в .NET (особенно в C#)?

однако - он не поддерживал Списки, добавлял, что поддержка, также учитывала повторное родительство. Для правила только для родителей, которое я создал, это поле или свойство должно быть названо «parent», тогда оно будет проигнорировано DeepClone. Возможно, вы захотите определить свои собственные правила для обратных ссылок - для древовидной иерархии это может быть «левый / правый» и т. д.

Вот весь фрагмент кода, включая тестовый код:

using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Text;

namespace TestDeepClone
{
    class Program
    {
        static void Main(string[] args)
        {
            A a = new A();
            a.name = "main_A";
            a.b_list.Add(new B(a) { name = "b1" });
            a.b_list.Add(new B(a) { name = "b2" });

            A a2 = (A)a.DeepClone();
            a2.name = "second_A";

            // Perform re-parenting manually after deep copy.
            foreach( var b in a2.b_list )
                b.parent = a2;


            Debug.WriteLine("ok");

        }
    }

    public class A
    {
        public String name = "one";
        public List<String> list = new List<string>();
        public List<String> null_list;
        public List<B> b_list = new List<B>();
        private int private_pleaseCopyMeAsWell = 5;

        public override string ToString()
        {
            return "A(" + name + ")";
        }
    }

    public class B
    {
        public B() { }
        public B(A _parent) { parent = _parent; }
        public A parent;
        public String name = "two";
    }


    public static class ReflectionEx
    {
        public static Type GetUnderlyingType(this MemberInfo member)
        {
            Type type;
            switch (member.MemberType)
            {
                case MemberTypes.Field:
                    type = ((FieldInfo)member).FieldType;
                    break;
                case MemberTypes.Property:
                    type = ((PropertyInfo)member).PropertyType;
                    break;
                case MemberTypes.Event:
                    type = ((EventInfo)member).EventHandlerType;
                    break;
                default:
                    throw new ArgumentException("member must be if type FieldInfo, PropertyInfo or EventInfo", "member");
            }
            return Nullable.GetUnderlyingType(type) ?? type;
        }

        /// <summary>
        /// Gets fields and properties into one array.
        /// Order of properties / fields will be preserved in order of appearance in class / struct. (MetadataToken is used for sorting such cases)
        /// </summary>
        /// <param name = "type">Type from which to get</param>
        /// <returns>array of fields and properties</returns>
        public static MemberInfo[] GetFieldsAndProperties(this Type type)
        {
            List<MemberInfo> fps = new List<MemberInfo>();
            fps.AddRange(type.GetFields());
            fps.AddRange(type.GetProperties());
            fps = fps.OrderBy(x => x.MetadataToken).ToList();
            return fps.ToArray();
        }

        public static object GetValue(this MemberInfo member, object target)
        {
            if (member is PropertyInfo)
            {
                return (member as PropertyInfo).GetValue(target, null);
            }
            else if (member is FieldInfo)
            {
                return (member as FieldInfo).GetValue(target);
            }
            else
            {
                throw new Exception("member must be either PropertyInfo or FieldInfo");
            }
        }

        public static void SetValue(this MemberInfo member, object target, object value)
        {
            if (member is PropertyInfo)
            {
                (member as PropertyInfo).SetValue(target, value, null);
            }
            else if (member is FieldInfo)
            {
                (member as FieldInfo).SetValue(target, value);
            }
            else
            {
                throw new Exception("destinationMember must be either PropertyInfo or FieldInfo");
            }
        }

        /// <summary>
        /// Deep clones specific object.
        /// Analogue can be found here: https://stackoverflow.com/questions/129389/how-do-you-do-a-deep-copy-an-object-in-net-c-specifically
        /// This is now improved version (list support added)
        /// </summary>
        /// <param name = "obj">object to be cloned</param>
        /// <returns>full copy of object.</returns>
        public static object DeepClone(this object obj)
        {
            if (obj == null)
                return null;

            Type type = obj.GetType();

            if (obj is IList)
            {
                IList list = ((IList)obj);
                IList newlist = (IList)Activator.CreateInstance(obj.GetType(), list.Count);

                foreach (object elem in list)
                    newlist.Add(DeepClone(elem));

                return newlist;
            } //if

            if (type.IsValueType || type == typeof(string))
            {
                return obj;
            }
            else if (type.IsArray)
            {
                Type elementType = Type.GetType(type.FullName.Replace("[]", string.Empty));
                var array = obj as Array;
                Array copied = Array.CreateInstance(elementType, array.Length);

                for (int i = 0; i < array.Length; i++)
                    copied.SetValue(DeepClone(array.GetValue(i)), i);

                return Convert.ChangeType(copied, obj.GetType());
            }
            else if (type.IsClass)
            {
                object toret = Activator.CreateInstance(obj.GetType());

                MemberInfo[] fields = type.GetFieldsAndProperties();
                foreach (MemberInfo field in fields)
                {
                    // Don't clone parent back-reference classes. (Using special kind of naming 'parent' 
                    // to indicate child's parent class.
                    if (field.Name == "parent")
                    {
                        continue;
                    }

                    object fieldValue = field.GetValue(obj);

                    if (fieldValue == null)
                        continue;

                    field.SetValue(toret, DeepClone(fieldValue));
                }

                return toret;
            }
            else
            {
                // Don't know that type, don't know how to clone it.
                if (Debugger.IsAttached)
                    Debugger.Break();

                return null;
            }
        } //DeepClone
    }

}

В. Почему я должен выбрать этот ответ?

  • Выберите этот ответ, если вам нужна максимальная скорость, на которую способен .NET.
  • Не обращайте внимания на этот ответ, если вам нужен действительно простой метод клонирования.

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

В 10 раз быстрее, чем другие методы

Следующий метод выполнения глубокого клонирования:

  • В 10 раз быстрее, чем все, что связано с сериализацией / десериализацией;
  • Чертовски близко к теоретической максимальной скорости, на которую способен .NET.

И способ ...

Для максимальной скорости вы можете использовать Вложенный MemberwiseClone для глубокой копии. Это почти такая же скорость, как копирование структуры значения, и намного быстрее, чем (а) отражение или (б) сериализация (как описано в других ответах на этой странице).

Обратите внимание, что если вы используете Вложенный MemberwiseClone для глубокой копии, вам нужно вручную реализовать ShallowCopy для каждого вложенного уровня в классе и DeepCopy, который вызывает все указанные методы ShallowCopy для создания полного клона. Это просто: всего несколько строк, см. Демонстрационный код ниже.

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

  • 1,08 секунды для вложенного элемента MemberwiseClone во вложенных структурах
  • 4,77 секунды для Nested MemberwiseClone во вложенных классах
  • 39,93 секунды для сериализации / десериализации

Использование Nested MemberwiseClone в классе почти так же быстро, как копирование структуры, а копирование структуры чертовски близко к теоретической максимальной скорости, на которую способен .NET.

Demo 1 of shallow and deep copy, using classes and MemberwiseClone:
  Create Bob
    Bob.Age=30, Bob.Purchase.Description=Lamborghini
  Clone Bob >> BobsSon
  Adjust BobsSon details
    BobsSon.Age=2, BobsSon.Purchase.Description=Toy car
  Proof of deep copy: If BobsSon is a true clone, then adjusting BobsSon details will not affect Bob:
    Bob.Age=30, Bob.Purchase.Description=Lamborghini
  Elapsed time: 00:00:04.7795670,30000000

Demo 2 of shallow and deep copy, using structs and value copying:
  Create Bob
    Bob.Age=30, Bob.Purchase.Description=Lamborghini
  Clone Bob >> BobsSon
  Adjust BobsSon details:
    BobsSon.Age=2, BobsSon.Purchase.Description=Toy car
  Proof of deep copy: If BobsSon is a true clone, then adjusting BobsSon details will not affect Bob:
    Bob.Age=30, Bob.Purchase.Description=Lamborghini
  Elapsed time: 00:00:01.0875454,30000000

Demo 3 of deep copy, using class and serialize/deserialize:
  Elapsed time: 00:00:39.9339425,30000000

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

// Nested MemberwiseClone example. 
// Added to demo how to deep copy a reference class.
[Serializable] // Not required if using MemberwiseClone, only used for speed comparison using serialization.
public class Person
{
    public Person(int age, string description)
    {
        this.Age = age;
        this.Purchase.Description = description;
    }
    [Serializable] // Not required if using MemberwiseClone
    public class PurchaseType
    {
        public string Description;
        public PurchaseType ShallowCopy()
        {
            return (PurchaseType)this.MemberwiseClone();
        }
    }
    public PurchaseType Purchase = new PurchaseType();
    public int Age;
    // Add this if using nested MemberwiseClone.
    // This is a class, which is a reference type, so cloning is more difficult.
    public Person ShallowCopy()
    {
        return (Person)this.MemberwiseClone();
    }
    // Add this if using nested MemberwiseClone.
    // This is a class, which is a reference type, so cloning is more difficult.
    public Person DeepCopy()
    {
            // Clone the root ...
        Person other = (Person) this.MemberwiseClone();
            // ... then clone the nested class.
        other.Purchase = this.Purchase.ShallowCopy();
        return other;
    }
}
// Added to demo how to copy a value struct (this is easy - a deep copy happens by default)
public struct PersonStruct
{
    public PersonStruct(int age, string description)
    {
        this.Age = age;
        this.Purchase.Description = description;
    }
    public struct PurchaseType
    {
        public string Description;
    }
    public PurchaseType Purchase;
    public int Age;
    // This is a struct, which is a value type, so everything is a clone by default.
    public PersonStruct ShallowCopy()
    {
        return (PersonStruct)this;
    }
    // This is a struct, which is a value type, so everything is a clone by default.
    public PersonStruct DeepCopy()
    {
        return (PersonStruct)this;
    }
}
// Added only for a speed comparison.
public class MyDeepCopy
{
    public static T DeepCopy<T>(T obj)
    {
        object result = null;
        using (var ms = new MemoryStream())
        {
            var formatter = new BinaryFormatter();
            formatter.Serialize(ms, obj);
            ms.Position = 0;
            result = (T)formatter.Deserialize(ms);
            ms.Close();
        }
        return (T)result;
    }
}

Затем вызовите демонстрацию из основного:

void MyMain(string[] args)
{
    {
        Console.Write("Demo 1 of shallow and deep copy, using classes and MemberwiseCopy:\n");
        var Bob = new Person(30, "Lamborghini");
        Console.Write("  Create Bob\n");
        Console.Write("    Bob.Age = {0}, Bob.Purchase.Description = {1}\n", Bob.Age, Bob.Purchase.Description);
        Console.Write("  Clone Bob >> BobsSon\n");
        var BobsSon = Bob.DeepCopy();
        Console.Write("  Adjust BobsSon details\n");
        BobsSon.Age = 2;
        BobsSon.Purchase.Description = "Toy car";
        Console.Write("    BobsSon.Age = {0}, BobsSon.Purchase.Description = {1}\n", BobsSon.Age, BobsSon.Purchase.Description);
        Console.Write("  Proof of deep copy: If BobsSon is a true clone, then adjusting BobsSon details will not affect Bob:\n");
        Console.Write("    Bob.Age = {0}, Bob.Purchase.Description = {1}\n", Bob.Age, Bob.Purchase.Description);
        Debug.Assert(Bob.Age == 30);
        Debug.Assert(Bob.Purchase.Description == "Lamborghini");
        var sw = new Stopwatch();
        sw.Start();
        int total = 0;
        for (int i = 0; i < 100000; i++)
        {
            var n = Bob.DeepCopy();
            total += n.Age;
        }
        Console.Write("  Elapsed time: {0},{1}\n\n", sw.Elapsed, total);
    }
    {               
        Console.Write("Demo 2 of shallow and deep copy, using structs:\n");
        var Bob = new PersonStruct(30, "Lamborghini");
        Console.Write("  Create Bob\n");
        Console.Write("    Bob.Age = {0}, Bob.Purchase.Description = {1}\n", Bob.Age, Bob.Purchase.Description);
        Console.Write("  Clone Bob >> BobsSon\n");
        var BobsSon = Bob.DeepCopy();
        Console.Write("  Adjust BobsSon details:\n");
        BobsSon.Age = 2;
        BobsSon.Purchase.Description = "Toy car";
        Console.Write("    BobsSon.Age = {0}, BobsSon.Purchase.Description = {1}\n", BobsSon.Age, BobsSon.Purchase.Description);
        Console.Write("  Proof of deep copy: If BobsSon is a true clone, then adjusting BobsSon details will not affect Bob:\n");
        Console.Write("    Bob.Age = {0}, Bob.Purchase.Description = {1}\n", Bob.Age, Bob.Purchase.Description);                
        Debug.Assert(Bob.Age == 30);
        Debug.Assert(Bob.Purchase.Description == "Lamborghini");
        var sw = new Stopwatch();
        sw.Start();
        int total = 0;
        for (int i = 0; i < 100000; i++)
        {
            var n = Bob.DeepCopy();
            total += n.Age;
        }
        Console.Write("  Elapsed time: {0},{1}\n\n", sw.Elapsed, total);
    }
    {
        Console.Write("Demo 3 of deep copy, using class and serialize/deserialize:\n");
        int total = 0;
        var sw = new Stopwatch();
        sw.Start();
        var Bob = new Person(30, "Lamborghini");
        for (int i = 0; i < 100000; i++)
        {
            var BobsSon = MyDeepCopy.DeepCopy<Person>(Bob);
            total += BobsSon.Age;
        }
        Console.Write("  Elapsed time: {0},{1}\n", sw.Elapsed, total);
    }
    Console.ReadKey();
}

Опять же, обратите внимание, что если вы используете Вложенный MemberwiseClone для глубокой копии, вам нужно вручную реализовать ShallowCopy для каждого вложенного уровня в классе и DeepCopy, который вызывает все указанные методы ShallowCopy для создания полного клона. Это просто: всего несколько строк, см. Демонстрационный код выше.

Типы значений и типы ссылок

Обратите внимание, что когда дело доходит до клонирования объекта, существует большая разница между «структура» и «учебный класс»:

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

См. различия между типами значений и ссылочными типами.

Контрольные суммы для помощи в отладке

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

Действительно полезно для отделения многих потоков от многих других потоков.

Отличный вариант использования этого кода - подача клонов вложенного класса или структуры в очередь для реализации шаблона производитель / потребитель.

  • У нас может быть один (или несколько) потоков, изменяющих принадлежащий им класс, а затем проталкивая полную копию этого класса в ConcurrentQueue.
  • Затем у нас есть один (или несколько) потоков, извлекающих копии этих классов и работающих с ними.

Это очень хорошо работает на практике и позволяет нам отделить множество потоков (производителей) от одного или нескольких потоков (потребителей).

И этот метод также невероятно быстр: если мы используем вложенные структуры, он в 35 раз быстрее, чем сериализация / десериализация вложенных классов, и позволяет нам использовать все потоки, доступные на машине.

Обновлять

Судя по всему, ExpressMapper работает так же быстро, если не быстрее, чем ручное кодирование, подобное приведенному выше. Возможно, мне придется посмотреть, как они сравниваются с профилировщиком.

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

Lasse V. Karlsen 04.07.2015 20:34

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

Contango 04.07.2015 20:51

При использовании Marc Gravells protobuf-net в качестве сериализатора принятый ответ требует некоторых незначительных изменений, поскольку копируемый объект не будет отнесен к [Serializable] и, следовательно, не может быть сериализован, а метод Clone вызовет исключение. Я модифицировал его для работы с protobuf-net:

public static T Clone<T>(this T source)
{
    if (Attribute.GetCustomAttribute(typeof(T), typeof(ProtoBuf.ProtoContractAttribute))
           == null)
    {
        throw new ArgumentException("Type has no ProtoContract!", "source");
    }

    if (Object.ReferenceEquals(source, null))
    {
        return default(T);
    }

    IFormatter formatter = ProtoBuf.Serializer.CreateFormatter<T>();
    using (Stream stream = new MemoryStream())
    {
        formatter.Serialize(stream, source);
        stream.Seek(0, SeekOrigin.Begin);
        return (T)formatter.Deserialize(stream);
    }
}

Это проверяет наличие атрибута [ProtoContract] и использует собственное средство форматирования protobufs для сериализации объекта.

Хорошо, в этом посте есть очевидный пример с отражением, НО отражение обычно медленное, пока вы не начнете правильно его кешировать.

если вы кешируете его правильно, то он будет глубоко клонировать 1000000 объектов на 4,6 секунды (измеряется Watcher).

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

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

foreach (var prop in propList)
{
        var value = prop.GetValue(source, null);   
        prop.SetValue(copyInstance, value, null);
}

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

https://stackoverflow.com/a/34365709/4711853

Вызов prop.GetValue(...) по-прежнему является отражением и не может быть кэширован. В дереве выражений он компилируется, поэтому быстрее

Tseng 28.09.2016 17:17

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

https://github.com/kalisohn/CloneBehave

Также доступно как пакет nuget: https://www.nuget.org/packages/Clone.Behave/1.0.0

Например: Следующий код будет DeepClone Address, но выполнить только поверхностную копию поля _currentJob.

public class Person 
{
  [DeepClone(DeepCloneBehavior.Shallow)]
  private Job _currentJob;      

  public string Name { get; set; }

  public Job CurrentJob 
  { 
    get{ return _currentJob; }
    set{ _currentJob = value; }
  }

  public Person Manager { get; set; }
}

public class Address 
{      
  public Person PersonLivingHere { get; set; }
}

Address adr = new Address();
adr.PersonLivingHere = new Person("John");
adr.PersonLivingHere.BestFriend = new Person("James");
adr.PersonLivingHere.CurrentJob = new Job("Programmer");

Address adrClone = adr.Clone();

//RESULT
adr.PersonLivingHere == adrClone.PersonLivingHere //false
adr.PersonLivingHere.Manager == adrClone.PersonLivingHere.Manager //false
adr.PersonLivingHere.CurrentJob == adrClone.PersonLivingHere.CurrentJob //true
adr.PersonLivingHere.CurrentJob.AnyProperty == adrClone.PersonLivingHere.CurrentJob.AnyProperty //true

Этот метод решил для меня проблему:

private static MyObj DeepCopy(MyObj source)
        {

            var DeserializeSettings = new JsonSerializerSettings { ObjectCreationHandling = ObjectCreationHandling.Replace };

            return JsonConvert.DeserializeObject<MyObj >(JsonConvert.SerializeObject(source), DeserializeSettings);

        }

Используйте это так: MyObj a = DeepCopy(b);

Сохраняйте простоту и используйте AutoMapper, как уже упоминалось, это простая небольшая библиотека для сопоставления одного объекта с другим ... Чтобы скопировать объект в другой с тем же типом, все, что вам нужно, это три строки кода:

MyType source = new MyType();
Mapper.CreateMap<MyType, MyType>();
MyType target = Mapper.Map<MyType, MyType>(source);

Целевой объект теперь является копией исходного объекта. Не достаточно просто? Создайте метод расширения, который будет использоваться повсюду в вашем решении:

public static T Copy<T>(this T source)
{
    T copy = default(T);
    Mapper.CreateMap<T, T>();
    copy = Mapper.Map<T, T>(source);
    return copy;
}

Метод расширения можно использовать следующим образом:

MyType copy = source.Copy();

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

Agorilla 11.04.2017 10:42

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

N73k 01.07.2019 21:27

Генератор кода

Мы видели много идей, от сериализации и ручной реализации до отражения, и я хочу предложить совершенно другой подход с использованием Генератор кода CGbR. Метод создания клонов эффективен с точки зрения памяти и ЦП и, следовательно, в 300 раз быстрее стандартного DataContractSerializer.

Все, что вам нужно, это частичное определение класса с ICloneable, а генератор сделает все остальное:

public partial class Root : ICloneable
{
    public Root(int number)
    {
        _number = number;
    }
    private int _number;

    public Partial[] Partials { get; set; }

    public IList<ulong> Numbers { get; set; }

    public object Clone()
    {
        return Clone(true);
    }

    private Root()
    {
    }
} 

public partial class Root
{
    public Root Clone(bool deep)
    {
        var copy = new Root();
        // All value types can be simply copied
        copy._number = _number; 
        if (deep)
        {
            // In a deep clone the references are cloned 
            var tempPartials = new Partial[Partials.Length];
            for (var i = 0; i < Partials.Length; i++)
            {
                var value = Partials[i];
                value = value.Clone(true);
                tempPartials[i] = value;
            }
            copy.Partials = tempPartials;
            var tempNumbers = new List<ulong>(Numbers.Count);
            for (var i = 0; i < Numbers.Count; i++)
            {
                var value = Numbers[i];
                tempNumbers.Add(value);
            }
            copy.Numbers = tempNumbers;
        }
        else
        {
            // In a shallow clone only references are copied
            copy.Partials = Partials; 
            copy.Numbers = Numbers; 
        }
        return copy;
    }
}

Примечание: В последней версии больше нулевых проверок, но я исключил их для лучшего понимания.

Вот быстрое и простое решение, которое сработало для меня, не прибегая к сериализации / десериализации.

public class MyClass
{
    public virtual MyClass DeepClone()
    {
        var returnObj = (MyClass)MemberwiseClone();
        var type = returnObj.GetType();
        var fieldInfoArray = type.GetRuntimeFields().ToArray();

        foreach (var fieldInfo in fieldInfoArray)
        {
            object sourceFieldValue = fieldInfo.GetValue(this);
            if (!(sourceFieldValue is MyClass))
            {
                continue;
            }

            var sourceObj = (MyClass)sourceFieldValue;
            var clonedObj = sourceObj.DeepClone();
            fieldInfo.SetValue(returnObj, clonedObj);
        }
        return returnObj;
    }
}

РЕДАКТИРОВАТЬ: требует

    using System.Linq;
    using System.Reflection;

Вот как я это использовал

public MyClass Clone(MyClass theObjectIneededToClone)
{
    MyClass clonedObj = theObjectIneededToClone.DeepClone();
}

Лучше всего реализовать метод расширения, например

public static T DeepClone<T>(this T originalObject)
{ /* the cloning code */ }

а затем используйте его в любом месте решения,

var copy = anyObject.DeepClone();

У нас могут быть следующие три реализации:

  1. По сериализации (самый короткий код)
  2. По отражению - В 5 раз быстрее
  3. По деревьям выражений - В 20 раз быстрее

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

клонирование кода с использованием деревьев выражений, которые вы опубликовали codeproject.com/Articles/1111658/…, не работает с более новыми версиями .Net framework с исключением безопасности, Операция может дестабилизировать время выполнения, это в основном исключение из-за искаженного дерева выражений, которое используется для генерации Func во время выполнения, пожалуйста, проверьте Если у вас есть какое-то решение.На самом деле я видел проблему только со сложными объектами с глубокой иерархией, простой легко копируется

Mrinal Kamboj 24.12.2017 17:36

Реализация ExpressionTree кажется очень хорошей. Он даже работает с круговыми ссылками и закрытыми членами. Атрибуты не нужны. Лучший ответ, который я нашел.

N73k 01.07.2019 23:29

Лучший ответ, сработал очень хорошо, ты спас мне день

Adel Mourad 12.01.2020 20:40

Думаю, ты можешь попробовать это.

MyObject myObj = GetMyObj(); // Create and fill a new object
MyObject newObj = new MyObject(myObj); //DeepClone it

Я знаю, что этот вопрос, и отвечать сидит здесь какое-то время, и последующее - не совсем ответ, а скорее наблюдение, на которое я недавно наткнулся, когда проверял, действительно ли частные лица не клонируются (я бы не был собой, если бы не ;) когда я с радостью скопировал @johnc обновленный ответ.

Я просто сделал себе метод расширения (который в значительной степени скопирован из вышеупомянутого ответа):

public static class CloneThroughJsonExtension
{
    private static readonly JsonSerializerSettings DeserializeSettings = new JsonSerializerSettings { ObjectCreationHandling = ObjectCreationHandling.Replace };

    public static T CloneThroughJson<T>(this T source)
    {
        return ReferenceEquals(source, null) ? default(T) : JsonConvert.DeserializeObject<T>(JsonConvert.SerializeObject(source), DeserializeSettings);
    }
}

и наивно отбросил такой класс (на самом деле их было больше, но они не связаны):

public class WhatTheHeck
{
    public string PrivateSet { get; private set; } // matches ctor param name

    public string GetOnly { get; } // matches ctor param name

    private readonly string _indirectField;
    public string Indirect => $"Inception of: {_indirectField} "; // matches ctor param name
    public string RealIndirectFieldVaule => _indirectField;

    public WhatTheHeck(string privateSet, string getOnly, string indirect)
    {
        PrivateSet = privateSet;
        GetOnly = getOnly;
        _indirectField = indirect;
    }
}

и такой код:

var clone = new WhatTheHeck("Private-Set-Prop cloned!", "Get-Only-Prop cloned!", "Indirect-Field clonned!").CloneThroughJson();
Console.WriteLine($"1. {clone.PrivateSet}");
Console.WriteLine($"2. {clone.GetOnly}");
Console.WriteLine($"3.1. {clone.Indirect}");
Console.WriteLine($"3.2. {clone.RealIndirectFieldVaule}");

привело к:

1. Private-Set-Prop cloned!
2. Get-Only-Prop cloned!
3.1. Inception of: Inception of: Indirect-Field cloned!
3.2. Inception of: Indirect-Field cloned!

Я подумал: «ЧТО ЗА Х ...», поэтому я взял репозиторий Newtonsoft.Json на Github и начал копать. В результате получается следующее: при десериализации типа, который имеет только один ctor и его имена параметров совпадают (без учета регистра) с именами общедоступных свойств, они будут переданы ctor в качестве этих параметров. Некоторые подсказки можно найти в кодах здесь и здесь.

Нижняя линия

Я знаю, что это довольно необычный случай, и пример кода немного оскорбительный, но эй! Это застало меня врасплох, когда я проверял, не ждет ли в кустах какой-нибудь дракон, чтобы выскочить и укусить меня за задницу. ;)

Картограф выполняет глубокую копию. По каждому члену вашего объекта он создает новый объект и присваивает все его значения. Он работает рекурсивно с каждым непримитивным внутренним членом.

Предлагаю вам один из самых быстрых, активно разрабатываемых на данный момент. Предлагаю UltraMapper https://github.com/maurosampietro/UltraMapper

Пакеты Nuget: https://www.nuget.org/packages/UltraMapper/

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

M.A.R. 23.04.2017 12:17

Я нашел новый способ сделать это - Emit.

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

Emit может видеть официальный документ и Гид

Вы должны выучить некоторый IL, чтобы читать код. Я напишу код, который сможет скопировать свойство в классе.

public static class Clone
{        
    // ReSharper disable once InconsistentNaming
    public static void CloneObjectWithIL<T>(T source, T los)
    {
        //see http://lindexi.oschina.io/lindexi/post/C-%E4%BD%BF%E7%94%A8Emit%E6%B7%B1%E5%85%8B%E9%9A%86/
        if (CachedIl.ContainsKey(typeof(T)))
        {
            ((Action<T, T>) CachedIl[typeof(T)])(source, los);
            return;
        }
        var dynamicMethod = new DynamicMethod("Clone", null, new[] { typeof(T), typeof(T) });
        ILGenerator generator = dynamicMethod.GetILGenerator();

        foreach (var temp in typeof(T).GetProperties().Where(temp => temp.CanRead && temp.CanWrite))
        {
            //do not copy static that will except
            if (temp.GetAccessors(true)[0].IsStatic)
            {
                continue;
            }

            generator.Emit(OpCodes.Ldarg_1);// los
            generator.Emit(OpCodes.Ldarg_0);// s
            generator.Emit(OpCodes.Callvirt, temp.GetMethod);
            generator.Emit(OpCodes.Callvirt, temp.SetMethod);
        }
        generator.Emit(OpCodes.Ret);
        var clone = (Action<T, T>) dynamicMethod.CreateDelegate(typeof(Action<T, T>));
        CachedIl[typeof(T)] = clone;
        clone(source, los);
    }

    private static Dictionary<Type, Delegate> CachedIl { set; get; } = new Dictionary<Type, Delegate>();
}

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

Еще один ответ JSON.NET. Эта версия работает с классами, которые не реализуют ISerializable.

public static class Cloner
{
    public static T Clone<T>(T source)
    {
        if (ReferenceEquals(source, null))
            return default(T);

        var settings = new JsonSerializerSettings { ContractResolver = new ContractResolver() };

        return JsonConvert.DeserializeObject<T>(JsonConvert.SerializeObject(source, settings), settings);
    }

    class ContractResolver : DefaultContractResolver
    {
        protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
        {
            var props = type.GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)
                .Select(p => base.CreateProperty(p, memberSerialization))
                .Union(type.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)
                    .Select(f => base.CreateProperty(f, memberSerialization)))
                .ToList();
            props.ForEach(p => { p.Writable = true; p.Readable = true; });
            return props;
        }
    }
}

Расширение C#, которое также будет поддерживать типы "not ISerializable".

 public static class AppExtensions
 {                                                                      
       public static T DeepClone<T>(this T a)
       {
           using (var stream = new MemoryStream())
           {
               var serializer = new System.Xml.Serialization.XmlSerializer(typeof(T));

               serializer.Serialize(stream, a);
               stream.Position = 0;
               return (T)serializer.Deserialize(stream);
           }
       }                                                                    
 }

использование

       var obj2 = obj1.DeepClone()

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

В большинстве реальных ситуаций вы также хотите иметь как можно более детальный контроль над процессом копирования, поскольку вы не только связаны со структурой доступа к данным, но и на практике скопированные бизнес-объекты редко должны быть на 100% одинаковыми. Подумайте о примере referenceId, который используется ORM для идентификации ссылок на объекты, полная глубокая копия также будет копировать этот идентификатор, поэтому в то время как в памяти объекты будут другими, как только вы отправите его в хранилище данных, он будет жаловаться, поэтому вы будете В любом случае необходимо изменить эти свойства вручную после копирования, и если объект изменяется, вам необходимо настроить его во всех местах, где используется общее глубокое копирование.

Расширяя ответ @cregox с помощью ICloneable, что на самом деле представляет собой глубокую копию? Это просто новый выделенный объект в куче, который идентичен исходному объекту, но занимает другое пространство памяти, как таковой, а не с использованием общих функций клонирования, почему бы просто не создать новый объект?

Я лично использую идею статических фабричных методов для объектов моей предметной области.

Пример:

    public class Client
    {
        public string Name { get; set; }

        protected Client()
        {
        }

        public static Client Clone(Client copiedClient)
        {
            return new Client
            {
                Name = copiedClient.Name
            };
        }
    }

    public class Shop
    {
        public string Name { get; set; }

        public string Address { get; set; }

        public ICollection<Client> Clients { get; set; }

        public static Shop Clone(Shop copiedShop, string newAddress, ICollection<Client> clients)
        {
            var copiedClients = new List<Client>();
            foreach (var client in copiedShop.Clients)
            {
                copiedClients.Add(Client.Clone(client));
            }

            return new Shop
            {
                Name = copiedShop.Name,
                Address = newAddress,
                Clients = copiedClients
            };
        }
    }

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

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

Стандартные атрибуты игнорирования поддерживаются с использованием [IgnoreDataMember], [NonSerialized]. Поддерживает сложные коллекции, свойства без сеттеров, поля только для чтения и т. д.

Я надеюсь, что это поможет кому-то еще, кто столкнулся с теми же проблемами, что и я.

Глубокое клонирование - это копирование государственный. Для .netгосударственный означает поля.

Скажем, у кого-то есть иерархия:

static class RandomHelper
{
    private static readonly Random random = new Random();

    public static int Next(int maxValue) => random.Next(maxValue);
}

class A
{
    private readonly int random = RandomHelper.Next(100);

    public override string ToString() => $"{typeof(A).Name}.{nameof(random)} = {random}";
}

class B : A
{
    private readonly int random = RandomHelper.Next(100);

    public override string ToString() => $"{typeof(B).Name}.{nameof(random)} = {random} {base.ToString()}";
}

class C : B
{
    private readonly int random = RandomHelper.Next(100);

    public override string ToString() => $"{typeof(C).Name}.{nameof(random)} = {random} {base.ToString()}";
}

Клонирование можно сделать:

static class DeepCloneExtension
{
    // consider instance fields, both public and non-public
    private static readonly BindingFlags bindingFlags =
        BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance;

    public static T DeepClone<T>(this T obj) where T : new()
    {
        var type = obj.GetType();
        var result = (T)Activator.CreateInstance(type);

        do
            // copy all fields
            foreach (var field in type.GetFields(bindingFlags))
                field.SetValue(result, field.GetValue(obj));
        // for every level of hierarchy
        while ((type = type.BaseType) != typeof(object));

        return result;
    }
}

Демо1:

Console.WriteLine(new C());
Console.WriteLine(new C());

var c = new C();
Console.WriteLine($"{Environment.NewLine}Image: {c}{Environment.NewLine}");

Console.WriteLine(new C());
Console.WriteLine(new C());

Console.WriteLine($"{Environment.NewLine}Clone: {c.DeepClone()}{Environment.NewLine}");

Console.WriteLine(new C());
Console.WriteLine(new C());

Результат:

C.random = 92 B.random = 66 A.random = 71
C.random = 36 B.random = 64 A.random = 17

Image: C.random = 96 B.random = 18 A.random = 46

C.random = 60 B.random = 7 A.random = 37
C.random = 78 B.random = 11 A.random = 18

Clone: C.random = 96 B.random = 18 A.random = 46

C.random = 33 B.random = 63 A.random = 38
C.random = 4 B.random = 5 A.random = 79

Обратите внимание, все новые объекты имеют случайные значения для поля random, но clone точно соответствует image.

Демо2:

class D
{
    public event EventHandler Event;
    public void RaiseEvent() => Event?.Invoke(this, EventArgs.Empty);
}

// ...

var image = new D();
Console.WriteLine($"Created obj #{image.GetHashCode()}");

image.Event += (sender, e) => Console.WriteLine($"Event from obj #{sender.GetHashCode()}");
Console.WriteLine($"Subscribed to event of obj #{image.GetHashCode()}");

image.RaiseEvent();
image.RaiseEvent();

var clone = image.DeepClone();
Console.WriteLine($"obj #{image.GetHashCode()} cloned to obj #{clone.GetHashCode()}");

clone.RaiseEvent();
image.RaiseEvent();

Результат:

Created obj #46104728
Subscribed to event of obj #46104728
Event from obj #46104728
Event from obj #46104728
obj #46104728 cloned to obj #12289376
Event from obj #12289376
Event from obj #46104728

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

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

Ted Mucuzany 04.07.2019 16:25

Используя System.Text.Json:

https://devblogs.microsoft.com/dotnet/try-the-new-system-text-json-apis/

public static T DeepCopy<T>(this T source)
{
    return source == null ? default : JsonSerializer.Parse<T>(JsonSerializer.ToString(source));
}

Новый API использует Span<T>. Это должно быть быстро, было бы неплохо провести тесты.

Примечание: нет необходимости в ObjectCreationHandling.Replace, как в Json.NET, поскольку он заменяет значения коллекции по умолчанию. Вам следует забыть о Json.NET сейчас, поскольку все будет заменено новым официальным API.

Я не уверен, что это будет работать с частными полями.

DeepCloner: быстрый, простой и эффективный пакет NuGet для решения проблемы клонирования

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

Проект DeepCloner GitHub

Пакет DeepCloner NuGet

Немного проработав README, вот почему мы выбрали его на работе:

  • It can deep or shallow copy
  • In deep cloning all object graph is maintained.
  • Uses code-generation in runtime, as result cloning is blazingly fast
  • Objects copied by internal structure, no methods or ctors called
  • You don't need to mark classes somehow (like Serializable-attribute, or implement interfaces)
  • No requirement to specify object type for cloning. Object can be casted to interface or as an abstract object (e.g. you can clone array of ints as abstract Array or IEnumerable; even null can be cloned without any errors)
  • Cloned object doesn't have any ability to determine that he is clone (except with very specific methods)

Использование:

var deepClone = new { Id = 1, Name = "222" }.DeepClone();
var shallowClone = new { Id = 1, Name = "222" }.ShallowClone();

Спектакль:

README содержит сравнение производительности различных библиотек и методов клонирования: Производительность DeepCloner.

Требования:

  • .NET 4.0 или выше или .NET Standard 1.3 (.NET Core)
  • Требуется набор разрешений полного доверия или разрешение на отражение (MemberAccess)

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

EduLopez 19.05.2020 01:38

Отказ от ответственности: я являюсь автором упомянутого пакета.

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

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

BinaryFormatter требует атрибута Serializable, JsonConverter требует конструктора или атрибутов без параметров, ни один из них не обрабатывает поля или интерфейсы, доступные только для чтения, и оба работают в 10-30 раз медленнее, чем необходимо.

Деревья выражений

Вместо этого вы можете использовать Деревья выражений или Отражение. для генерации кода клонирования только один раз, а затем использовать этот скомпилированный код вместо медленного отражения или сериализации.

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

Вы можете найти проект на GitHub: https://github.com/marcelltoth/ObjectCloner

использование

Вы можете установить его из NuGet. Либо получите пакет ObjectCloner и используйте его как:

var clone = ObjectCloner.DeepClone(original);

или, если вы не против загрязнения вашего типа объекта расширениями, также получите ObjectCloner.Extensions и напишите:

var clone = original.DeepClone();

Спектакль

Простой тест клонирования иерархии классов показал производительность ~ в 3 раза быстрее, чем при использовании Reflection, ~ в 12 раз быстрее, чем сериализация Newtonsoft.Json и ~ в 36 раз быстрее, чем настоятельно рекомендуемый BinaryFormatter.

Причина, по которой сериализация по-прежнему популярна в 2019 году, заключается в том, что генерация кода работает ТОЛЬКО в надежных средах. Это означает, что он не будет работать в Unity или iOS и, вероятно, никогда не будет. Таким образом, генерация кода не переносима.

JamesHoux 05.07.2020 00:28

Я использовал версию NewtonSoft 12.0.3, у моего класса нет конструктора параметров, и он работает для меня

Ramil Aliyev 15.01.2021 20:51

Самый короткий способ, но нужна зависимость:

using Newtonsoft.Json;
    public static T Clone<T>(T source) =>
        JsonConvert.DeserializeObject<T>(JsonConvert.SerializeObject(source));

Для процесса клонирования объект может быть сначала преобразован в массив байтов, а затем преобразован обратно в объект.

public static class Extentions
{
    public static T Clone<T>(this T obj)
    {
        byte[] buffer = BinarySerialize(obj);
        return (T)BinaryDeserialize(buffer);
    }

    public static byte[] BinarySerialize(object obj)
    {
        using (var stream = new MemoryStream())
        {
            var formatter = new BinaryFormatter(); 
            formatter.Serialize(stream, obj); 
            return stream.ToArray();
        }
    }

    public static object BinaryDeserialize(byte[] buffer)
    {
        using (var stream = new MemoryStream(buffer))
        {
           var formatter = new BinaryFormatter(); 
           return formatter.Deserialize(stream);
        }
    }
}

Для процесса сериализации объект должен быть сериализован.

[Serializable]
public class MyObject
{
    public string Name  { get; set; }
}

Использование:

MyObject myObj  = GetMyObj();
MyObject newObj = myObj.Clone();

Создайте расширение:

public static T Clone<T>(this T theObject)
{
    string jsonData = JsonConvert.SerializeObject(theObject);
    return JsonConvert.DeserializeObject<T>(jsonData);
}

И назовите это так:

NewObject = OldObject.Clone();

Дополнение к @Konrad и @craastad с использованием встроенного System.Text.Json для .NET >5

https://docs.microsoft.com/en-us/dotnet/standard/serialization/system-text-json-how-to?pivots=dotnet-5-0

Метод:

public static T Clone<T>(T source)
{
    var serialized = JsonSerializer.Serialize(source);
    return JsonSerializer.Deserialize<T>(serialized);
}

Способ расширения:

public static class SystemExtension
{
    public static T Clone<T>(this T source)
    {
        var serialized = JsonSerializer.Serialize(source);
        return JsonSerializer.Deserialize<T>(serialized);
    }
}

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

Ogglas 09.03.2021 01:38

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