Nano Hash - криптовалюты, майнинг, программирование

Как сделать глубокую копию объекта в .NET?

Я хочу настоящую глубокую копию. В Java это было легко, но как это сделать на C #?

24.09.2008

  • Что делает глубокая копия? Копирует ли он битовый поток? 24.09.2008
  • Глубокая копия - это то, что копирует КАЖДОЕ поле объекта. Неглубокая копия создаст только новый объект и укажет все поля на оригинал. 24.09.2008
  • Платформа для копирования / клонирования объектов .NET: github.com/havard/copyable 18.02.2011
  • Глубокая копия создает второй экземпляр объекта с теми же значениями. Неглубокая копия (упрощенная) подобна созданию второй ссылки на объект. 30.03.2011
  • Используйте Mapper, я предлагаю UltraMapper github.com/maurosampietro/UltraMapper 23.04.2017
  • проверьте это сообщение: stackoverflow.com/a/52490699/1404642 25.09.2018
  • BinaryFormatter небезопасен, посмотрите официальные документы: docs.microsoft.com/en-us/dotnet/api/ 26.01.2021
  • Если перфоманс не является проблемой, newtonsoft приходит на помощь ... JsonConvert.DeserializeObject ‹MyType› (JsonConvert.SerializeObject (MyInstance)); 09.06.2021

Ответы:


1

Важная заметка

BinaryFormatter устарел и больше не будет доступен в .NET после ноября 2023 года. См. Стратегия отмены BinaryFormatter


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

public static T DeepClone<T>(this T obj)
{
 using (var ms = new MemoryStream())
 {
   var formatter = new BinaryFormatter();
   formatter.Serialize(ms, obj);
   ms.Position = 0;

   return (T) formatter.Deserialize(ms);
 }
}

Примечания:

  • Ваш класс ДОЛЖЕН быть отмечен как [Serializable], чтобы это работало.

  • Ваш исходный файл должен включать следующий код:

     using System.Runtime.Serialization.Formatters.Binary;
     using System.IO;
    
24.09.2008
  • Что произойдет, если у объекта есть событие. Все ли они потеряли из-за сериализации? 24.09.2008
  • Подписки на события включаются в граф сериализации, поскольку BinaryFormatter использует поля через отражение, а события - это просто поля типов делегатов плюс методы добавления / удаления / вызова. Вы можете использовать [field: NonSerialized] в событии, чтобы этого избежать. 25.09.2008
  • Что это за необъявленная переменная потока? Или это что-то только на C #, а не на VB.NET? Я преобразовал все, кроме этой переменной. 19.03.2009
  • мс. местонахождение или мс. положение? или это как-то связано с версией Framework? 30.03.2009
  • Как пометить что-то как [Serializable] ?? 11.04.2011
  • @ Sean87: над объявлением класса добавьте [Serializable]. поэтому [Serializable]public class Foo { } сделает Foo помеченным как сериализуемый. 04.08.2011
  • Рекурсивный MemberwiseClone также выполняет глубокое копирование, он работает в 3 раза быстрее, чем BinaryFormatter, не требует конструктора по умолчанию или каких-либо атрибутов. См. Мой ответ: stackoverflow.com/a/11308879/235715 12.07.2012
  • Это создает любопытную сборку исключения, не обнаруженную при использовании этого кода утилиты в UserControlTestContainer. Это действительно странно, потому что сборка загружена ... 21.05.2013
  • Чтобы программно обработать вашу первую заметку, вы можете немного лучше обработать исключение, используя следующее: if (!typeof(T).IsSerializable) { throw new ArgumentException("Type {0} is not serializable",typeof(T).Name); } 16.07.2013
  • как бы вы ответили на -› эта очевидная проблема с утечкой памяти с помощью Deserialize ()? 06.03.2014
  • Более полезно использовать var formatter = new BinaryFormatter {Context = new StreamingContext (StreamingContextStates.Clone)}; 27.11.2014
  • хотя это работает в некоторых сценариях - сериализовать все и десериализовать все просто для создания копии объекта кажется излишним. Я чувствую, что это должен быть пример того, что делать, когда вам нужно завершить свой проект в следующие 5 минут. 25.04.2015
  • Это также очень медленно. 09.06.2016
  • Для тех, кому интересно, см. Это простое решение: stackoverflow.com/questions/222598/ 09.11.2016
  • ms.Position = 0 спас мне день! Без этого происходили действительно странные вещи. 06.02.2017
  • Красивый! Это было прямо у меня под носом, и я этого даже не заметил! 18.07.2018
  • Спасибо за ответ. Меня устраивает! 31.10.2018
  • Я знаю, что этот пост старый, но он по-прежнему пользуется успехом при поиске глубокого клонирования. Обратите внимание, что согласно Microsoft (aka.ms/binaryformatter) это больше не рекомендуется, поскольку оно небезопасно. . 23.11.2020

  • 2

    Я написал метод расширения глубокого копирования объектов, основанный на рекурсивном "MemberwiseClone". Это быстро (в три раза быстрее, чем BinaryFormatter), и работает с любыми объектами. Вам не нужен конструктор по умолчанию или сериализуемые атрибуты.

    Исходный код:

    using System.Collections.Generic;
    using System.Reflection;
    using System.ArrayExtensions;
    
    namespace System
    {
        public static class ObjectExtensions
        {
            private static readonly MethodInfo CloneMethod = typeof(Object).GetMethod("MemberwiseClone", BindingFlags.NonPublic | BindingFlags.Instance);
    
            public static bool IsPrimitive(this Type type)
            {
                if (type == typeof(String)) return true;
                return (type.IsValueType & type.IsPrimitive);
            }
    
            public static Object Copy(this Object originalObject)
            {
                return InternalCopy(originalObject, new Dictionary<Object, Object>(new ReferenceEqualityComparer()));
            }
            private static Object InternalCopy(Object originalObject, IDictionary<Object, Object> visited)
            {
                if (originalObject == null) return null;
                var typeToReflect = originalObject.GetType();
                if (IsPrimitive(typeToReflect)) return originalObject;
                if (visited.ContainsKey(originalObject)) return visited[originalObject];
                if (typeof(Delegate).IsAssignableFrom(typeToReflect)) return null;
                var cloneObject = CloneMethod.Invoke(originalObject, null);
                if (typeToReflect.IsArray)
                {
                    var arrayType = typeToReflect.GetElementType();
                    if (IsPrimitive(arrayType) == false)
                    {
                        Array clonedArray = (Array)cloneObject;
                        clonedArray.ForEach((array, indices) => array.SetValue(InternalCopy(clonedArray.GetValue(indices), visited), indices));
                    }
    
                }
                visited.Add(originalObject, cloneObject);
                CopyFields(originalObject, visited, cloneObject, typeToReflect);
                RecursiveCopyBaseTypePrivateFields(originalObject, visited, cloneObject, typeToReflect);
                return cloneObject;
            }
    
            private static void RecursiveCopyBaseTypePrivateFields(object originalObject, IDictionary<object, object> visited, object cloneObject, Type typeToReflect)
            {
                if (typeToReflect.BaseType != null)
                {
                    RecursiveCopyBaseTypePrivateFields(originalObject, visited, cloneObject, typeToReflect.BaseType);
                    CopyFields(originalObject, visited, cloneObject, typeToReflect.BaseType, BindingFlags.Instance | BindingFlags.NonPublic, info => info.IsPrivate);
                }
            }
    
            private static void CopyFields(object originalObject, IDictionary<object, object> visited, object cloneObject, Type typeToReflect, BindingFlags bindingFlags = BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.FlattenHierarchy, Func<FieldInfo, bool> filter = null)
            {
                foreach (FieldInfo fieldInfo in typeToReflect.GetFields(bindingFlags))
                {
                    if (filter != null && filter(fieldInfo) == false) continue;
                    if (IsPrimitive(fieldInfo.FieldType)) continue;
                    var originalFieldValue = fieldInfo.GetValue(originalObject);
                    var clonedFieldValue = InternalCopy(originalFieldValue, visited);
                    fieldInfo.SetValue(cloneObject, clonedFieldValue);
                }
            }
            public static T Copy<T>(this T original)
            {
                return (T)Copy((Object)original);
            }
        }
    
        public class ReferenceEqualityComparer : EqualityComparer<Object>
        {
            public override bool Equals(object x, object y)
            {
                return ReferenceEquals(x, y);
            }
            public override int GetHashCode(object obj)
            {
                if (obj == null) return 0;
                return obj.GetHashCode();
            }
        }
    
        namespace ArrayExtensions
        {
            public static class ArrayExtensions
            {
                public static void ForEach(this Array array, Action<Array, int[]> action)
                {
                    if (array.LongLength == 0) return;
                    ArrayTraverse walker = new ArrayTraverse(array);
                    do action(array, walker.Position);
                    while (walker.Step());
                }
            }
    
            internal class ArrayTraverse
            {
                public int[] Position;
                private int[] maxLengths;
    
                public ArrayTraverse(Array array)
                {
                    maxLengths = new int[array.Rank];
                    for (int i = 0; i < array.Rank; ++i)
                    {
                        maxLengths[i] = array.GetLength(i) - 1;
                    }
                    Position = new int[array.Rank];
                }
    
                public bool Step()
                {
                    for (int i = 0; i < Position.Length; ++i)
                    {
                        if (Position[i] < maxLengths[i])
                        {
                            Position[i]++;
                            for (int j = 0; j < i; j++)
                            {
                                Position[j] = 0;
                            }
                            return true;
                        }
                    }
                    return false;
                }
            }
        }
    
    }
    
    03.07.2012
  • Я пробовал это, но получил сообщение об ошибке, в котором говорилось, что мне нужны сериализуемые атрибуты в моем классе, который я клонирую ... 18.02.2013
  • @theJerm Я думаю, вы вызвали Clone вместо Copy, есть метод Clone, который использует BinaryFormatter, как описано в принятом ответе для сравнения тестов 18.02.2013
  • Спасибо, Алекс, да, мне нужно было вызвать копию, и это сработало! 20.02.2013
  • Для ReferenceEqualityComparer.GetHashCode(object obj) вы должны использовать RuntimeHelpers.GetHashCode(obj), иначе он будет использовать реальный хэш-код объекта. См. stackoverflow.com/a/11240110/495262 23.04.2014
  • Что касается IsPrimitive: по какой причине вы возвращаете истину для строки. Кроме того, есть ли причина, по которой вы используете сингл & вместо && в заявлении: return (type.IsValueType & type.IsPrimitive);? 23.04.2014
  • Основываясь на чтении кода, я предполагаю, что это не работает для типов, содержащих Delegates. Вы знаете, работают ли другие подходы и с делегатами? Я бы предположил, что нет. 23.04.2014
  • @MattSmith Вы абсолютно правы насчет GetHashCode, проверил, да исключение StackOverflow - gist.github.com/ Бурцев-Алексей / 11227277 23.04.2014
  • @MattSmith Это работало для делегатов, но я намеренно отключил его (установив null), см. github.com/Burtsev-Alexey/net-object-deep-copy/issues/7, подписчики были клонированы, в конце концов, если у вас было подключено два объекта A и B (по событию подписка), вы бы связали объекты A 'и B', это правильно, но это не то, чего хочет большинство людей при клонировании объектов. 23.04.2014
  • @AlexBurtsev, Что касается делегатов - хорошо, я изменил это, чтобы не копировать делегаты, а скопировать ссылку (аналогично тому, как вы обрабатываете строки), поскольку (как и строки) делегаты неизменяемы. Подход сериализации действительно обрабатывает делегатов, но я не уверен, есть ли у них проблема, аналогичная той, что вы испытывали с делегатами. 23.04.2014
  • @MattSmith Строки рассматриваются как примитивы, потому что 1: они неизменяемы, 2: вызов защищенного MemberwiseClone в строке приведет к повреждению памяти, строковые данные превратятся в случайные символы, и вскоре среда выполнения .NET выйдет из строя с внутренней ошибкой, говоря, что это но в .NET :-) 23.04.2014
  • @MattSmith Я полагаю, вы понимаете, что при повторном использовании одного и того же делегата в исходном и клонированном объекте они будут использовать те же объекты, на которые ссылались в делегате, например, у A есть делегат, который изменяет B, при клонировании A вы получите A ', который изменяет то же самое B, если вы хотите получить истинную копию (снимок) графов объектов (память), просто удалите строку, которая устанавливает делегат в null. Тогда вы получите A, модифицирующий B, и A, `` изменяющий B '' 23.04.2014
  • @AlexBurtsev, Хорошие моменты. То, что вы хотите, в некоторой степени зависит от того, к каким объектам осуществляется доступ в делегате. Если вы обращаетесь к копируемым объектам, вам, вероятно, понадобится скопированный делегат. Если вы обращаетесь к объекту, внешнему по отношению к скопированному объекту, вы, вероятно, хотите, чтобы делегат не копировался. Тем не менее, у меня нет варианта использования для делегатов, поэтому, возможно, лучше пока оставить его нереализованным (т.е. я выброшу исключение). 23.04.2014
  • Кстати, когда я включаю делегатов для моего простого тестового примера, он переполняется. Вот мой образец объекта, который я пытаюсь скопировать: shar.es/T86JR 23.04.2014
  • Я раздвоил и обновил ваш код для совместимости с новым API отражения на основе TypeInfo (используется в приложениях Windows Phone и Windows Store и их соответствующих профилях PCL). github.com/Gameleon12/net-object-deep- копировать / blob / master / 08.10.2014
  • Добавьте пример того, как использовать ваш метод расширения, это поможет конкретизировать ваш ответ. 15.01.2015
  • Еще быстрее (для меня) использовать JSON.NET и сериализовать, а затем десериализовать. Может не работать для всех (я думаю о частных полях и т. Д.). 01.04.2015
  • @AlexBurtsev, ваш код, использующий пространство имен System.ArrayExtensions, где я могу его получить? 12.04.2015
  • @ Alex141 - только что столкнулся с той же проблемой. Весь соответствующий код находится в указанном файле, внизу есть пространство имен ArrayExtensions. 15.05.2015
  • Это очень умная и мощная реализация, однако вы должны учесть несколько вещей, прежде чем решить, подходит ли она для вашей модели данных. Memberwiseclone() работает так быстро, потому что не вызывает конструкторов. Так что, если ваши конструкторы делают тяжелую работу, такую ​​как подписка на события, вам не повезло. Он полагается на копирование частных полей объекта, минуя бизнес-логику в свойствах и методах. Например, я видел, как поле hashCode копируется в коллекцию HashSet, хотя все экземпляры изменились. 26.05.2015
  • Я получаю StackOverflowException при клонировании объектов, которые взаимно ссылаются друг на друга ... Кто-нибудь знает обходной путь? 27.05.2015
  • Циклические ссылки @yu_ominae поддерживаются и отлично работают. Ваша проблема в другом месте. Если вы можете поделиться исходным кодом класса, который пытаетесь клонировать, откройте проблему на веб-сайте проекта. Иногда я сталкиваюсь с SO-исключениями при клонировании того, что не должно быть клонировано, например внутренней инфраструктуры .NET, на которую могут случайно ссылаться, особенно указатели и их отражения. Например, попытка клонирования объекта NHibernate, содержащего коллекцию, прикрепленную к сеансу, приведет к большому беспорядку, потому что коллекция NH, реализующая IList ‹T›, содержит внутри поле DbConnection. 27.05.2015
  • @AlexBurtsev Я пытался клонировать объекты коллекции, отражающие объекты в другом приложении. Доступ осуществляется через com-вызовы, так что, возможно, у меня есть ссылка, которая вызывает взрыв. Метод .Copy() работал нормально, если я установил точку останова и проверил содержимое списка в часах перед возобновлением выполнения. Странный. Я обнаружил, что звонки .ToList() на самом деле достигают того, чего я хочу сейчас, так что все в порядке. Спасибо за ответ :) 27.05.2015
  • Могу ли я использовать это, но при этом убедиться, что определенное поле копируется только по ссылке? 11.03.2016
  • @ Дэвид Не из коробки. 12.03.2016
  • Можете ли вы объяснить, в чем разница между глубиной и мелкостью? Я понимаю, что memberwiseClone неглубокий, и это то, что вы здесь вызываете. Если я чего-то не упускаю. 07.12.2016
  • Привет, Алекс! Спасибо за расширение. Это также события / делегаты DeepCopy? 05.01.2017
  • Кажется, есть проблемы с типом PropertyInfo. Есть ли способ пометить свойства этого типа как исключенные или разрешенные для ссылки? 13.07.2017
  • Ни единого комментария во всем. УХ ТЫ. 23.12.2018
  • По-прежнему можно изменять вложенные списки :( 19.03.2019
  • вы не копируете массивы, если их примитив + строка, но можно изменить одну копию, тогда другая копия увидит это изменение, которое не является глубокой копией. 24.03.2019
  • Предупреждение: такой подход тормозит XDocuments. Я больше не могу получить доступ к атрибутам по имени, даже если пространство имен не определено. 27.03.2019
  • Похоже, что List ‹BsonElement› не отражается должным образом в fieldInfo. Из: List<BsonElement> { BsonElement ("status=Connected"), BsonElement("lastConnectedTimeUtc=2019-05-15T10:50:40.5570932Z") } получается: BsonElement[] { BsonElement ("status=Connected"), BsonElement("lastConnectedTimeUtc=2019-05-15T10:50:40.5570932Z"), BsonElement("="), BsonElement("=") } 15.05.2019
  • Я использовал это в сочетании с fluentassertions, чтобы создать клон объекта, а затем проверить originalObject.Should (). BeEquivalentTo (objectClone). Однако это не удается, если объект содержит структуры Noda Time. Решением было добавить в InternalCopy такую ​​строку: if (typeToReflect == typeof(LocalDate) || typeToReflect == typeof(OffsetDateTime)) return originalObject; 10.03.2021
  • @ g.t.w.d - насколько это глубокое или мелкое. Если он остановился после вызова MemberwiseClone, то он будет неглубоким. Он использует это в качестве первого шага, а затем использует информацию об отражении для внесения некоторых рекурсивных изменений в клон. Это превращает мелкий клон в глубокий. (Я предполагаю, что причина первоначального создания MemberwiseClone заключается в том, чтобы избежать необходимости обрабатывать поля, которые на самом деле являются неглубокими - типы полей, которые не нуждаются в рекурсии.) 12.03.2021
  • Обратите внимание, что этот подход можно легко оптимизировать для использования настраиваемого интерфейса, который вы реализуете для ускорения ваших собственных классов. interface IDeepCopy { object DeepCopy(IDictionary<object, object> visited); } Каждый разработчик делает object DeepCopy(visited) { ..check-visited-add-to-visited..; MyType clone = MemberwiseClone; ... }, где ... рекурсивно повторяется по мере необходимости. Например. clone.myField1 = myField1.DeepCopy(visited); или для полей, отличных от IDeepCopy, выполните вызов InternalCopy, указанный в ответе. 12.03.2021
  • Я знаю, что этот код очень старый и не управляется постоянно, но у него есть серьезные проблемы. Если вы глубоко копируете объект, который имеет Type объект в качестве члена, Type сам объект также будет скопирован. И новый кажется таким же, как старый, но сравнение типов между двумя Type будет неудачным, потому что Type.Equals(Type) использует сравнение ссылок. И еще одно: если объект 'A' имеет определяемый пользователем struct член 'B', а 'B' снова имеет ссылку на объект 'C' в качестве члена, объект 'C' не будет глубоко скопирован с ' А '. Пожалуйста, используйте этот код осторожно. 07.05.2021

  • 3

    Основываясь на решении Килхоффера ...

    В C # 3.0 вы можете создать метод расширения следующим образом:

    public static class ExtensionMethods
    {
        // Deep clone
        public static T DeepClone<T>(this T a)
        {
            using (MemoryStream stream = new MemoryStream())
            {
                BinaryFormatter formatter = new BinaryFormatter();
                formatter.Serialize(stream, a);
                stream.Position = 0;
                return (T) formatter.Deserialize(stream);
            }
        }
    }
    

    который расширяет любой класс, помеченный как [Serializable], с помощью метода DeepClone

    MyClass copy = obj.DeepClone();
    
    31.07.2009
  • К этому добавьте общедоступный статический T DeepClone ‹T› (это T a), где T: ISerializable 11.02.2011
  • @Amir - классу не обязательно реализовывать ISerializable, достаточно пометки с помощью SerializableAttribute. Атрибут использует отражение для выполнения сериализации, а интерфейс позволяет вам написать собственный сериализатор. 14.02.2011
  • Я согласен с вашим утверждением, но мне нравится предложение Амира, поскольку оно обеспечивает проверку во время компиляции. Есть ли способ примирить их? 30.03.2011
  • Пройден модульный тест var stringbuilder = new StringBuilder (TestData); var copy = stringbuilder.DeepClone (); Assert.IsFalse (Equals (построитель строк, копия)); Большое спасибо. 06.05.2012
  • @Neil Этот метод в 10 раз медленнее, чем метод NestedMemberwiseClone, см. Мой пост на этой странице. 20.05.2012
  • Привет, @Gravitas. Неудивительно, что методы, написанные вручную, могут превзойти решение, использующее отражение. Было бы интересно сравнить производительность вашего решения с методом сериализации с ручным кодированием (то есть с реализацией ISerializable). 23.05.2012
  • @Gravitas, ручная сериализация была не лучше. Ваш метод клонирования с ручным кодированием намного быстрее для простых классов (может быть, больше как 100x) 23.05.2012
  • +1 @Neil - Спасибо, это именно то, что я искал! 07.08.2012
  • 780 репов только за добавление this .. Мощь методов расширения :) 17.04.2013
  • Привет @nawfal - ну, в то время в коде Килхоффера тоже была ошибка, которую у меня не хватило кармы исправить. Но да, мой лучший вклад на данный момент - это тривиальность. 01.05.2013
  • это банально. не каламбур, хе-хе-хе 05.08.2015
  • Я написал метод DeepClone на основе ответа Нила. Он отлично работал, пока я не вызвал DeepClone для объекта, класс которого был определен в «плагине» ... сборке, которую я загрузил программно. Это привело к возникновению исключения SerializationException с ошибкой «Не удалось найти сборку xxx». Я решил эту проблему с помощью решения, которое я разместил здесь 14.02.2019

  • 4

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

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

    Вот результат кода, показывающий относительную разницу в производительности (4,77 секунды для глубоко вложенного MemberwiseCopy против 39,93 секунды для сериализации). Использование вложенного MemberwiseCopy почти так же быстро, как копирование структуры, а копирование структуры чертовски близко к теоретической максимальной скорости, на которую способен .NET, что, вероятно, довольно близко к скорости того же самого в C или C ++ (но было бы необходимо выполнить некоторые эквивалентные тесты, чтобы проверить это утверждение).

        Demo 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 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 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 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", sw.Elapsed, total);
            }
            {               
                Console.Write("Demo 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", sw.Elapsed, total);
            }
            {
                Console.Write("Demo 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 для создания полного клона. Это просто: всего несколько строк, см. Демонстрационный код выше.

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

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

    Обновить

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

    Обновить

    Цитата из независимого теста скорости (см. Комментарии ниже):

    Я провел свой собственный тест скорости, используя метод расширения сериализации / десериализации Нила, Nested MemberwiseClone Contango, метод расширения на основе отражения Алекса Бурцева и AutoMapper, по 1 миллиону раз каждый. Сериализация-десериализация была самой медленной, занимая 15,7 секунды. Затем появился AutoMapper, занимавший 10,1 секунды. Намного быстрее оказался метод, основанный на отражении, который занял 2,4 секунды. Безусловно, самым быстрым был Nested MemberwiseClone, занимавший 0,1 секунды. Все сводится к производительности, а не к необходимости добавления кода в каждый класс для его клонирования. Если производительность не является проблемой, используйте метод Алекса Бурцева. - Саймон Тевси

    30.12.2011
  • Хороший пост. Есть идеи, почему сериализация намного медленнее? Кроме того, как будет работать ваша контрольная сумма? Почему бы просто не использовать средство проверки равенства? 01.04.2012
  • @ user420667 Контрольная сумма работает путем ручного преобразования всех параметров в классе в целые числа, а затем складывания указанных целых чисел для создания контрольной суммы. Это полезно, если вы загружаете копии объектов в очередь в одном потоке и считываете их другим потоком. Вам понадобятся методы: ChecksumWrite и ChecksumVerify. 20.05.2012
  • @ user420667 Можно также использовать средство проверки равенства, но только если у вас есть что-то еще для сравнения. Если вы загружаете предметы в очередь, как вы определяете, действительны ли предметы, выходящие на другой конец? Средство проверки равенства не будет работать, тогда как контрольная сумма будет проверять внутреннюю согласованность. Как только будет доказано, что код работает, скажем, со 100 миллионами элементов в очереди за пару недель развертывания, вы можете быть уверены, что код написан надежно, и вы можете удалить контрольную сумму. 20.05.2012
  • Могу подтвердить, что это намного быстрее, чем метод сериализации. Стоимость: написание большего количества кода; риск обслуживания при добавлении поля без добавления его в метод клонирования; необходимо написать вспомогательные классы для любых сторонних классов (например, Dictionary ‹›) 23.05.2012
  • @Gravitas: А, ладно. Полагаю, я представлял item1 по сравнению с item2, поэтому казалось излишним делать item1.GetChecksum () только для сравнения его с item2.GetChecksum (), что потребовало бы больше операций, чем простое сравнение на равенство. Но если вы позже сравните его только с самим собой ... я не знаю, это звучит как невыполнимая задача. Моя ChecksumVerify, вероятно, вызовет GetChecksum (), так что они почти наверняка будут совпадать. 23.05.2012
  • @Gravitas: Я думаю, вы беспокоитесь о целостности данных в какой-то зашумленной линии передачи, верно? 23.05.2012
  • Вы можете создать метод расширения, который работает с любым объектом, см. Мой ответ stackoverflow.com/a/11308879/235715 12.07.2012
  • @ user420667. Нет, меня больше беспокоит неправильное клонирование объектов, которое приводит к ошибкам (см. Мой предпоследний пункт в моем ответе выше). 28.01.2013
  • Жаль, что ни Java, ни .NET не различают ссылки, инкапсулирующие идентичность, изменяемое состояние, и то, и другое, или ни то, ни другое. По идее, должен быть только один тип клона: новый объект, каждая ссылка которого инкапсулирует то же самое, что и соответствующая ссылка в оригинале. Если ссылка инкапсулирует идентичность, ссылка клона должна ссылаться на тот же объект. Если он инкапсулирует изменяемое состояние, но не идентичности, клон должен получить ссылку на другой объект с тем же состоянием [в противном случае обе ссылки будут ошибочными ... 23.09.2013
  • ... инкапсулируют личность, а также состояние]. Ссылку на объект, которая инкапсулирует как личность, так и состояние, нельзя клонировать, кроме как путем копирования всего остального , содержащего ссылку на этот объект - подвиг, который часто бывает трудным или невозможным. Хотя ссылки на некоторые типы объектов обычно используются для инкапсуляции идентичности, а ссылки на другие обычно инкапсулируют изменяемое состояние, знания типа объекта недостаточно для цели, для которой хранится ссылка. 24.09.2013
  • Я провел свой собственный тест скорости, используя метод расширения сериализации / десериализации Нила, Nested MemberwiseClone Contango, метод расширения на основе отражения Алекса Бурцева и AutoMapper, по 1 миллиону раз каждый. Сериализация-десериализация была самой медленной, занимая 15,7 секунды. Затем появился AutoMapper, занимавший 10,1 секунды. Намного быстрее оказался метод, основанный на отражении, который занял 2,4 секунды. Безусловно, самым быстрым был Nested MemberwiseClone, занимавший 0,1 секунды. Все сводится к производительности, а не к необходимости добавления кода в каждый класс для его клонирования. Если производительность не является проблемой, используйте метод Алекса Бурцева. 07.06.2016
  • Как NestedMemberwiseClone обрабатывает циклические ссылки? 26.06.2019
  • @vargonian Нет циклических ссылок, если кто-то проектирует структуру данных без них. Как правило, структуры данных POCO простые, вложенные в 2 (или, возможно, 3) уровня. 12.09.2019
  • this.Purchase.ShallowCopy(). Это не имеет смысла, если самой Purchase требуется DeepCopy. Правильный шаблон для истинного DeepCopy - вызывать DeepCopy для каждого непримитивного поля. Любой тип, содержащий только примитивы, просто реализует DeepCopy как ShallowCopy (Person)this.MemberwiseClone(). В DeepCopy родительский не знает, является ли каждый непримитивный дочерний элемент глубоким или мелким, поэтому он должен вызвать DeepCopy для дочернего элемента. Ответственность ребенка - продолжать глубоко или делать мелкие. Чтобы избежать ошибок, лучше всегда вызывать DeepCopy. Пусть ребенок сам отвечает за себя. 12.03.2021
  • @ToolmakerSteve Верно. Также имейте в виду, что это только демонстрационный код, в основном направленный на то, чтобы показать разницу между глубокими и мелкими копиями. В производственном коде также рекомендуется, чтобы все называлось DeepCopy прямо в стеке, даже если оно выполняет только неглубокую копию. К счастью, это проблема с самоограничением: наличие метода ShallowCopy вряд ли приведет к возникновению каких-либо ошибок, поскольку название дает понять, что он делает, и его можно отрефакторировать для добавления DeepCopy по запросу. В конце концов, этот код абсолютно надежен и испытан во многих успешных проектах за последние 10 лет. 12.03.2021

  • 5

    Я считаю, что подход BinaryFormatter относительно медленный (что стало для меня неожиданностью!). Вы можете использовать ProtoBuf .NET для некоторых объектов, если они соответствуют требованиям ProtoBuf. Со страницы «Приступая к работе с ProtoBuf» (http://code.google.com/p/protobuf-net/wiki/GettingStarted):

    Примечания к поддерживаемым типам:

    Пользовательские классы, которые:

    • Помечены как данные-контракт
    • Имейте конструктор без параметров
    • Для Silverlight: общедоступны
    • Многие общие примитивы и т. Д.
    • Одномерные массивы измерений: T []
    • Список ‹T> / IList ‹T>
    • Словарь ‹TKey, TValue> / IDictionary‹ TKey, TValue>
    • любой тип, который реализует IEnumerable ‹T> и имеет метод Add (T)

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

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

    public static void deepCopy<T>(ref T object2Copy, ref T objectCopy)
    {
        using (var stream = new MemoryStream())
        {
            Serializer.Serialize(stream, object2Copy);
            stream.Position = 0;
            objectCopy = Serializer.Deserialize<T>(stream);
        }
    }
    

    Что действительно ОЧЕНЬ быстро ...

    Изменить:

    Вот рабочий код для модификации этого (протестирован на .NET 4.6). Он использует System.Xml.Serialization и System.IO. Нет необходимости отмечать классы как сериализуемые.

    public void DeepCopy<T>(ref T object2Copy, ref T objectCopy)
    {
        using (var stream = new MemoryStream())
        {
            var serializer = new XS.XmlSerializer(typeof(T));
    
            serializer.Serialize(stream, object2Copy);
            stream.Position = 0;
            objectCopy = (T)serializer.Deserialize(stream);
        }
    }
    
    03.01.2012
  • Интересно, насколько быстро это сравнивается с ответом Nested MemberwiseClone выше? 25.09.2013
  • это не сработает, если в вашем классе есть словарь, который необходимо скопировать, поскольку IDictionary нельзя сериализовать 15.11.2019

  • 6

    Вы можете попробовать это

        public static object DeepCopy(object obj)
        {
            if (obj == null)
                return null;
            Type type = obj.GetType();
    
            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(DeepCopy(array.GetValue(i)), i);
                }
                return Convert.ChangeType(copied, obj.GetType());
            }
            else if (type.IsClass)
            {
    
                object toret = Activator.CreateInstance(obj.GetType());
                FieldInfo[] fields = type.GetFields(BindingFlags.Public |
                            BindingFlags.NonPublic | BindingFlags.Instance);
                foreach (FieldInfo field in fields)
                {
                    object fieldValue = field.GetValue(obj);
                    if (fieldValue == null)
                        continue;
                    field.SetValue(toret, DeepCopy(fieldValue));
                }
                return toret;
            }
            else
                throw new ArgumentException("Unknown type");
        }
    

    Спасибо DetoX83 статье о проекте кода.

    01.04.2012
  • Это работает, только если у вашего объекта есть конструктор по умолчанию! 23.08.2014

  • 7

    Лучший способ:

        public interface IDeepClonable<T> where T : class
        {
            T DeepClone();
        }
    
        public class MyObj : IDeepClonable<MyObj>
        {
            public MyObj Clone()
            {
                var myObj = new MyObj();
                myObj._field1 = _field1;//value type
                myObj._field2 = _field2;//value type
                myObj._field3 = _field3;//value type
    
                if (_child != null)
                {
                    myObj._child = _child.DeepClone(); //reference type .DeepClone() that does the same
                }
    
                int len = _array.Length;
                myObj._array = new MyObj[len]; // array / collection
                for (int i = 0; i < len; i++)
                {
                    myObj._array[i] = _array[i];
                }
    
                return myObj;
            }
    
            private bool _field1;
            public bool Field1
            {
                get { return _field1; }
                set { _field1 = value; }
            }
    
            private int _field2;
            public int Property2
            {
                get { return _field2; }
                set { _field2 = value; }
            }
    
            private string _field3;
            public string Property3
            {
                get { return _field3; }
                set { _field3 = value; }
            }
    
            private MyObj _child;
            private MyObj Child
            {
                get { return _child; }
                set { _child = value; }
            }
    
            private MyObj[] _array = new MyObj[4];
        }
    
    22.04.2012
  • Используя CGbR Clone Generator, вы получите тот же результат без написания кода вручную. 09.06.2016

  • 8

    Может быть, вам нужна только неглубокая копия, в этом случае используйте Object.MemberWiseClone().

    В документации на MemberWiseClone() есть хорошие рекомендации по стратегиям для глубокого копирования: -

    http://msdn.microsoft.com/en-us/library/system.object.memberwiseclone.aspx

    01.12.2010
  • Хорошая попытка, но он специально попросил глубокий клон. 26.09.2011
  • Вы можете сделать глубокий клон с помощью MemberwiseClone, все, что вам нужно сделать, это добавить вложение. См. Ответ @Gravitas выше. 30.12.2011

  • 9
        public static object CopyObject(object input)
        {
            if (input != null)
            {
                object result = Activator.CreateInstance(input.GetType());
                foreach (FieldInfo field in input.GetType().GetFields(Consts.AppConsts.FullBindingList))
                {
                    if (field.FieldType.GetInterface("IList", false) == null)
                    {
                        field.SetValue(result, field.GetValue(input));
                    }
                    else
                    {
                        IList listObject = (IList)field.GetValue(result);
                        if (listObject != null)
                        {
                            foreach (object item in ((IList)field.GetValue(input)))
                            {
                                listObject.Add(CopyObject(item));
                            }
                        }
                    }
                }
                return result;
            }
            else
            {
                return null;
            }
        }
    

    Этот способ в несколько раз быстрее, чем BinarySerialization, И для этого не требуется атрибут [Serializable].

    04.07.2011
  • Вы не продолжаете глубокое копирование своей ветки, отличной от IList, и я думаю, у вас могут возникнуть проблемы с ICollection / IEnumerable. 05.07.2011
  • Использование техники Nested MemberwiseClone снова на порядок быстрее (см. Мой пост под @Gravitas). 02.01.2012
  • Что такое Consts.AppConsts.FullBindingList? 24.04.2018

  • 10

    Документация MSDN, похоже, намекает, что Clone должен выполнять глубокую копию, но это никогда явно не указывается:

    Интерфейс ICloneable содержит один член, Clone, который предназначен для поддержки клонирования сверх того, что предоставляется MemberWiseClone ... Метод MemberwiseClone создает неглубокую копию ...

    Вы можете найти мой пост полезным.

    http://pragmaticcoding.com/index.php/cloning-objects-in-c/

    13.02.2013
  • Проблема с ICloneable заключается в том, что метод Clone явно не указывает, выполняет ли он поверхностное или глубокое копирование, поэтому вызывающие абоненты никогда не могут быть уверены. Следовательно, есть [обсуждение | blogs.msdn.com /brada/archive/2004/05/03/125427.aspx] о том, чтобы сделать ICloneable устаревшим в .NET Framework. 08.08.2016
  • Новые материалы

    Кластеризация: более глубокий взгляд
    Кластеризация — это метод обучения без учителя, в котором мы пытаемся найти группы в наборе данных на основе некоторых известных или неизвестных свойств, которые могут существовать. Независимо от..

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

    Частный метод Python: улучшение инкапсуляции и безопасности
    Введение Python — универсальный и мощный язык программирования, известный своей простотой и удобством использования. Одной из ключевых особенностей, отличающих Python от других языков, является..

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

    Работа с векторными символическими архитектурами, часть 4 (искусственный интеллект)
    Hyperseed: неконтролируемое обучение с векторными символическими архитектурами (arXiv) Автор: Евгений Осипов , Сачин Кахавала , Диланта Хапутантри , Тимал Кемпития , Дасвин Де Сильва ,..

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

    Обеспечение масштабируемости LLM: облачный анализ с помощью AWS Fargate и Copilot
    В динамичной области искусственного интеллекта все большее распространение получают модели больших языков (LLM). Они жизненно важны для различных приложений, таких как интеллектуальные..