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

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

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

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

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

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

59 ответов

Решение

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Теперь рассмотрите возможность получения класса от Person.

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

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

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

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

Результат будет:

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

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

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

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

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

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

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

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

var newInstance = source.GetClone();

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

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

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

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

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

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

Как быстро это?

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

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

и больше...

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

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

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


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

}

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

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

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

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

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

    return target;
}

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

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

MyObject oldObj; // The existing object to clone

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

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

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

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

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

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

var copy = anyObject.DeepClone();

Мы можем иметь следующие три реализации:

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

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

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

static public class SerializeHelper
{
    //Michael White, Holly Springs Consulting, 2009
    //michael@hollyspringsconsulting.com
    public static T DeserializeXML<T>(string xmlData) where T:new()
    {
        if (string.IsNullOrEmpty(xmlData))
            return default(T);

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

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

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

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

        return sbTR.ToString();
    }

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

Быстрый, легкий, эффективный пакет Nuget для решения проблемы клонирования

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

https://github.com/force-net/DeepCloner

Немного проработав его readme, вот причина, по которой мы выбрали его на работе:

Отказ от ответственности - требования:

  • .NET 4.0 или выше или.NET Standard 1.3 (.NET Core)
  • Требуется набор разрешений "Полное доверие" или "Отражение" (MemberAccess)
  • Это может глубокая или мелкая копия
  • При глубоком клонировании сохраняется весь граф объектов.
  • Использует генерацию кода во время выполнения, так как клонирование результатов происходит невероятно быстро
  • Объекты, скопированные внутренней структурой, никакие методы или вызванные ctors
  • Вам не нужно как-то маркировать классы (например, Serializable-attribute или реализовывать интерфейсы).
  • Нет необходимости указывать тип объекта для клонирования. Объект может быть приведен к интерфейсу или как абстрактный объект (например, вы можете клонировать массив целых чисел как абстрактный Array или IEnumerable; даже нуль можно клонировать без каких-либо ошибок).
  • Клонированный объект не имеет никакой возможности определить, является ли он клоном (за исключением очень специфических методов)

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

При использовании метода расширения три строки становятся одной строкой:

MyType copy = source.Copy();

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

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

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

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

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

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

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

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

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

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

var clone = ObjectCloner.DeepClone(original);

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

var clone = original.DeepClone();

Представление

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

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

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

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

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

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

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

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

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

Более того, используйте общий клонер List:

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

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

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

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

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

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

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

И метод...

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Обновить

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

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

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

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

NewObject = OldObject.Clone();

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

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

https://github.com/kalisohn/CloneBehave

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

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

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

  public string Name { get; set; }

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

  public Person Manager { get; set; }
}

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

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

Address adrClone = adr.Clone();

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

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

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

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

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

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

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

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

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

    public Partial[] Partials { get; set; }

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

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

    private Root()
    {
    }
} 

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

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

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

private static MyObj DeepCopy(MyObj source)
        {

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

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

        }

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

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

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

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

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

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

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

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

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

    using System.Linq;
    using System.Reflection;

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

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

В кодовой базе, с которой я работаю, у нас была копия файла из проекта GitHub Burtsev-Alexey/net-object-deep-copy. Это 9 лет. Это сработало, хотя позже мы поняли, что для больших структур объектов это было очень медленно.

Вместо этого мы нашли форк файлаObjectExtensions.csв проекте GitHub jpmikkers/Baksteen.Extensions.DeepCopy. Операция глубокого копирования большой структуры данных, которая раньше занимала у нас около 30 минут, теперь кажется почти мгновенной.

Эта улучшенная версия имеет следующую документацию:

Метод расширения C# для быстрого клонирования объектов.

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

Ускорение достигается с помощью следующих методов:

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

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

       using Baksteen.Extensions.DeepCopy;
...
var myobject = new SomeClass();
...
var myclone = myobject.DeepCopy()!;    // creates a new deep copy of the original object 

Примечание: восклицательный знак (оператор, допускающий нулевое значение) требуется только в том случае, если вы включили в своем проекте ссылочные типы, допускающие нулевое значение.

Кратчайший способ, но нужна зависимость:

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

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

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

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

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