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


2226

Я хочу зробити щось на кшталт:

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

А потім внесіть зміни в новий об’єкт, які не відображаються в оригінальному об'єкті.

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

Як я можу клонувати або глибоко скопіювати об’єкт, щоб клонований об’єкт міг бути змінений без змін, відображених у вихідному об'єкті?


81
Може бути корисним: "Чому копіювання об'єкта - це страшна справа?" agiledeveloper.com/articles/cloning072002.htm
Pedro77


18
Ви повинні подивитися на AutoMapper
Деніел Літтл

3
Ваше рішення набагато складніше, я заблукав, читаючи його ... hehehe. Я використовую інтерфейс DeepClone. публічний інтерфейс IDeepCloneable <T> {T DeepClone (); }
Pedro77

1
@ Pedro77 - Хоча, що цікаво, ця стаття в кінці кінців говорить про створення cloneметоду для класу, тоді слід викликати внутрішній, приватний конструктор, який передається this. Таким чином, копіювання є жахливим [sic], але ретельно копіювати (і статтю, безумовно, варто прочитати) не є. ; ^)
ruffin

Відповіді:


1715

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

Як вже було сказано в іншому місці, це вимагає, щоб ваші об'єкти були серіалізаційні.

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

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

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

24
stackoverflow.com/questions/78536/cloning-objects-in-c/… має посилання на вищезазначений код [та посилається на дві інші такі реалізації, одна з яких є більш доречною у моєму контексті]
Ruben Bartelink

102
Серіалізація / десеріалізація передбачає значні витрати, які не потрібні. Дивіться інтерфейс ICloneable та методи клонування .MemberWise () у C #.
3Dave

18
@ Давид, надано, але якщо об’єкти легкі, а продуктивність при його використанні не надто висока для ваших вимог, то це корисна порада. Признаюсь, я не використовував його інтенсивно з великою кількістю даних, але ніколи не бачив жодної проблеми щодо ефективності.
johnc

16
@Amir: насправді ні: typeof(T).IsSerializableтакож вірно, якщо тип був позначений [Serializable]атрибутом. Він не повинен реалізувати ISerializableінтерфейс.
Даніель Герігер

11
Я просто подумав, що я хотів би згадати, що, хоча цей метод корисний, і я сам його використовував багато разів, він зовсім не сумісний із Середнім довірою - тому стежте, чи пишете ви код, який потребує сумісності. BinaryFormatter отримує доступ до приватних полів і, отже, не може працювати у наборі дозволів за умовчанням для часткових середовищ довіри. Ви можете спробувати інший серіалізатор, але переконайтесь, що ваш абонент знає, що клон може бути не ідеальним, якщо вхідний об'єкт покладається на приватні поля.
Алекс Норкліфф

298

Мені хотілося клонування дуже простих предметів, в основному примітивів та списків. Якщо ваш об'єкт не встановлений у серіалі 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);
    }
}

13
рішення solutiojn навіть швидше, ніж рішення BinaryFormatter, порівняння продуктивності .NET Serialization
esskar

3
Дякую за це Мені вдалося зробити те ж саме з серіалізатором BSON, який постачається разом із драйвером MongoDB для C #.
Марк Евер

3
Це найкращий спосіб для мене, однак я використовую, Newtonsoft.Json.JsonConvertале це те саме
П'єр

1
Для цього об'єкт для клонування повинен бути серіалізаційним, як уже згадувалося - це також означає, що він може не мати кругової залежності
radomeit

2
Я думаю, що це найкраще рішення, оскільки реалізація може застосовуватися на більшості мов програмування.
mr5

178

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

Так, MemberwiseCloneробиться неглибока копія, але протилежне MemberwiseCloneвідсутнє Clone; можливо, DeepCloneтакого не існує. Коли ви використовуєте об'єкт через його інтерфейс ICloneable, ви не можете знати, який тип клонування виконує базовий об'єкт. (І коментарі XML не дадуть зрозуміти, оскільки ви отримаєте коментарі інтерфейсу, а не коментарі методу Clone об'єкта.)

Те, що я зазвичай роблю, - це просто зробити Copyметод, який робить саме те, що я хочу.


Мені не ясно, чому ICloneable вважається розпливчастим. З огляду на такий тип словника (Of T, U), я б очікував, що ICloneable.Clone повинен виконувати будь-який рівень глибокої та дрібної копіювання, щоб новий словник був незалежним словником, що містить ті самі T і U (структура вмісту, та / або посилання на об'єкт) як оригінал. Де двозначність? Безумовно, загальний ICloneable (Of T), який успадкував ISelf (Of T), який включав метод "Self", був би набагато кращим, але я не бачу двозначності в глибокому проти мілкому клонуванні.
supercat

31
Ваш приклад ілюструє проблему. Припустимо, у вас є словник <рядок, клієнт>. Чи повинен клонований словник мати ті самі об'єкти Замовника, що й оригінал, або копії цих об'єктів Замовника? Існують розумні випадки використання для будь-якого. Але ICloneable не дає зрозуміти, який з них ви отримаєте. Ось чому це не корисно.
Райан Лунді

@Kyralessa У статті Microsoft MSDN насправді йдеться про цю саму проблему, коли ми не знаємо, чи вимагаєш ти глибоку чи дрібну копію.
розчав

Відповідь із дубліката stackoverflow.com/questions/129389/… описує розширення для копіювання на основі рекурсивного MembershipClone
Майкл Фрейджім

123

Після того, як багато багато читати про багатьох з варіантів , пов'язаних тут, і можливі рішення для цієї проблеми, я вважаю , все варіанти наведені досить добре на Ian P посилання «s (всі інші варіанти є варіаціями) , а ще краще рішення забезпечується Pedro77 «S посилання на коментарі питання.

Тому я просто скопіюю відповідні частини цих 2 посилань тут. Таким чином ми можемо мати:

Найкраще робити для клонування об’єктів у C різкі!

По-перше, це всі наші варіанти:

У статті Швидке глибоке копіювання деревами виразів також зіставлено ефективність клонування за допомогою серіалізації, відбиття та дерев виразів.

Чому я вибираю ICloneable (тобто вручну)

Містер Венкат Субраманіам (надмірне посилання тут) пояснює дуже докладно, чому .

Усі його статті кружляють навколо прикладу, який намагається застосувати для більшості випадків, використовуючи 3 об’єкти: Особа , Мозок та Місто . Ми хочемо клонувати людину, яка матиме власний мозок, але те саме місто. Ви можете зобразити всі проблеми, будь-який із інших способів, наведених вище, може принести чи прочитати статтю.

Це моя дещо змінена версія його висновку:

Копіювання об'єкта шляхом зазначення 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

Зауважте, що якщо ми будемо вести підрахунок кількості об'єктів, клон, як реалізований тут, буде зберігати правильний підрахунок кількості об'єктів.


6
MS рекомендує не використовувати ICloneableдля публічних членів. "Оскільки виклики Clone не можуть залежати від способу виконання передбачуваної операції клонування, ми рекомендуємо ICloneable не застосовуватись у публічних API." msdn.microsoft.com/en-us/library/… Однак, виходячи з пояснення, поданого Венкатом Субраманіамом у вашій пов’язаній статті, я думаю, що в цій ситуації є сенс використовувати , доки у творців об’єктів ICloneable є глибока розуміння того, які властивості мають бути глибокими та неглибокими копіями (тобто глибока копія «Мозг, мілка копія міста»)
BateTech

По-перше, я далеко не фахівець у цій темі (публічні API). Я думаю, що зауваження МС має один раз багато сенсу. І я не думаю, що можна припустити, що користувачі цього API матимуть таке глибоке розуміння. Отже, це має сенс реалізувати його на публічному API, якщо це дійсно не має значення для того, хто збирається ним користуватися. Я думаю, що якийсь UML, який явно робить чітке розмежування кожного властивості, може допомогти. Але я хотів би почутись від когось із більшим досвідом. : P
Крегокс

Ви можете використовувати CGbR Clone Generator і отримати аналогічний результат, не вручаючи код вручну.
Токсантрон

Корисна реалізація мови проміжних мов
Майкл Фрейджім

У С # немає жодного фіналу
Конрад,

84

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


5
.Net не має конструкторів копій.
Поп Каталін

48
Звичайно, це так: новий MyObject (objToCloneFrom) Просто оголосити ctor, який приймає об'єкт до клонування як параметр.
Нік

30
Це не одне й те саме. Ви повинні додавати його до кожного класу вручну, і навіть не знаєте, чи гарантуєте ви глибоку копію.
Дейв Ван ден Ейнде

15
+1 для копіювання копіювача. Вам також потрібно вручну записати функцію clone () для кожного типу об’єктів, і удачі в цьому, коли ваша ієрархія класу заглибиться на кілька рівнів.
Ендрю Грант

3
З конструкторами копій ви втрачаєте ієрархію. agiledeveloper.com/articles/cloning072002.htm
Will

41

Простий метод розширення для копіювання всіх загальнодоступних властивостей. Працює для будь-яких об’єктів і не вимагає класу [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 ) } );
        }
    };
}

15
Це, на жаль, є недоліком. Це еквівалентно виклику objectOne.MyProperty = objectTwo.MyProperty (тобто він просто скопіює посилання поперек). Це не буде клонувати значення властивостей.
Алекс Норкліфф

1
до Алекса Норкліффа: автор запитань запитав про "копіювання кожного ресурсу", а не про клонування. у більшості випадків точне дублювання властивостей не потрібно.
Костянтин Салаватов

1
Я думаю про використання цього методу, але з рекурсією. тому, якщо значення властивості є посиланням, створіть новий об’єкт та зателефонуйте CopyTo ще раз. Я просто бачу одну проблему, що всі використовувані класи повинні мати конструктор без параметрів. Хтось це вже пробував? Мені також цікаво, чи це насправді буде працювати з властивостями, що містять .net-класи, такі як DataRow та DataTable?
Корю

33

Я щойно створив проект CloneExtensionsбібліотеки . Він виконує швидкий, глибокий клон, використовуючи прості операції по призначенню, створені компіляцією коду виконання Expression Tree.

Як ним користуватися?

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

var newInstance = source.GetClone();

Ви можете вибрати, з чого слід скопіювати, sourceщоб newInstanceвикористовувати CloningFlagsenum:

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

Що можна клонувати?

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

Наступні члени класу / структури клонуються внутрішньо:

  • Значення загальнодоступних, а не лише читання полів
  • Цінності загальнодоступних властивостей як з доступом до отримання, так і встановленням
  • Елементи колекції для типів, що реалізують ICollection

Як швидко це?

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

Це також швидше, ніж рішення на основі серіалізації, коли ви клонуєте більше, ніж пару екземплярів одного типу T.

і більше...

Детальніше про створені вирази читайте на документації .

Зразок налагодження зразкового вираження для List<int>:

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


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

}

що має те саме значення, як наступний c # код:

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

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

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

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

    return target;
}

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


2
Які шанси цього потрапити на NuGet? Здається, найкраще рішення. Як воно порівнюється з NClone ?
розчавити

Я вважаю, що цю відповідь слід оскаржувати більше разів. Реалізація ICloneable вручну є виснажливою та схильною до помилок; використання рефлексії чи серіалізації є повільним, якщо продуктивність важлива і вам потрібно копіювати тисячі об'єктів за короткий проміжок часу.
нічний кодер

Зовсім не, ви помиляєтесь у роздумах, вам слід просто правильно кешувати це. Перевірте мій відповідь нижче stackoverflow.com/a/34368738/4711853
Roma бороду

31

Ну, у мене виникли проблеми з використанням 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);
    }
}

31

Якщо ви вже використовуєте стороннє додаток, наприклад ValueInjecter або Automapper , ви можете зробити щось подібне:

MyObject oldObj; // The existing object to clone

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

Використовуючи цей метод, вам не доведеться реалізовувати ISerializableабо ICloneableоб'єкти. Це звичайно з моделлю MVC / MVVM, тому створені такі прості інструменти.

див . зразок глибокого клонування ValueInjecter на GitHub .


25

Найкраще реалізувати метод розширення , як

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

а потім використовувати його в будь-якому місці рішення

var copy = anyObject.DeepClone();

У нас може бути три такі реалізації:

  1. За серіалізацією (найкоротший код)
  2. За відбиттям - в 5 разів швидше
  3. За деревами виразів - в 20 разів швидше

Всі пов'язані методи добре працюють і були глибоко перевірені.


клонування коди з допомогою дерев виразів , які ви виклали codeproject.com/Articles/1111658 / ... , зазнає невдачі з новішими версіями платформи .NET з виключенням безпеки, операція може дестабілізувати виконання , це в основному виключення з - за некоректну дерево виразів, який використовується для генерації функцій під час виконання, будь ласка, перевірте, чи є у вас якесь рішення. Насправді я бачив проблему лише зі складними об'єктами з глибокою ієрархією, простий легко скопіювати
Mrinal Kamboj

1
Реалізація ExpressionTree здається дуже хорошою. Він навіть працює з циркулярними посиланнями та приватними членами. Атрибути не потрібні. Найкраща відповідь, яку я знайшов.
N73k

Найкраща відповідь, спрацював дуже добре, ти врятував мій день
Адель Мурад

23

Коротка відповідь - ви успадковуєте інтерфейс ICloneable, а потім реалізуєте функцію .clone. Клон повинен виконати копіювання по контуру і виконати глибоку копію на будь-якому члені, який цього вимагає, а потім повернути отриманий об'єкт. Це рекурсивна операція (вона вимагає, щоб усі члени класу, якого ви хочете клонувати, були або типовими значеннями, або реалізовували ICloneable, а їхні члени були або типом значень, або реалізували ICloneable тощо).

Для більш детального пояснення щодо клонування за допомогою ICloneable, перегляньте цю статтю .

Довгий відповідь «це залежить». Як зазначають інші, ICloneable не підтримується дженериками, вимагає особливих міркувань щодо циркулярних посилань класів, а деякі насправді розглядаються як "помилка" в .NET Framework. Метод серіалізації залежить від того, що ваші об'єкти можуть бути серіалізаційними, яких вони можуть не бути, і ви можете не контролювати. У громаді ще багато дискусій щодо того, яка «найкраща» практика. Насправді жодне з рішень не є єдиним розміром, який відповідає найкращій практиці для всіх ситуацій, таких як ICloneable, що спочатку трактувався.

Дивіться цю статтю «Куточок розробника» для ще декількох варіантів (кредит Іану).


1
ICloneable не має загального інтерфейсу, тому не рекомендується використовувати цей інтерфейс.
Карг

Ваше рішення працює до тих пір, поки йому не доведеться обробляти кругові посилання, тоді все починає ускладнюватися, краще спробувати здійснити глибоке клонування за допомогою глибокої серіалізації.
Поп Каталін

На жаль, не всі об'єкти також є серіалізаційними, тому ви не завжди можете використовувати цей метод. Посилання Яна - найбільш вичерпна відповідь досі.
Zach Burlingame

19
  1. В основному вам потрібно реалізувати інтерфейс ICloneable, а потім реалізувати копіювання структури об'єкта.
  2. Якщо це глибока копія всіх членів, вам потрібно переконатись (не стосується обраного вами рішення), що всі діти також можуть бути закритими.
  3. Іноді вам потрібно знати про деякі обмеження під час цього процесу, наприклад, якщо ви копіюєте об'єкти ORM, більшість фреймворків дозволяють до сеансу приєднати лише один об’єкт, і ви НЕ МОЖЕТЕ робити клонів цього об’єкта, або якщо це можливо, вам потрібно піклуватися. про приєднання сеансів цих об'єктів.

Ура.


4
ICloneable не має загального інтерфейсу, тому не рекомендується використовувати цей інтерфейс.
Карг

Прості та стислі відповіді - найкращі.
DavidGuaita

17

EDIT: проект припинено

Якщо ви хочете справжнього клонування до невідомих типів, можете поглянути на fastclone .

Це клонування, засноване на виразах, працює приблизно в 10 разів швидше, ніж двійкова серіалізація та підтримує повну цілісність об'єктного графіка.

Це означає: якщо ви декілька разів посилаєтесь на один і той же об’єкт у своїй ієрахії, на клон також буде посилатися один екземпляр, який посилається.

Немає потреби в інтерфейсах, атрибутах чи будь-яких інших модифікаціях об'єктів, що клонуються.


Цей, здається, досить корисний
LuckyLikey

Починати працювати з одного знімка коду простіше, ніж для загальної системи, особливо закритої. Цілком зрозуміло, що жодна бібліотека не може вирішити всі проблеми одним кадром. Слід зробити деякі розслаблення.
TarmoPikaro

1
Я спробував ваше рішення, і, здається, працює добре, дякую! Я вважаю, що цю відповідь слід оскаржувати більше разів. Реалізація ICloneable вручну є виснажливою та схильною до помилок; використання рефлексії чи серіалізації є повільним, якщо продуктивність важлива і вам потрібно копіювати тисячі об'єктів за короткий проміжок часу.
нічний кодер

Я спробував це, і це у мене зовсім не вийшло. Викидає виняток MemberAccess.
Майкл Браун

Він не працює з новішими версіями .NET і припиняється
Michael Sander

14

Будьте простими і використовуйте 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();

Будьте обережні з цим, він працює дуже погано. Я закінчився переходом до відповіді johnc, яка така коротка, як ця, і працює набагато краще.
Агорілла

1
Це робиться лише неглибока копія.
N73k

11

Я придумав це, щоб подолати недолік у форматі .NET, змусивши вручну копіювати Список <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();
        }
    }
}

10

З. Чому я обрав би цю відповідь?

  • Виберіть цю відповідь, якщо ви хочете, щоб швидкість .NET була здатна.
  • Ігноруйте цю відповідь, якщо ви хочете по-справжньому, дуже простому способу клонування.

Іншими словами, зверніться до іншої відповіді, якщо у вас немає вузького місця, яке потребує виправлення, і ви можете довести це за допомогою профілера .

10 разів швидше, ніж інші методи

Наступний метод виконання глибокого клону:

  • 10 разів швидше за все, що передбачає серіалізацію / десеріалізацію;
  • Досить чорт, близький до теоретичної максимальної швидкості .NET здатний.

І метод ...

Для досягнення максимальної швидкості ви можете використовувати Nested MemberwiseClone, щоб зробити глибоку копію . Його майже така ж швидкість, як і копіювання ціннісної структури, і набагато швидша, ніж (a) відображення або (b) серіалізація (як описано в інших відповідях на цій сторінці).

Зауважте, що якщо ви використовуєте Nested MemberwiseClone для глибокої копії , вам потрібно вручну реалізувати ShallowCopy для кожного вкладеного рівня в класі та DeepCopy, який викликає всі зазначені методи ShallowCopy для створення повного клону. Це просто: всього кілька рядків, дивіться демо-код нижче.

Ось вихід коду, що показує відносну різницю продуктивності для 100000 клонів:

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

Використання Nested MemberwiseClone в класі майже так само швидко, як і копіювання структури, а також копіювання структури досить проклятене, близьке до максимальної теоретичної швидкості. NET здатна.

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

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

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

Щоб зрозуміти, як зробити глибоку копію за допомогою MemberwiseCopy, ось демонстраційний проект, який використовувався для генерування вищезазначених часів:

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

Потім зателефонуйте до демо-версії з основного:

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

Ще раз зауважте, що якщо ви використовуєте Nested MemberwiseClone для глибокої копії , вам доведеться вручну реалізувати ShallowCopy для кожного вкладеного рівня в класі та DeepCopy, який викликає всі зазначені методи ShallowCopy для створення повного клону. Це просто: всього кілька рядків, дивіться демо-код вище.

Типи значення порівняно з типами посилань

Зауважте, що якщо мова йде про клонування об'єкта, існує велика різниця між " структура " та " клас ":

  • Якщо у вас є " структура ", це тип значення, тому ви можете просто скопіювати його, а вміст буде клоновано (але це зробить лише неглибокий клон, якщо ви не використовуєте методи в цій публікації).
  • Якщо у вас є " клас ", це тип посилання , тому якщо ви його копіюєте, все, що ви робите, - це копіювання вказівника на нього. Щоб створити справжній клон, ви повинні бути більш креативними та використовувати відмінності між типами значень та типами посилань, що створює ще одну копію оригінального об'єкта в пам'яті.

Дивіться відмінності між типами значень та типами посилань .

Контрольні суми допоможуть у налагодженні

  • Неправильне клонування об’єктів може призвести до дуже важких помилок, які можна легко усунути. У виробничому коді я схильний застосовувати контрольну суму, щоб подвійно перевірити, чи об'єкт був клонований належним чином, і не був пошкоджений іншим посиланням на нього. Цю контрольну суму можна вимкнути в режимі випуску.
  • Я вважаю цей метод досить корисним: часто вам потрібно клонувати лише частини об’єкта, а не всю річ.

Дійсно корисно для роз'єднання багатьох потоків від багатьох інших потоків

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

  • У нас може бути одна (або більше) ниток, що змінюють клас, яким вони володіють, а потім висуваючи повну копію цього класу в a ConcurrentQueue .
  • Потім у нас є одна (або більше) ниток, які витягують копії цих класів і розбираються з ними.

Це дуже добре працює на практиці і дозволяє нам відокремити багато ниток (виробників) від однієї або декількох ниток (споживачів).

І цей метод також сліпуче швидкий: якщо ми використовуємо вкладені структури, це на 35 разів швидше, ніж серіалізація / дезаріалізація вкладених класів, і дозволяє нам скористатися всіма потоками, доступними на машині.

Оновлення

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


Якщо ви скопіюєте структуру, ви отримаєте неглибоку копію, можливо, все ще знадобиться конкретна реалізація для глибокої копії.
Лассе В. Карлсен

@Lasse В. Карлсен. Так, ви абсолютно праві, я оновив відповідь, щоб зробити це зрозумілішим. Цей метод можна використовувати для створення глибоких копій конструкцій та класів. Ви можете запустити включений демо-код прикладу, щоб показати, як його виконано, він має приклад глибокого клонування вкладеної структури та інший приклад глибокого клонування вкладеного класу.
Контанго

9

Загалом, ви реалізуєте інтерфейс ICloneable та реалізуєте Clone самостійно. Об'єкти C # мають вбудований метод MemberwiseClone, який виконує дрібну копію, яка може допомогти вам знайти усі примітиви.

Для глибокої копії немає ніякого способу знати, як це зробити автоматично.


ICloneable не має загального інтерфейсу, тому не рекомендується використовувати цей інтерфейс.
Карг

8

Я бачив, як це реалізується і через роздуми. В основному був метод, який би перебирав учасників об'єкта і відповідним чином копіював їх до нового об'єкта. Коли він досяг референтних типів або колекцій, я думаю, що він робив рекурсивний дзвінок на себе. Рефлексія дорога, але вона спрацювала досить добре.


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
Це виглядає як член-клон, оскільки не знає властивостей референтного типу
sll

1
Якщо ви хочете сліпо швидкої продуктивності, не використовуйте цю реалізацію: вона використовує відображення, тому вона не буде такою швидкою. І навпаки, "передчасна оптимізація - це все зло", тому ігноруйте продуктивність до тих пір, поки не запустите профілера.
Контанго

1
CreateInstanceOfType не визначено?
MonsterMMORPG

Він не працює на interger: "Нестатичний метод вимагає цілі."
Mr.B

8

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

https://github.com/kalisohn/CloneBehave

Також доступний як набір пакунків: https://www.nuget.org/packages/Clone.Behave/1.0.0

Наприклад: Наступний код поширюватиме адресу AddressClone, але виконує лише дрібну копію поля _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

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

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

Примітка. В останній версії є більш нульові перевірки, але я їх залишив для кращого розуміння.


6

Мені подобаються такі копіювальники:

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

Якщо у вас є більше речей для копіювання, додайте їх


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


6

Тут швидке та просте рішення, яке працювало для мене, не покладаючись на серіалізацію / десеріалізацію.

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

EDIT : вимагає

    using System.Linq;
    using System.Reflection;

Ось як я цим користувався

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

5

Виконайте такі дії:

  • Визначте властивість 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.


5

Я думаю, ви можете спробувати це.

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

4

Я створив версію прийнятої відповіді, яка працює як із "[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;
    }

} 

4

Гаразд, є кілька очевидних прикладів із відображенням у цій публікації, Але НЕ відображення зазвичай повільне, поки ви не почнете кешувати його належним чином.

якщо ви будете кешувати його належним чином, то він глибоко буде клонувати 1000000 об'єкта на 4,6s (вимірюється 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


2
Виклик prop.GetValue(...)все ще відображається і не може бути кешований. У дереві виразів його складено, проте так швидше
Tseng,

4

Оскільки майже всі відповіді на це питання були незадовільними або явно не працюють у моїй ситуації, я створив AnyClone, який повністю реалізований з роздумом і вирішив усі потреби тут. Мені не вдалося отримати серіалізацію для роботи за складним сценарієм зі складною структурою, іIClonable є менш ідеальним - адже це навіть не повинно бути необхідним.

Стандартні атрибути ігнорування підтримуються за допомогою [IgnoreDataMember],[NonSerialized] . Підтримує складні колекції, властивості без сеттерів, поля лише для читання тощо.

Я сподіваюся, що це допомагає комусь іншому, хто зіткнувся з тими ж проблемами, що і я.


4

Відмова: Я є автором згаданого пакету.

Мене здивувало, як найвищі відповіді на це питання у 2019 році все ще використовують серіалізацію чи рефлексію.

Серіалізація обмежує (вимагає атрибутів, конкретних конструкторів тощо) і дуже повільна

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

Виразні дерева

Ви можете замість цього використовувати « Дерева виразів» або « Reflection.Emit», щоб генерувати код клонування лише один раз, а потім використовувати цей скомпільований код замість повільного відображення чи серіалізації.

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

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

Використання

Ви можете встановити його з NuGet. Або отримайте ObjectClonerпакет і використовуйте його як:

var clone = ObjectCloner.DeepClone(original);

або якщо ви не заперечуєте забруднення вашого типу об’єкта розширеннями, ObjectCloner.Extensionsтакож запишіть:

var clone = original.DeepClone();

Продуктивність

Простий орієнтир клонування ієрархії класів показав продуктивність ~ 3x швидше, ніж використання Reflection, ~ 12x швидше, ніж серіалізація Newtonsoft.Json і ~ 36x швидше, ніж настійно запропоновано BinaryFormatter.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.