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

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

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

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

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

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

30 ответов


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

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

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

/// <summary>
/// Reference Article http://www.codeproject.com/KB/tips/SerializedObjectCloner.aspx
/// Provides a method for performing a deep copy of an object.
/// Binary Serialization is used to perform the copy.
/// </summary>
public static class ObjectCopier
{
    /// <summary>
    /// Perform a deep Copy of the object.
    /// </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 serializable, то этот метод сделает трюк. Это не требует модификации или реализации интерфейсов в клонированном классе, просто сериализатор 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 is не потому что у него нет общего интерфейса. причина не использовать его, потому что это смутные. Не ясно, получаете ли вы мелкую или глубокую копию; это зависит от исполнителя.

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

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


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

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

лучшие что нужно сделать для клонирования объектов в Си-Шарп!

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

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

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

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

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

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

копирование объекта путем указания New с последующим именем класса часто приводит к коду, который не является расширяемым. Используя клон, применение картины прототипа, a лучший способ достичь этого. Однако использование clone, как это предусмотрено в 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 ) } );
        }
    };
}

Ну я был возникли проблемы с использованием ICloneable в Silverlight, но мне понравилась идея seralization, я могу seralize 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);
    }
}

Если вы уже используете стороннее приложение, например 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.


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

как его использовать?

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

var newInstance = source.GetClone();

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

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

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

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

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

  • значения полей public, а не readonly
  • значения общедоступных свойств с помощью методов доступа 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>?


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

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

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

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


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

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

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

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


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

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

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

var copy = anyObject.DeepClone();

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

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

все связанные методы хорошо работают и были глубоко протестированы.


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

Ура.


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

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

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, который делает это, но это невозможно, из-за выхода не работает внутри анонимных блоков метода.

еще лучше, используйте generic List cloner:

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

держите вещи простыми и используйте 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();

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

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


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


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

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

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

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

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

10x быстрее, чем другие методы

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

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

и метод ...

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

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

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

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

использование вложенного 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;
    }
}

затем вызовите демо из main:

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

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

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

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

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

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

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

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

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

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

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

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

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

обновление

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


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

https://github.com/kalisohn/CloneBehave

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

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

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

  public string Name { get; set; }

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

  public Person Manager { get; set; }
}

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

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

Address adrClone = adr.Clone();

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

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

private static MyObj DeepCopy(MyObj source)
        {

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

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

        }

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


Мне нравятся такие Copyconstructors:

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

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


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

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

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

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

    public Partial[] Partials { get; set; }

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

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

    private Root()
    {
    }
} 

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

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


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

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

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

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

редактировать: требуется

    using System.Linq;
    using System.Reflection;

вот как я его использовал

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

выполните следующие действия:

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

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


Я думаю, что вы можете попробовать это.

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

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

требует Система, Система.ИО, система.Во время выполнения.Сериализация, Система.Во время выполнения.Сериализация.Форматеры.двойная система.В XML;

public static class ObjectCopier
{

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

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

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


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

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

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


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

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

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


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

} 

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

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

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

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

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

yourClass newLine = oldLine.DeepCopy();

надеюсь, что это помогает.


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

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

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

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

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

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

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


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

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

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

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