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

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

Что делает глубокая копия? Копирует ли он битовый поток?

SpoiledTechie.com 24.09.2008 23:41

Глубокая копия - это то, что копирует КАЖДОЕ поле объекта. Неглубокая копия создаст только новый объект и укажет все поля на оригинал.

swilliams 24.09.2008 23:46

Глубокая копия создает второй экземпляр объекта с теми же значениями. Неглубокая копия (упрощенная) подобна созданию второй ссылки на объект.

Michael Blackburn 30.03.2011 23:49

Фреймворк для копирования / клонирования объектов .NET: github.com/havard/copyable

jgauffin 18.02.2011 16:14

Используйте Mapper, я предлагаю UltraMapper github.com/maurosampietro/UltraMapper

Mauro Sampietro 23.04.2017 12:15

проверьте этот пост: stackoverflow.com/a/52490699/1404642

Siavash 25.09.2018 08:55

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

Arnold Vakaria 26.01.2021 22:05
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
627
7
496 953
11
Перейти к ответу Данный вопрос помечен как решенный

Ответы 11

Ответ принят как подходящий

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

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;
    

Что произойдет, если у объекта есть событие. Они потеряли все из-за сериализации?

Patrick Desjardins 24.09.2008 23:56

Подписки на события включаются в граф сериализации, поскольку BinaryFormatter использует поля через отражение, а события - это просто поля типов делегатов плюс методы добавления / удаления / вызова. Вы можете использовать [field: NonSerialized] в событии, чтобы этого избежать.

Ilya Ryzhenkov 25.09.2008 00:16

Что это за необъявленная «потоковая» переменная? Или это что-то только на C#, а не на VB.NET? Я преобразовал все, кроме этой переменной.

HardCode 19.03.2009 23:41

мс местоположение или мс положение? или это как-то связано с версией Framework?

dr. evil 30.03.2009 01:08

Как пометить что-то как [Serializable] ??

Saeid Yazdani 11.04.2011 14:22

@ Sean87: над объявлением класса добавьте [Serializable]. поэтому [Serializable]public class Foo { } сделает Foo помеченным как сериализуемый.

Dan Atkinson 04.08.2011 02:51

Рекурсивный MemberwiseClone также выполняет глубокое копирование, он работает в 3 раза быстрее, чем BinaryFormatter, не требует конструктора по умолчанию или каких-либо атрибутов. Смотрите мой ответ: stackoverflow.com/a/11308879/235715

Alex Burtsev 12.07.2012 08:19

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

v.oddou 21.05.2013 07:13

Чтобы программно обработать вашу первую заметку, вы можете немного лучше обработать исключение, используя следующее: if (!typeof(T).IsSerializable) { throw new ArgumentException("Type {0} is not serializable",typeof(T).Name); }

KyleMit 16.07.2013 20:08

как бы вы ответили на -> эта очевидная проблема утечки памяти с Deserialize ()?

Code Jockey 06.03.2014 23:27

Более полезно использовать var formatter = new BinaryFormatter {Context = new StreamingContext (StreamingContextStates.Clone)};

Chris Ward 27.11.2014 11:06

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

Bogdan 25.04.2015 05:17

К тому же он очень медленный.

Toxantron 09.06.2016 23:48

Для тех, кому интересно, посмотрите это простое решение: stackoverflow.com/questions/222598/…

F.H. 09.11.2016 16:56

ms.Position = 0 спас меня! Без этого происходили действительно странные вещи.

GDS 06.02.2017 06:47

Красивый! Это было прямо у меня под носом, и я этого даже не заметил!

RiA 18.07.2018 15:54

Спасибо за ответ. Меня устраивает!

Maksym Labutin 31.10.2018 17:53

Я знаю, что этот пост старый, но он по-прежнему пользуется успехом при поиске глубокого клонирования. Обратите внимание, что, согласно Microsoft (aka.ms/binaryformatter), это решение больше не рекомендуется, поскольку оно небезопасно.

pwnell 23.11.2020 22:36

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

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

К этому добавьте «общедоступный статический T DeepClone <T> (это T a), где T: ISerializable»

Amir Rezaei 11.02.2011 17:35

@Amir - классу не обязательно реализовывать ISerializable, достаточно пометки с помощью SerializableAttribute. Атрибут использует отражение для выполнения сериализации, а интерфейс позволяет вам написать собственный сериализатор.

Neil 14.02.2011 19:10

Я согласен с вашим утверждением, но мне нравится предложение Амира, поскольку оно обеспечивает проверку во время компиляции. Есть ли способ примирить их?

Michael Blackburn 30.03.2011 23:47

Пройден модульный тест var stringbuilder = new StringBuilder ("TestData"); var copy = stringbuilder.DeepClone (); Assert.IsFalse (Equals (построитель строк, копия)); Большое спасибо.

om471987 06.05.2012 22:17

@Neil Этот метод в 10 раз медленнее, чем метод NestedMemberwiseClone, см. Мой пост на этой странице.

Contango 20.05.2012 22:02

Привет, @Gravitas. Неудивительно, что методы, написанные вручную, могут превзойти решение, использующее отражение. Было бы интересно сравнить производительность вашего решения с методом сериализации с ручным кодированием (т.е. реализацией ISerializable).

Neil 23.05.2012 17:50

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

Neil 23.05.2012 18:34

+1 @Neil - Спасибо, это именно то, что я искал!

Mark Kram 07.08.2012 23:25

780 rep только за добавление this .. Сила методов расширения :)

nawfal 17.04.2013 23:39

Привет, @nawfal - ну, в то время в коде Килхоффера тоже была ошибка, которую мне не хватало кармы исправить. Но да, мой лучший вклад на данный момент - это тривиальность.

Neil 01.05.2013 12:29

это банально. нет каламбура, хе-хе

David 05.08.2015 12:55

Я написал метод DeepClone на основе ответа Нила. Он отлично работал, пока я не вызвал DeepClone для объекта, класс которого был определен в «плагине» ... сборке, которую я загрузил программно. Это привело к возникновению исключения SerializationException с ошибкой «Невозможно найти сборку xxx». Я решил эту проблему с помощью решения, которое я опубликовал здесь

Brad Oestreicher 14.02.2019 18:44

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

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

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

Хорошая попытка, но он специально попросил глубокий клон.

Levitikon 26.09.2011 21:38

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

Contango 30.12.2011 23:22

    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].

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

Rob McCready 05.07.2011 11:04

Использование техники «Nested MemberwiseClone» снова на порядок быстрее (см. Мой пост под @Gravitas).

Contango 02.01.2012 03:29

Что такое Consts.AppConsts.FullBindingList?

DmitryBoyko 24.04.2018 10:10

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

Обратите внимание, что если вы используете Вложенный 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();
    }

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

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

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

Обновлять

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

Обновлять

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

I've run my own speed test using Neil's serialize/deserialize extension method, Contango's Nested MemberwiseClone, Alex Burtsev's reflection-based extension method and AutoMapper, 1 million times each. Serialize-deserialize was slowest, taking 15.7 seconds. Then came AutoMapper, taking 10.1 seconds. Much faster was the reflection-based method which took 2.4 seconds. By far the fastest was Nested MemberwiseClone, taking 0.1 seconds. Comes down to performance versus hassle of adding code to each class to clone it. If performance isn't an issue go with Alex Burtsev's method. – Simon Tewsi

Хороший пост. Есть идеи, почему сериализация намного медленнее? Кроме того, как будет работать ваша контрольная сумма? Почему бы просто не использовать средство проверки равенства?

user420667 01.04.2012 19:33

@ user420667 Контрольная сумма работает путем ручного преобразования всех параметров в классе в целые числа, а затем складывания указанных целых чисел для создания контрольной суммы. Это полезно, если вы загружаете копии объектов в очередь в одном потоке и считываете их другим потоком. Вам понадобятся методы: ChecksumWrite и ChecksumVerify.

Contango 20.05.2012 21:59

@ user420667 Можно также использовать средство проверки равенства, но только если у вас есть что-то еще для сравнения. Если вы загружаете вещи в очередь, как вы определяете, действительны ли предметы, выходящие на другой конец? Средство проверки равенства не будет работать, тогда как контрольная сумма будет проверять внутреннюю согласованность. После того, как будет доказано, что код работает, скажем, со 100 миллионами элементов в очереди за пару недель развертывания, вы можете быть уверены, что код написан надежно, и вы можете удалить контрольную сумму.

Contango 20.05.2012 22:01

Могу подтвердить, что это намного быстрее, чем метод сериализации. Стоимость: написание большего количества кода; риск обслуживания при добавлении поля без добавления его в метод клонирования; необходимо написать вспомогательные классы для любых сторонних классов (например, Dictionary <>)

Neil 23.05.2012 18:37

@Gravitas: А, ладно. Полагаю, я представлял item1 по сравнению с item2, поэтому казалось излишним делать item1.GetChecksum () только для сравнения его с item2.GetChecksum (), что потребовало бы больше операций, чем простое сравнение на равенство. Но если позже вы сравните его только с самим собой ... я не знаю, это звучит как невыполнимая задача. Моя ChecksumVerify, вероятно, вызовет GetChecksum (), поэтому почти наверняка они будут совпадать.

user420667 23.05.2012 20:51

@Gravitas: Полагаю, вас беспокоит целостность данных в какой-то зашумленной линии передачи, верно?

user420667 23.05.2012 20:55

Вы можете создать метод расширения, который работает с любым объектом, см. Мой ответ stackoverflow.com/a/11308879/235715

Alex Burtsev 12.07.2012 08:16

@ user420667. Нет, меня больше беспокоит неправильное клонирование объектов, которое приводит к ошибкам (см. Мой предпоследний пункт в моем ответе выше).

Contango 28.01.2013 23:03

Жаль, что ни Java, ни .NET не различают ссылки, которые инкапсулируют идентичность, изменяемое состояние, и то, и другое, или ни то, ни другое. По идее, должен быть только один тип «клона»: новый объект, каждая ссылка которого инкапсулирует то же самое, что и соответствующая ссылка в оригинале. Если ссылка инкапсулирует идентичность, ссылка клона должна относиться к объекту такой же. Если он инкапсулирует изменяемое состояние, но идентификатор нет, клон должен получить ссылку на другой объект с тем же состоянием [в противном случае обе ссылки будут ошибочными ...

supercat 23.09.2013 23:53

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

supercat 24.09.2013 00:01

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

Simon Tewsi 07.06.2016 18:12

Как Nested MemberwiseClone обрабатывает циклические ссылки?

vargonian 26.06.2019 01:58

@vargonian Нет циклических ссылок, если кто-то проектирует структуру данных без них. Как правило, структуры данных POCO простые, вложенные в 2 (или, возможно, 3) уровня.

Contango 12.09.2019 22:29

this.Purchase.ShallowCopy(). Это не имеет смысла, если самой Purchase требуется DeepCopy. Правильный шаблон для истинного DeepCopy - вызывать DeepCopy для каждого непримитивного поля. Любой тип, содержащий только примитивы, просто реализует DeepCopy как ShallowCopy (Person)this.MemberwiseClone(). В DeepCopy родительский не знает, является ли каждый непримитивный ребенок глубоким или мелким, поэтому он должен вызывать DeepCopy для дочернего элемента. Ответственность ребенка - продолжать глубокое или мелкое. Чтобы избежать ошибок, лучше всегда вызвать DeepCopy. Пусть ребенок сам отвечает за себя.

ToolmakerSteve 12.03.2021 05:14

@ToolmakerSteve Верно. Кроме того, имейте в виду, что это только демонстрационный код, в основном направленный на то, чтобы показать разницу между глубокими и мелкими копиями. В производственном коде я также рекомендовал бы, чтобы все называлось DeepCopy прямо в стеке, даже если оно выполняет только неглубокую копию. К счастью, это проблема с самоограничением: наличие метода ShallowCopy вряд ли приведет к возникновению каких-либо ошибок, поскольку название дает понять, что он делает, и его можно отредактировать для добавления DeepCopy по запросу. В конце концов, этот код абсолютно надежен и испытан во многих успешных проектах за последние 10 лет.

Contango 12.03.2021 11:58

Я считаю, что подход 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);
    }
}

Интересно, насколько быстро это сравнивается с ответом Nested MemberwiseClone выше?

Contango 25.09.2013 00:15

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

Fabio Napodano 15.11.2019 19:36

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

    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 статья за проект кода.

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

Cyrus 23.08.2014 16:50

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

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

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

Toxantron 09.06.2016 23:50

Я написал метод расширения глубокой копии объекта, основанный на рекурсивном "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;
            }
        }
    }

}

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

theJerm 18.02.2013 11:05

@theJerm Я думаю, вы вызвали Clone вместо Copy, есть метод Clone, который использует BinaryFormatter, как описано в принятом ответе для сравнения тестов

Alex Burtsev 18.02.2013 17:42

Спасибо, Алекс, да, мне нужно было вызвать копию, и это сработало!

theJerm 20.02.2013 00:23

Для ReferenceEqualityComparer.GetHashCode(object obj) вы должны использовать RuntimeHelpers.GetHashCode(obj), иначе он будет использовать реальный хэш-код объекта. См. stackoverflow.com/a/11240110/495262

Matt Smith 23.04.2014 20:46

Что касается IsPrimitive: по какой причине вы возвращаете true для строки. Кроме того, есть ли причина, по которой вы используете один & вместо && в заявлении: return (type.IsValueType & type.IsPrimitive);?

Matt Smith 23.04.2014 20:51

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

Matt Smith 23.04.2014 20:54

@MattSmith Вы абсолютно правы насчет GetHashCode, проверил, да исключение StackOverflow - gist.github.com/Burtsev-Alexey/11227277

Alex Burtsev 23.04.2014 22:33

@MattSmith Это сработало для делегатов, но я намеренно отключил его (установив null), см. github.com/Burtsev-Alexey/net-object-deep-copy/issues/7, подписчики были клонированы, в конце концов, если у вас были подключены два объекта A и B (по подписке на событие), вы получили бы объекты A ' и B 'связаны, это правильно, но это не то, чего хотят большинство людей, когда клонируют объекты.

Alex Burtsev 23.04.2014 22:39

@AlexBurtsev, Что касается делегатов - хорошо, я изменил его, чтобы не копировать делегаты, а копировать ссылку (аналогично тому, как вы обрабатываете строки), поскольку (как и строки) делегаты неизменяемы. Подход сериализации действительно обрабатывает делегатов, но я не уверен, есть ли у них проблема, аналогичная той, что вы испытывали с делегатами.

Matt Smith 23.04.2014 22:43

@MattSmith Строки обрабатываются как примитивы, потому что 1: они неизменяемы, 2: вызов защищенного MemberwiseClone в строке приведет к повреждению памяти, строковые данные превратятся в случайные символы, и вскоре среда выполнения .NET выйдет из строя с внутренней ошибкой, заявив, что это но в .NET :-)

Alex Burtsev 23.04.2014 22:45

@MattSmith Я полагаю, вы понимаете, что при повторном использовании одного и того же делегата в исходном и клонированном объекте они будут использовать те же объекты, на которые ссылались в делегате, например, у A есть делегат, который изменяет B, при клонировании A вы получите A ', который изменяет то же самое B, если вы хотите получить истинную копию (снимок) графов объектов (память), просто удалите строку, которая устанавливает делегат в значение null. Тогда вы получите A, изменяющий B, и A, который `` изменяет B ''

Alex Burtsev 23.04.2014 23:19

@AlexBurtsev, Хорошие моменты. То, что вы хотите, в некоторой степени зависит от того, к каким объектам осуществляется доступ в делегате. Если вы обращаетесь к копируемым объектам, вам, вероятно, понадобится скопированный делегат. Если вы обращаетесь к объекту, внешнему по отношению к скопированному объекту, вы, вероятно, хотите, чтобы делегат не копировался. Тем не менее, у меня нет варианта использования для делегатов, поэтому, возможно, лучше пока оставить его нереализованным (т.е. я выброшу исключение).

Matt Smith 23.04.2014 23:23

Кстати, когда я включаю делегатов для моего простого тестового примера, он переполняется. Вот мой образец объекта, который я пытаюсь скопировать: shar.es/T86JR

Matt Smith 23.04.2014 23:43

Я раздвоил и обновил ваш код для совместимости с новым API отражения на основе TypeInfo (используется в приложениях Windows Phone и Windows Store и их соответствующих профилях PCL). github.com/Gameleon12/net-object-deep-copy/blob/master/…

Leon Lucardie 08.10.2014 16:08

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

Dean 15.01.2015 07:25

Еще быстрее (для меня) использовать JSON.NET и сериализовать, а затем десериализовать. Может не работать для всех (я думаю о частных полях и т. д.).

Peter 01.04.2015 12:57

@AlexBurtsev, ваш код, использующий пространство имен System.ArrayExtensions, где я могу его получить?

Alex141 12.04.2015 00:37

@ Alex141 - только что столкнулся с той же проблемой. Весь соответствующий код находится в указанном файле, внизу есть пространство имен ArrayExtensions.

Arunas 15.05.2015 06:24

Это очень умная и мощная реализация, однако вы должны учесть несколько вещей, прежде чем решить, подходит ли она для вашей модели данных. Memberwiseclone() работает так быстро, потому что не вызывает конструкторов. Так что, если ваши конструкторы выполняют тяжелую работу, такую ​​как подписка на события, вам не повезло. Он полагается на копирование частных полей объекта, минуя бизнес-логику в свойствах и методах. Например, я видел, как поле hashCode копируется в коллекцию HashSet, хотя все экземпляры были изменены.

kat 26.05.2015 20:21

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

yu_ominae 27.05.2015 07:57

@yu_ominae циклические ссылки поддерживаются и отлично работают. Ваша проблема в другом месте. Если вы можете поделиться исходным кодом класса, который пытаетесь клонировать, откройте проблему на веб-сайте проекта. Иногда я сталкиваюсь с SO-исключениями при клонировании того, что не должно быть клонировано, например внутренней инфраструктуры .NET, на которую могут случайно ссылаться, особенно указатели и их отражения. Например, попытка клонирования сущности NHibernate, содержащей коллекцию, прикрепленную к сеансу, приведет к большому беспорядку, потому что коллекция NH, реализующая IList <T>, содержит внутри поле DbConnection.

Alex Burtsev 27.05.2015 12:41

@AlexBurtsev Я пытался клонировать объекты коллекции, отражающие объекты в другом приложении. Доступ осуществляется через com-вызовы, так что, возможно, у меня есть ссылка, которая вызывает взрыв. Метод .Copy() работал нормально, если я установил точку останова и проверил содержимое списка в часах перед возобновлением выполнения. Странный. Я обнаружил, что звонок на .ToList() действительно дает то, что я хочу сейчас, так что все в порядке. Спасибо за ответ :)

yu_ominae 27.05.2015 13:59

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

David 11.03.2016 12:56

@ Дэвид Не из коробки.

Alex Burtsev 12.03.2016 08:58

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

g.t.w.d 07.12.2016 19:44

Привет, Алекс, спасибо за расширение. Это также события / делегаты DeepCopy?

Deepak 05.01.2017 15:39

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

Wobbles 13.07.2017 15:31

Ни единого комментария во всем. ВАУ.

dannyhut 23.12.2018 09:35

По-прежнему можно изменять вложенные списки :(

judehall 19.03.2019 00:50

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

TakeMeAsAGuest 24.03.2019 21:31

Предупреждение: такой подход тормозит XDocuments. Я больше не могу получить доступ к атрибутам по имени, даже если пространство имен не определено.

Darek 27.03.2019 18:47

Похоже, что List <BsonElement> неправильно отражается в fieldInfo. Из: List<BsonElement> { BsonElement ("status=Connected"), BsonElement("lastConnectedTimeUtc=2019-05-15T10:50:40.557093‌​2Z") } делает: BsonElement[] { BsonElement ("status=Connected"), BsonElement("lastConnectedTimeUtc=2019-05-15T10:50:40.557093‌​2Z"), BsonElement(" = "), BsonElement(" = ") }

Jakub Dropia 15.05.2019 18:42

Я использовал это в сочетании с fluentassertions, чтобы создать клон объекта, а затем проверить originalObject.Should (). BeEquivalentTo (objectClone). Однако это не удается, если объект содержит структуры Noda Time. Решением было добавить в InternalCopy такую ​​строку: if (typeToReflect == typeof(LocalDate) || typeToReflect == typeof(OffsetDateTime)) return originalObject;.

Ralph Gonzalez 10.03.2021 01:39

@ g.t.w.d - "насколько это глубокое или мелкое". Если это остановился после вызова MemberwiseClone, то оно будет мелким. Он использует это в качестве первого шага, а затем использует информацию об отражении для внесения некоторых рекурсивных изменений в клон. Это превращает мелкий клон в глубокий. (Я предполагаю, что причина первоначального создания MemberwiseClone заключается в том, чтобы избежать необходимости обрабатывать поля, которые на самом деле являются неглубокими - типы полей, которые не нуждаются в рекурсии.)

ToolmakerSteve 12.03.2021 05:05

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

ToolmakerSteve 12.03.2021 05:28

У меня есть идея попроще. Используйте LINQ с новым выбором.

public class Fruit
{
  public string Name {get; set;}
  public int SeedCount {get; set;}
}

void SomeMethod()
{
  List<Fruit> originalFruits = new List<Fruit>();
  originalFruits.Add(new Fruit {Name = "Apple", SeedCount=10});
  originalFruits.Add(new Fruit {Name = "Banana", SeedCount=0});

  //Deep Copy
  List<Fruit> deepCopiedFruits = from f in originalFruits
              select new Fruit {Name=f.Name, SeedCount=f.SeedCount};
}

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

Nelson Rothermel 04.02.2013 23:32

Присвоение справочника Name = f.Name - это поверхностное копирование.

Tarec 05.02.2014 14:35

если у нас есть 100 свойств, то весь наш день будет потрачен на написание такого кода для одного объекта и так далее ...

Umar Abbas 10.04.2014 09:49

@Tarec, если Name является строкой, а строки неизменяемы, это не имеет значения.

Arturo Torres Sánchez 20.05.2015 20:58

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

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

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

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

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

Mahmoud Samy 08.08.2016 21:54

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