Глубокое клонирование объектов

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

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

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

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

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

17.09.2008 00:06:27
Может быть полезно: «Почему копирование объекта - ужасная вещь?» agiledeveloper.com/articles/cloning072002.htm
Pedro77 7.12.2011 11:56:18
stackoverflow.com/questions/8025890/… Другое решение ...
Felix K. 16.03.2012 16:39:48
Вы должны взглянуть на AutoMapper
Daniel Little 19.12.2012 00:36:13
Ваше решение намного сложнее, я заблудился, читая его ... хехехе. Я использую интерфейс DeepClone. открытый интерфейс IDeepCloneable <T> {T DeepClone (); }
Pedro77 9.08.2013 14:12:33
@ Pedro77 - Интересно, что в этой статье говорится о создании cloneметода в классе, а затем вызов его внутреннего частного конструктора, который передается this. Таким образом, копирование ужасно, но копирование тщательно (и статья определенно стоит прочитать) - нет. ; ^)
ruffin 5.09.2014 16:54:17
30 ОТВЕТОВ
РЕШЕНИЕ

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

Как упоминалось в другом месте, он требует, чтобы ваши объекты были сериализуемыми.

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.
    /// </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)
        {
            throw new ArgumentException("The type must be serializable.", nameof(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);
        }
    }
}

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

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

Если вы предпочитаете использовать новые методы расширения 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 serialisation 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 (Object.ReferenceEquals(source, null))
    {
        return default(T);
    }

    // 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);
}
1703
22.08.2019 12:18:20
stackoverflow.com/questions/78536/cloning-objects-in-c/… имеет ссылку на приведенный выше код [и ссылается на две другие подобные реализации, одна из которых более подходит в моем контексте]
Ruben Bartelink 4.02.2009 13:13:51
Сериализация / десериализация включает в себя значительные накладные расходы, которые не нужны. Смотрите интерфейс ICloneable и методы клонирования .MemberWise () в C #.
3Dave 28.01.2010 17:28:44
@ Давид, конечно, но если объекты легкие и производительность при использовании не слишком высока для ваших требований, то это полезный совет. Признаюсь, я не использовал его интенсивно с большими объемами данных в цикле, но я никогда не видел ни одной проблемы с производительностью.
johnc 29.01.2010 00:21:51
@Amir: на самом деле, no: typeof(T).IsSerializableтакже верно, если тип помечен [Serializable]атрибутом. Он не должен реализовывать ISerializableинтерфейс.
Daniel Gehriger 3.06.2011 15:25:44
Просто подумал, что упомяну, что хотя этот метод полезен, и я сам много раз использовал его, он совсем не совместим с Medium Trust - так что следите, пишете ли вы код, который требует совместимости. BinaryFormatter имеет доступ к закрытым полям и поэтому не может работать в наборе разрешений по умолчанию для сред с частичным доверием. Вы можете попробовать другой сериализатор, но убедитесь, что вызывающая сторона знает, что клон может быть не идеальным, если входящий объект полагается на частные поля.
Alex Norcliffe 17.10.2011 11:35:27

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

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

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

Приветствия.

19
3.07.2017 07:25:13
У ICloneable нет общего интерфейса, поэтому использовать этот интерфейс не рекомендуется.
Karg 17.09.2008 00:13:59
Простые и краткие ответы являются лучшими.
DavidGuaita 20.04.2018 00:45:30

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

84
3.12.2012 17:21:32
.Net не имеет конструкторов копирования.
Pop Catalin 17.09.2008 00:45:08
Конечно, это так: new MyObject (objToCloneFrom) Просто объявите ctor, который принимает объект для клонирования в качестве параметра.
Nick 17.09.2008 11:49:49
Это не одно и то же. Вы должны добавить его в каждый класс вручную, и вы даже не знаете, хотите ли вы получить глубокую копию.
Dave Van den Eynde 4.06.2009 08:01:53
+1 за копирование ctor. Вы должны вручную написать функцию clone () для каждого типа объектов, и удачи вам в этом, когда ваша иерархия классов достигнет нескольких уровней.
Andrew Grant 15.09.2009 00:50:40
С конструкторами копирования вы теряете иерархию. agiledeveloper.com/articles/cloning072002.htm
Will 6.11.2011 21:10:50

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

Для более подробного объяснения клонирования с использованием ICloneable ознакомьтесь с этой статьей .

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

Посмотрите эту статью Уголок разработчика для нескольких дополнительных вариантов (кредит Ян).

22
9.04.2018 22:46:07
У ICloneable нет общего интерфейса, поэтому использовать этот интерфейс не рекомендуется.
Karg 17.09.2008 00:15:18
Ваше решение работает до тех пор, пока оно не должно обрабатывать циклические ссылки, затем все начинает усложняться, лучше попытаться реализовать глубокое клонирование с использованием глубокой сериализации.
Pop Catalin 17.09.2008 00:46:58
К сожалению, не все объекты также сериализуемы, поэтому вы не всегда можете использовать этот метод. На данный момент ссылка Яна является наиболее полным ответом.
Zach Burlingame 17.09.2008 00:56:54

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

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

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

175
7.10.2015 18:37:12
Я не понимаю, почему ICloneable считается расплывчатым. Учитывая тип типа Dictionary (Of T, U), я ожидал бы, что ICloneable.Clone должен делать любой уровень глубокого и поверхностного копирования, необходимый для того, чтобы новый словарь был независимым словарем, который содержит те же T и U (содержимое структуры, и / или ссылки на объекты) в качестве оригинала. Где двусмысленность? Безусловно, универсальный ICloneable (Of T), который унаследовал ISelf (Of T), который включал метод «Self», был бы намного лучше, но я не вижу двусмысленности в отношении глубокого и мелкого клонирования.
supercat 12.01.2011 18:35:29
Ваш пример иллюстрирует проблему. Предположим, у вас есть словарь <строка, клиент>. Должны ли клонированный Словарь иметь те же объекты Customer, что и оригинал, или копии этих объектов Customer? Есть разумные варианты использования любого из них. Но ICloneable не дает понять, какой вы получите. Вот почему это не полезно.
Ryan Lundy 12.01.2011 18:53:08
@Kyralessa В статье Microsoft MSDN говорится, что именно эта проблема заключается в том, что вы не знаете, запрашиваете ли вы глубокую или мелкую копию.
crush 28.05.2014 19:05:49
Ответ от дубликата stackoverflow.com/questions/129389/… описывает расширение копирования, основанное на рекурсивном MembershipClone
Michael Freidgeim 23.01.2018 12:15:05

Я придумал это, чтобы преодолеть недостаток .NET, связанный с необходимостью глубокого копирования 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;
}

Я попытался придумать oneliner, который делает это, но это невозможно, так как 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();
        }
    }
}
12
3.12.2012 17:23:13

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

static public class SerializeHelper
{
    //Michael White, Holly Springs Consulting, 2009
    //michael@hollyspringsconsulting.com
    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);
    }
}
32
21.03.2017 14:11:33

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

8
19.10.2010 13:01:19

Простой метод расширения для копирования всех открытых свойств. Работает на любые объекты и не требует, чтобы класс был [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 ) } );
        }
    };
}
42
7.04.2016 12:59:49
Это, к сожалению, некорректно. Это эквивалентно вызову objectOne.MyProperty = objectTwo.MyProperty (то есть просто скопирует ссылку). Он не будет клонировать значения свойств.
Alex Norcliffe 18.10.2011 00:59:24
Алексу Норклиффу: автору вопроса задали вопрос о «копировании каждого свойства», а не о клонировании. в большинстве случаев точное дублирование свойств не требуется.
Konstantin Salavatov 28.03.2012 09:41:27
Я думаю об использовании этого метода, но с рекурсией. поэтому, если значение свойства является ссылкой, создайте новый объект и снова вызовите CopyTo. Я просто вижу одну проблему, что все используемые классы должны иметь конструктор без параметров. Кто-нибудь уже пробовал это? Мне также интересно, будет ли это работать со свойствами, содержащими классы .net, такие как DataRow и DataTable?
Koryu 25.07.2013 09:22:34

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

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;
}
8
6.09.2011 07:38:32
Это похоже на членский клон, потому что не знает о свойствах ссылочного типа
sll 6.11.2011 10:17:17
Если вам нужна невероятно быстрая производительность, не используйте эту реализацию: она использует отражение, поэтому она не будет такой быстрой. И наоборот, «преждевременная оптимизация - это зло», поэтому игнорируйте производительность до тех пор, пока вы не запустите профилировщик.
Contango 30.12.2011 17:30:57
CreateInstanceOfType не определен?
MonsterMMORPG 14.10.2015 19:40:48
Сбой на интергере: «Нестатический метод требует цели».
Mr.B 17.05.2016 09:08:31

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

  • Определите 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.

5
4.06.2013 14:29:45

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

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

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

Прежде всего, это все наши варианты:

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

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

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

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

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

Копирование объекта путем указания Newпоследующего имени класса часто приводит к тому, что код не является расширяемым. Использование клона, применение шаблона прототипа, является лучшим способом для достижения этой цели. Однако использование клона, как это предусмотрено в C # (и Java), также может быть довольно проблематичным. Лучше предоставить защищенный (непубличный) конструктор копирования и вызвать его из метода clone. Это дает нам возможность делегировать задачу создания объекта экземпляру самого класса, обеспечивая таким образом расширяемость, а также безопасное создание объектов с помощью конструктора защищенной копии.

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

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

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

119
20.10.2019 12:52:51
MS рекомендует не использовать ICloneableдля публичных членов. «Поскольку вызывающие функции Clone не могут зависеть от метода, выполняющего предсказуемую операцию клонирования, мы рекомендуем не реализовывать ICloneable в общедоступных API». msdn.microsoft.com/en-us/library/… Однако, исходя из объяснения, данного Venkat Subramaniam в вашей связанной статье, я думаю, что имеет смысл использовать в этой ситуации, пока создатели объектов ICloneable имеют глубокий понимание того, какие свойства должны быть глубокими и мелкими копиями (т. е. глубокая копия мозга, мелкая копия города)
BateTech 9.01.2015 16:57:19
Прежде всего, я далеко не эксперт в этой теме (публичные API). Я думаю, что на этот раз замечание MS имеет большой смысл. И я не думаю, что можно с уверенностью предполагать, что пользователи этого API будут иметь такое глубокое понимание. Таким образом, имеет смысл реализовывать его в общедоступном API, если это действительно не имеет значения для тех, кто будет его использовать. Я предполагаю, что наличие какого-то UML очень явно делает различие по каждому свойству может помочь. Но я хотел бы услышать от кого-то с большим опытом. : P
cregox 10.01.2015 03:45:43
Вы можете использовать CGbR Clone Generator и получить аналогичный результат без написания кода вручную.
Toxantron 9.06.2016 20:53:18
Реализация на промежуточном языке полезна
Michael Freidgeim 23.01.2018 12:35:25
Там нет финала в C #
Konrad 5.09.2018 11:31:25

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

MyObject oldObj; // The existing object to clone

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

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

посмотрите решение глубокого клонирования valueinjecter на CodePlex .

30
19.09.2016 22:57:37

Я хотел клонер для очень простых объектов, в основном из примитивов и списков. Если ваш объект из коробки 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);
    }
}
295
14.09.2018 06:16:54
решение даже быстрее, чем решение BinaryFormatter, сравнение производительности сериализации .NET
esskar 12.03.2014 10:25:34
Спасибо за это. Я смог сделать то же самое с сериализатором BSON, который поставляется с драйвером MongoDB для C #.
Mark Ewer 18.06.2014 00:58:20
Это лучший способ для меня, однако, я использую, Newtonsoft.Json.JsonConvertно это то же самое
Pierre 4.02.2015 12:20:30
Чтобы это работало, объект для клонирования должен быть сериализуемым, как уже упоминалось - это также означает, например, что он может не иметь циклических зависимостей
radomeit 22.02.2018 10:03:09
Я думаю, что это лучшее решение, так как реализация может применяться на большинстве языков программирования.
mr5 2.01.2019 07:58:34

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

 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);
1
8.08.2013 10:44:20

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

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

Вместо того, чтобы писать свои собственные Cloneили Copyметоды с тоном назначений между полями и свойствами, заставьте программу сделать это для себя, используя Expression Tree. 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 и т. Д.)
  • Nullable
  • 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>?

32
8.09.2015 15:26:38
Каковы шансы этого получить на NuGet? Похоже, лучшее решение. Как это по сравнению с NClone ?
crush 28.05.2014 19:56:53
Я думаю, что за этот ответ нужно проголосовать больше раз. Внедрение ICloneable вручную утомительно и подвержено ошибкам, использование отражения или сериализации является медленным, если важна производительность и вам необходимо скопировать тысячи объектов в течение короткого периода времени.
nightcoder 28.07.2015 14:15:47
Вовсе нет, вы ошибаетесь в рефлексии, вы просто должны правильно кешировать это. Проверьте мой ответ ниже stackoverflow.com/a/34368738/4711853
Roma Borodov 19.12.2015 09:32:53

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

Требуется System, 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;
    }

} 
4
11.04.2014 16:06:55

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

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

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

2
13.04.2014 12:53:53
Пробная переработка с объектом со свойствами с простыми типами и ссылочными типами. Только сделал мелкую копию свойства, которое было ссылочным типом.
Simon Tewsi 9.06.2016 21:44:00

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

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

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

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

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

yourClass newLine = oldLine.DeepCopy();

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

3
25.04.2014 09:39:22
Метод MemberwiseClone создает поверхностную копию, а не глубокую копию. msdn.microsoft.com/en-us/library/…
odyth 21.12.2014 01:38:53

РЕДАКТИРОВАТЬ: проект прекращен

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

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

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

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

17
21.10.2019 06:57:53
Этот, кажется, очень полезен
LuckyLikey 20.04.2015 13:53:00
Работать с одного снимка кода легче, чем для всей системы, особенно закрытой. Вполне понятно, что ни одна библиотека не может решить все проблемы одним выстрелом. Некоторые расслабления должны быть сделаны.
TarmoPikaro 24.04.2015 19:56:32
Я попробовал ваше решение, и, похоже, оно работает хорошо, спасибо! Я думаю, что за этот ответ нужно проголосовать больше раз. Внедрение ICloneable вручную утомительно и подвержено ошибкам, использование отражения или сериализации является медленным, если важна производительность и вам необходимо скопировать тысячи объектов в течение короткого периода времени.
nightcoder 28.07.2015 15:25:13
Я попробовал это, и у меня это не сработало. Выдает исключение MemberAccess.
Michael Brown 15.11.2018 22:22:12
Это не работает с более новыми версиями .NET и прекращено
Michael Sander 19.11.2018 15:55:11

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

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

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

7
6.03.2015 15:39:15

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

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

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

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

4
21.03.2017 14:14:04

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

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

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

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

}
2
23.05.2017 12:18:36

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

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

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

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

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

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

И метод ...

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

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

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

  • 1,08 секунды для вложенного MemberwiseClone на вложенных структурах
  • 4,77 секунды для вложенного 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();
}

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

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

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

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

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

Контрольные суммы, чтобы помочь в отладке

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

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

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

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

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

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

Обновить

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

10
18.09.2015 18:12:33
Если вы копируете структуру, вы получаете мелкую копию, вам все равно может потребоваться конкретная реализация для глубокой копии.
Lasse V. Karlsen 4.07.2015 17:34:41
@ Лассе В. Карлсен. Да, вы абсолютно правы, я обновил ответ, чтобы прояснить ситуацию. Этот метод может быть использован для создания глубоких копий структур и классов. Вы можете запустить включенный пример демонстрационного кода, чтобы показать, как это делается, у него есть пример глубокого клонирования вложенной структуры и еще один пример глубокого клонирования вложенного класса.
Contango 4.07.2015 17:51:06

При использовании 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 для сериализации объекта.

2
22.08.2015 11:36:20

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

если вы кешируете его правильно, то он клонирует 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

4
23.05.2017 11:47:36
Вызов prop.GetValue(...)все еще является отражением и не может быть кэширован. В дереве выражений оно скомпилировано, так что быстрее
Tseng 28.09.2016 14:17:05

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

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
8
27.01.2016 12:53:06

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

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

7
12.04.2016 13:43:36

Сохраняйте простоту и используйте 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();
14
1.01.2020 13:39:02
Будьте осторожны с этим, он работает очень плохо. Я закончил тем, что переключился на ответ johnc, который является столь же коротким как этот и работает намного лучше.
Agorilla 11.04.2017 07:42:32
Это только делает мелкую копию.
N73k 1.07.2019 18:27:21