Вопрос

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

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

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

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

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

Это было полезно?

Решение

Хотя стандартной практикой является внедрение 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].(Примечание: @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 метод, который делает именно то, что я хочу.

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

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

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

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

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

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

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

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

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

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

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

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, но мне понравилась идея сериализации, я могу сериализовать 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);
    }
}

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

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

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

var newInstance = source.GetClone();

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

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

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

  • Примитива (int, uint, байт, двойной, чар и т. Д.), Известные неизвестные типы (DateTime, Timesspan, String) и делегаты (включая действие, фанк и т. Д.)
  • Обнуляемый
  • Т[] массивы
  • Пользовательские классы и структуры, включая универсальные классы и структуры.

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

  • Значения общедоступных, а не только для чтения полей
  • Значения общедоступных свойств с методами доступа 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:
}

}

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

(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 или Автокартограф, вы можете сделать что-то вроде этого:

MyObject oldObj; // The existing object to clone

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

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

видеть решение для глубокого клонирования valueinjecter на CodePlex.

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

Более подробное объяснение клонирования с использованием ICloneable см. Эта статья.

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

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

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

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

Ваше здоровье.

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

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

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

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

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

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

Я придумал это, чтобы преодолеть .СЕТЬ недостаток, связанный с необходимостью глубокого копирования List<T> вручную.

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

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

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

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

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

А еще лучше использовать универсальный клонер List<T>:

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

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

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

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

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

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

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

И метод...

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

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

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

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

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

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

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

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

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

Контрольные суммы для помощи в отладке

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

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

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

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

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

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

Обновлять

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

В общем, вы реализуете интерфейс ICloneable и реализуете Clone самостоятельно.Объекты 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;
}

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

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

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

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

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

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

Мы видели много идей, от сериализации до ручной реализации и отражения, и я хочу предложить совершенно другой подход, используя метод Генератор кода 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, но являются производными от базового класса, который в остальном идентичен, за исключением отсутствия клонирования.Вместо передачи переменных явного клонируемого типа возьмите параметр типа ICloneable<theNonCloneableType>.Это позволит программе, которая ожидает клонируемую производную Foo работать с клонируемой производной DerivedFoo, но также позволяют создавать неклонируемые производные Foo.

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

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

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

Требует Система, System.IO, System.Runtime.Serialization, System.Runtime.Serialization.Formatters.Binary, System.Xml;

public static class ObjectCopier
{

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

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

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


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

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

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


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

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

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


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

} 

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

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

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

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

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

yourClass newLine = oldLine.DeepCopy();

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

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

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

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

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

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

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

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

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

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

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top