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

1844

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

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

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

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

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

  • 71
    Может быть полезно: «Почему копирование объекта - ужасная вещь?» agiledeveloper.com/articles/cloning072002.htm
  • 0
    stackoverflow.com/questions/8025890/… Другое решение ...
Показать ещё 10 комментариев
Теги:
clone

39 ответов

1479
Лучший ответ

В то время как стандартная практика заключается в реализации интерфейса 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);
        }
    }
}

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

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

Если вы предпочитаете использовать новый метод метода расширения С# 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);
}
  • 22
    stackoverflow.com/questions/78536/cloning-objects-in-c/… имеет ссылку на приведенный выше код [и ссылается на две другие подобные реализации, одна из которых более подходит в моем контексте]
  • 93
    Сериализация / десериализация включает в себя значительные накладные расходы, которые не нужны. Посмотрите интерфейс ICloneable и методы клонирования .MemberWise () в C #.
Показать ещё 28 комментариев
176

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

public static T Clone<T>(T source)
{
    var serialized = JsonConvert.SerializeObject(source);
    return JsonConvert.DeserializeObject<T>(serialized);
}
  • 12
    решение даже быстрее, чем решение BinaryFormatter, сравнение производительности сериализации .NET
  • 3
    Спасибо за это. Я смог сделать то же самое с сериализатором BSON, который поставляется с драйвером MongoDB для C #.
Показать ещё 6 комментариев
149

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

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

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

  • 0
    Я не понимаю, почему ICloneable считается расплывчатым. Учитывая тип типа Dictionary (Of T, U), я ожидал бы, что ICloneable.Clone должен делать любой уровень глубокого и поверхностного копирования, необходимый для того, чтобы новый словарь был независимым словарем, который содержит те же T и U (содержимое структуры, и / или ссылки на объекты) в качестве оригинала. Где двусмысленность? Безусловно, универсальный ICloneable (Of T), который унаследовал ISelf (Of T), который включал метод «Self», был бы намного лучше, но я не вижу двусмысленности в отношении глубокого и мелкого клонирования.
  • 27
    Ваш пример иллюстрирует проблему. Предположим, у вас есть словарь <строка, клиент>. Должны ли клонированный Словарь иметь те же объекты Customer, что и оригинал, или копии этих объектов Customer? Есть разумные варианты использования любого из них. Но ICloneable не дает понять, какой вы получите. Вот почему это не полезно.
Показать ещё 2 комментария
80

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

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

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

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

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

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

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

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

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

Копирование объекта путем указания New, за которым следует имя класса, часто приводит к тому, что код не расширяется. Использование клона, применение шаблона прототипа, является лучшим способом достижения этого. Однако использование клона, как это предусмотрено в С# (и 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

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

  • 5
    MS рекомендует не использовать ICloneable для публичных участников. «Поскольку вызывающие функции Clone не могут зависеть от метода, выполняющего предсказуемую операцию клонирования, мы рекомендуем не реализовывать ICloneable в общедоступных API». msdn.microsoft.com/en-us/library/… Однако, исходя из объяснения, данного Venkat Subramaniam в вашей связанной статье, я думаю, что имеет смысл использовать в этой ситуации, пока создатели объектов ICloneable имеют глубокий понимание того, какие свойства должны быть глубокими или мелкими копиями (т.е. глубокая копия мозга, мелкая копия города)
  • 0
    Во-первых, я далеко не эксперт в этой теме (публичные API). Я думаю, что на этот раз замечание MS имеет большой смысл. И я не думаю, что можно с уверенностью предполагать, что пользователи этого API будут иметь такое глубокое понимание. Таким образом, имеет смысл реализовывать его в общедоступном API, если это действительно не имеет значения для тех, кто будет его использовать. Я предполагаю, что наличие какого-то UML очень явно делает различие по каждому свойству может помочь. Но я хотел бы услышать от кого-то с большим опытом. :П
Показать ещё 4 комментария
71

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

  • 5
    .Net не имеет конструкторов копирования.
  • 44
    Конечно, это так: new MyObject (objToCloneFrom) Просто объявите ctor, который принимает объект для клонирования в качестве параметра.
Показать ещё 11 комментариев
35

Простой метод расширения для копирования всех общедоступных свойств. Работы для любых объектов и не требуют, чтобы класс был [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 ) } );
        }
    };
}
  • 12
    Это, к сожалению, некорректно. Это эквивалентно вызову objectOne.MyProperty = objectTwo.MyProperty (то есть просто скопирует ссылку). Он не будет клонировать значения свойств.
  • 1
    Алексу Норклиффу: автору вопроса задали вопрос о «копировании каждого свойства», а не о клонировании. в большинстве случаев точное дублирование свойств не требуется.
Показать ещё 1 комментарий
28

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

static public class SerializeHelper
{
    //Michael White, Holly Springs Consulting, 2009
    //[email protected]
    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);
    }
}
27

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

MyObject oldObj; // The existing object to clone

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

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

см. решение для глубокого клонирования значения инжектора на CodePlex.

26

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

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

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

var newInstance = source.GetClone();

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

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

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

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

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

  • Значения общедоступных, а не текстовых полей
  • Значения публичных свойств с помощью как get, так и set accessors
  • Элементы коллекции для типов, реализующих 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>?

  • 2
    Каковы шансы этого получить на NuGet? Кажется, это лучшее решение. Как это по сравнению с NClone ?
  • 0
    Я думаю, что за этот ответ нужно проголосовать больше раз. Внедрение ICloneable вручную утомительно и подвержено ошибкам, использование отражения или сериализации является медленным, если важна производительность и вам необходимо скопировать тысячи объектов в течение короткого периода времени.
Показать ещё 1 комментарий
20

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

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

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

См. эту статью разработчика Corner для нескольких дополнительных опций (кредит для Ian).

  • 1
    У ICloneable нет общего интерфейса, поэтому использовать этот интерфейс не рекомендуется.
  • 0
    Ваше решение работает до тех пор, пока оно не должно обрабатывать циклические ссылки, затем все начинает усложняться, лучше попытаться реализовать глубокое клонирование с использованием глубокой сериализации.
Показать ещё 2 комментария
15

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

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

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

var copy = anyObject.DeepClone();

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

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

  • 0
    клонирование кода с использованием деревьев выражений, которые вы разместили на codeproject.com/Articles/1111658/… , приводит к сбою в более новых версиях .Net framework с исключением из-за безопасности, операция может дестабилизировать среду выполнения , это в основном исключение из-за искаженного дерева выражений, который используется для генерации Func во время выполнения, пожалуйста, проверьте, если у вас есть какое-то решение. На самом деле я видел проблему только со сложными объектами с глубокой иерархией, простой можно легко скопировать
15

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

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

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

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

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

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

  • 4
    У ICloneable нет общего интерфейса, поэтому использовать этот интерфейс не рекомендуется.
  • 0
    Простые и краткие ответы являются лучшими.
10

Я придумал это, чтобы преодолеть недостаток .NET, чтобы вручную скопировать 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();
        }
    }
}
9

Держите вещи простыми и используйте 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();
  • 0
    Будьте осторожны с этим, он работает очень плохо. Я закончил тем, что переключился на ответ johnc, который является столь же коротким как этот и работает намного лучше.
  • 0
    @Agorilla, откуда ты знаешь пул производительности?
8

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

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;
}
  • 2
    Это похоже на членский клон, потому что не знает о свойствах ссылочного типа
  • 1
    Если вы хотите невероятно быстрой производительности, не используйте эту реализацию: она использует отражение, поэтому она не будет такой быстрой. И наоборот, «преждевременная оптимизация - это зло», поэтому игнорируйте производительность до тех пор, пока вы не запустите профилировщик.
Показать ещё 2 комментария
7

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

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

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

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

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

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

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

И метод...

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

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

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

  • 1.08 секунд для вложенных элементов-членов для вложенных структур
  • 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();
}

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

Типы значений vs. Ссылки Типы

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

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

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

Контрольные суммы для облегчения отладки

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

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

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

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

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

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

Update

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

  • 0
    Если вы копируете структуру, вы получаете мелкую копию, вам все равно может потребоваться конкретная реализация для глубокой копии.
  • 0
    @ Лассе В. Карлсен. Да, вы абсолютно правы, я обновил ответ, чтобы прояснить ситуацию. Этот метод может быть использован для создания глубоких копий структур и классов. Вы можете запустить включенный пример демонстрационного кода, чтобы показать, как это делается, у него есть пример глубокого клонирования вложенной структуры и еще один пример глубокого клонирования вложенного класса.
7

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

7

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

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

  • 0
    У ICloneable нет общего интерфейса, поэтому использовать этот интерфейс не рекомендуется.
6

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

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

5

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

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

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

Мы видели много идей от сериализации по сравнению с ручной реализацией до отражения, и я хочу предложить совершенно другой подход, используя Генератор кода 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;
    }
}

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

5

Мне нравится 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));
        }
    }

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

4

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

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

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

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

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

3

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

если вы будете кэшировать его правильно, иначе он будет клонировать 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/questions/222598/how-do-i-clone-a-generic-list-in-c

  • 0
    Вызов prop.GetValue(...) все еще является отражением и не может быть кэширован. В дереве выражений он компилируется, так что быстрее
3

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

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

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

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

3

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

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

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

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

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

yourClass newLine = oldLine.DeepCopy();

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

  • 3
    Метод MemberwiseClone создает поверхностную копию, а не глубокую копию. msdn.microsoft.com/en-us/library/...
3

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

Требуется System, 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;
    }

} 
1

Еще один ответ JSON.NET. Эта версия работает с классами, которые не реализуют ISerializable.

public static class Cloner
{
    public static T Clone<T>(T source)
    {
        if (ReferenceEquals(source, null))
            return default(T);

        var settings = new JsonSerializerSettings { ContractResolver = new ContractResolver() };

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

    class ContractResolver : DefaultContractResolver
    {
        protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
        {
            var props = type.GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)
                .Select(p => base.CreateProperty(p, memberSerialization))
                .Union(type.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)
                    .Select(f => base.CreateProperty(f, memberSerialization)))
                .ToList();
            props.ForEach(p => { p.Writable = true; p.Readable = true; });
            return props;
        }
    }
}
1

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

Я предлагаю вам один из самых быстрых, в настоящее время активно развитых. Я предлагаю UltraMapper https://github.com/maurosampietro/UltraMapper

Пакеты Nuget: https://www.nuget.org/packages/UltraMapper/

1

При использовании Marc Gravells protobuf-net в качестве вашего сериализатора принятый ответ нуждается в некоторых небольших модификациях, поскольку объект для копирования не будет отнесен к [Serializable] и, следовательно, не будет сериализуемым и метод Clone будет бросать исключение.
Я изменил его для работы с protobuf-net:

public static T Clone<T>(this T source)
{
    if(Attribute.GetCustomAttribute(typeof(T), typeof(ProtoBuf.ProtoContractAttribute))
           == null)
    {
        throw new ArgumentException("Type has no ProtoContract!", "source");
    }

    if(Object.ReferenceEquals(source, null))
    {
        return default(T);
    }

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

Это проверяет наличие атрибута [ProtoContract] и использует собственный форматтер protobufs для сериализации объекта.

1

Невероятно, сколько усилий вы можете потратить на интерфейс IClonable - особенно если у вас есть иерархии тяжелых классов. Также MemberwiseClone работает как-то странно - он точно не клонирует даже обычные типы типов типов типа.

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

Мне как-то понравилось предлагаемое решение: Как вы делаете глубокую копию объекта в .NET(С# специально)?

однако - он не поддерживал списки, добавил, что поддержка также учитывает повторное воссоединение. Для правила родительского права, которое я сделал, это поле или свойство должно быть названо "parent", тогда оно будет игнорироваться DeepClone. Возможно, вы захотите решить свои собственные правила для обратных ссылок - для иерархии деревьев это может быть "левое/правое" и т.д.

Вот фрагмент всего кода, включая тестовый код:

using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Text;

namespace TestDeepClone
{
    class Program
    {
        static void Main(string[] args)
        {
            A a = new A();
            a.name = "main_A";
            a.b_list.Add(new B(a) { name = "b1" });
            a.b_list.Add(new B(a) { name = "b2" });

            A a2 = (A)a.DeepClone();
            a2.name = "second_A";

            // Perform re-parenting manually after deep copy.
            foreach( var b in a2.b_list )
                b.parent = a2;


            Debug.WriteLine("ok");

        }
    }

    public class A
    {
        public String name = "one";
        public List<String> list = new List<string>();
        public List<String> null_list;
        public List<B> b_list = new List<B>();
        private int private_pleaseCopyMeAsWell = 5;

        public override string ToString()
        {
            return "A(" + name + ")";
        }
    }

    public class B
    {
        public B() { }
        public B(A _parent) { parent = _parent; }
        public A parent;
        public String name = "two";
    }


    public static class ReflectionEx
    {
        public static Type GetUnderlyingType(this MemberInfo member)
        {
            Type type;
            switch (member.MemberType)
            {
                case MemberTypes.Field:
                    type = ((FieldInfo)member).FieldType;
                    break;
                case MemberTypes.Property:
                    type = ((PropertyInfo)member).PropertyType;
                    break;
                case MemberTypes.Event:
                    type = ((EventInfo)member).EventHandlerType;
                    break;
                default:
                    throw new ArgumentException("member must be if type FieldInfo, PropertyInfo or EventInfo", "member");
            }
            return Nullable.GetUnderlyingType(type) ?? type;
        }

        /// <summary>
        /// Gets fields and properties into one array.
        /// Order of properties / fields will be preserved in order of appearance in class / struct. (MetadataToken is used for sorting such cases)
        /// </summary>
        /// <param name="type">Type from which to get</param>
        /// <returns>array of fields and properties</returns>
        public static MemberInfo[] GetFieldsAndProperties(this Type type)
        {
            List<MemberInfo> fps = new List<MemberInfo>();
            fps.AddRange(type.GetFields());
            fps.AddRange(type.GetProperties());
            fps = fps.OrderBy(x => x.MetadataToken).ToList();
            return fps.ToArray();
        }

        public static object GetValue(this MemberInfo member, object target)
        {
            if (member is PropertyInfo)
            {
                return (member as PropertyInfo).GetValue(target, null);
            }
            else if (member is FieldInfo)
            {
                return (member as FieldInfo).GetValue(target);
            }
            else
            {
                throw new Exception("member must be either PropertyInfo or FieldInfo");
            }
        }

        public static void SetValue(this MemberInfo member, object target, object value)
        {
            if (member is PropertyInfo)
            {
                (member as PropertyInfo).SetValue(target, value, null);
            }
            else if (member is FieldInfo)
            {
                (member as FieldInfo).SetValue(target, value);
            }
            else
            {
                throw new Exception("destinationMember must be either PropertyInfo or FieldInfo");
            }
        }

        /// <summary>
        /// Deep clones specific object.
        /// Analogue can be found here: /questions/2370/how-do-you-do-a-deep-copy-of-an-object-in-net-c-specifically
        /// This is now improved version (list support added)
        /// </summary>
        /// <param name="obj">object to be cloned</param>
        /// <returns>full copy of object.</returns>
        public static object DeepClone(this object obj)
        {
            if (obj == null)
                return null;

            Type type = obj.GetType();

            if (obj is IList)
            {
                IList list = ((IList)obj);
                IList newlist = (IList)Activator.CreateInstance(obj.GetType(), list.Count);

                foreach (object elem in list)
                    newlist.Add(DeepClone(elem));

                return newlist;
            } //if

            if (type.IsValueType || type == typeof(string))
            {
                return obj;
            }
            else if (type.IsArray)
            {
                Type elementType = Type.GetType(type.FullName.Replace("[]", string.Empty));
                var array = obj as Array;
                Array copied = Array.CreateInstance(elementType, array.Length);

                for (int i = 0; i < array.Length; i++)
                    copied.SetValue(DeepClone(array.GetValue(i)), i);

                return Convert.ChangeType(copied, obj.GetType());
            }
            else if (type.IsClass)
            {
                object toret = Activator.CreateInstance(obj.GetType());

                MemberInfo[] fields = type.GetFieldsAndProperties();
                foreach (MemberInfo field in fields)
                {
                    // Don't clone parent back-reference classes. (Using special kind of naming 'parent' 
                    // to indicate child parent class.
                    if (field.Name == "parent")
                    {
                        continue;
                    }

                    object fieldValue = field.GetValue(obj);

                    if (fieldValue == null)
                        continue;

                    field.SetValue(toret, DeepClone(fieldValue));
                }

                return toret;
            }
            else
            {
                // Don't know that type, don't know how to clone it.
                if (Debugger.IsAttached)
                    Debugger.Break();

                return null;
            }
        } //DeepClone
    }

}
0

Расширение С#, которое будет поддерживать типы "not ISerializable".

 public static class AppExtensions
 {                                                                      
       public static T DeepClone<T>(this T a)
       {
           using (var stream = new MemoryStream())
           {
               var serializer = new System.Xml.Serialization.XmlSerializer(typeof(T));

               serializer.Serialize(stream, a);
               stream.Position = 0;
               return (T)serializer.Deserialize(stream);
           }
       }                                                                    
 }

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

       var obj2 = obj1.DeepClone()
0

Я нашел новый способ сделать это, это Emit.

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

Emit может видеть официальный документ и Guide

Вы должны изучить некоторые IL, чтобы прочитать код. Я напишу код, который может скопировать свойство в класс.

public static class Clone
{        
    // ReSharper disable once InconsistentNaming
    public static void CloneObjectWithIL<T>(T source, T los)
    {
        //see http://lindexi.oschina.io/lindexi/post/C-%E4%BD%BF%E7%94%A8Emit%E6%B7%B1%E5%85%8B%E9%9A%86/
        if (CachedIl.ContainsKey(typeof(T)))
        {
            ((Action<T, T>) CachedIl[typeof(T)])(source, los);
            return;
        }
        var dynamicMethod = new DynamicMethod("Clone", null, new[] { typeof(T), typeof(T) });
        ILGenerator generator = dynamicMethod.GetILGenerator();

        foreach (var temp in typeof(T).GetProperties().Where(temp => temp.CanRead && temp.CanWrite))
        {
            //do not copy static that will except
            if (temp.GetAccessors(true)[0].IsStatic)
            {
                continue;
            }

            generator.Emit(OpCodes.Ldarg_1);// los
            generator.Emit(OpCodes.Ldarg_0);// s
            generator.Emit(OpCodes.Callvirt, temp.GetMethod);
            generator.Emit(OpCodes.Callvirt, temp.SetMethod);
        }
        generator.Emit(OpCodes.Ret);
        var clone = (Action<T, T>) dynamicMethod.CreateDelegate(typeof(Action<T, T>));
        CachedIl[typeof(T)] = clone;
        clone(source, los);
    }

    private static Dictionary<Type, Delegate> CachedIl { set; get; } = new Dictionary<Type, Delegate>();
}

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

0

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

T t = new T();
T t2 = (T)t;  //eh something like that

        List<myclass> cloneum;
        public void SomeFuncB(ref List<myclass> _mylist)
        {
            cloneum = new List<myclass>();
            cloneum = (List < myclass >) _mylist;
            cloneum.Add(new myclass(3));
            _mylist = new List<myclass>();
        }

похоже, работает со мной

  • 0
    Пробная переработка с объектом со свойствами с простыми типами и ссылочными типами. Только сделал мелкую копию свойства, которое было ссылочным типом.
0

Это скопирует все доступные для чтения и записываемые свойства объекта другому.

 public class PropertyCopy<TSource, TTarget> 
                        where TSource: class, new()
                        where TTarget: class, new()
        {
            public static TTarget Copy(TSource src, TTarget trg, params string[] properties)
            {
                if (src==null) return trg;
                if (trg == null) trg = new TTarget();
                var fulllist = src.GetType().GetProperties().Where(c => c.CanWrite && c.CanRead).ToList();
                if (properties != null && properties.Count() > 0)
                    fulllist = fulllist.Where(c => properties.Contains(c.Name)).ToList();
                if (fulllist == null || fulllist.Count() == 0) return trg;

                fulllist.ForEach(c =>
                    {
                        c.SetValue(trg, c.GetValue(src));
                    });

                return trg;
            }
        }

и вот как вы его используете:

 var cloned = Utils.PropertyCopy<TKTicket, TKTicket>.Copy(_tmp, dbsave,
                                                            "Creation",
                                                            "Description",
                                                            "IdTicketStatus",
                                                            "IdUserCreated",
                                                            "IdUserInCharge",
                                                            "IdUserRequested",
                                                            "IsUniqueTicketGenerated",
                                                            "LastEdit",
                                                            "Subject",
                                                            "UniqeTicketRequestId",
                                                            "Visibility");

или скопировать все:

var cloned = Utils.PropertyCopy<TKTicket, TKTicket>.Copy(_tmp, dbsave);
-1

Я знаю, что этот вопрос и answer сидит здесь некоторое время, а следующий - не совсем ответ, а скорее наблюдение, с которым я столкнулся недавно, когда я проверял, действительно, рядовые не клонируются (я бы не был собой, если не был), когда я с радостью скопировал скопированный @johnc обновленный ответ.

Я просто сделал метод расширения (который в значительной степени скопирован в виде вышеупомянутого ответа):

public static class CloneThroughJsonExtension
{
    private static readonly JsonSerializerSettings DeserializeSettings = new JsonSerializerSettings { ObjectCreationHandling = ObjectCreationHandling.Replace };

    public static T CloneThroughJson<T>(this T source)
    {
        return ReferenceEquals(source, null) ? default(T) : JsonConvert.DeserializeObject<T>(JsonConvert.SerializeObject(source), DeserializeSettings);
    }
}

и упал наивно классом, подобным этому (на самом деле их было больше, но они не связаны):

public class WhatTheHeck
{
    public string PrivateSet { get; private set; } // matches ctor param name

    public string GetOnly { get; } // matches ctor param name

    private readonly string _indirectField;
    public string Indirect => $"Inception of: {_indirectField} "; // matches ctor param name
    public string RealIndirectFieldVaule => _indirectField;

    public WhatTheHeck(string privateSet, string getOnly, string indirect)
    {
        PrivateSet = privateSet;
        GetOnly = getOnly;
        _indirectField = indirect;
    }
}

и код:

var clone = new WhatTheHeck("Private-Set-Prop cloned!", "Get-Only-Prop cloned!", "Indirect-Field clonned!").CloneThroughJson();
Console.WriteLine($"1. {clone.PrivateSet}");
Console.WriteLine($"2. {clone.GetOnly}");
Console.WriteLine($"3.1. {clone.Indirect}");
Console.WriteLine($"3.2. {clone.RealIndirectFieldVaule}");

привело к:

1. Private-Set-Prop cloned!
2. Get-Only-Prop cloned!
3.1. Inception of: Inception of: Indirect-Field cloned!
3.2. Inception of: Indirect-Field cloned!

Я был целым как: ЧТО Ф... поэтому я схватил Newtonsoft.Json Github repo и начал копать. Выходит, что: при десериализации типа, который имеет только один ctor и его имена параметров, соответствуют (нечувствительный к регистру) public property имена они будут переданы в ctor как те параметры. Некоторые подсказки можно найти в коде здесь и здесь.

Нижняя строка

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

Ещё вопросы

Сообщество Overcoder
Наверх
Меню