Мне нужна настоящая глубокая копия. В Java это было легко, но как это сделать на C#?
Глубокая копия - это то, что копирует КАЖДОЕ поле объекта. Неглубокая копия создаст только новый объект и укажет все поля на оригинал.
Глубокая копия создает второй экземпляр объекта с теми же значениями. Неглубокая копия (упрощенная) подобна созданию второй ссылки на объект.
Фреймворк для копирования / клонирования объектов .NET: github.com/havard/copyable
Используйте Mapper, я предлагаю UltraMapper github.com/maurosampietro/UltraMapper
проверьте этот пост: stackoverflow.com/a/52490699/1404642
BinaryFormatter небезопасен, посмотрите официальные документы: docs.microsoft.com/en-us/dotnet/api/…





BinaryFormatter устарел и больше не будет доступен в .NET после ноября 2023 года. См. Стратегия устаревания BinaryFormatter
Я видел несколько разных подходов к этому, но я использую общий служебный метод как таковой:
public static T DeepClone<T>(this T obj)
{
using (var ms = new MemoryStream())
{
var formatter = new BinaryFormatter();
formatter.Serialize(ms, obj);
ms.Position = 0;
return (T) formatter.Deserialize(ms);
}
}
Заметки:
Ваш класс ДОЛЖЕН быть отмечен как [Serializable], чтобы это работало.
Ваш исходный файл должен включать следующий код:
using System.Runtime.Serialization.Formatters.Binary;
using System.IO;
Что произойдет, если у объекта есть событие. Они потеряли все из-за сериализации?
Подписки на события включаются в граф сериализации, поскольку BinaryFormatter использует поля через отражение, а события - это просто поля типов делегатов плюс методы добавления / удаления / вызова. Вы можете использовать [field: NonSerialized] в событии, чтобы этого избежать.
Что это за необъявленная «потоковая» переменная? Или это что-то только на C#, а не на VB.NET? Я преобразовал все, кроме этой переменной.
мс местоположение или мс положение? или это как-то связано с версией Framework?
Как пометить что-то как [Serializable] ??
@ Sean87: над объявлением класса добавьте [Serializable]. поэтому [Serializable]public class Foo { } сделает Foo помеченным как сериализуемый.
Рекурсивный MemberwiseClone также выполняет глубокое копирование, он работает в 3 раза быстрее, чем BinaryFormatter, не требует конструктора по умолчанию или каких-либо атрибутов. Смотрите мой ответ: stackoverflow.com/a/11308879/235715
Это создает любопытное исключение «сборка не найдена» при использовании этого служебного кода в UserControlTestContainer. Это действительно странно, потому что сборка загружена ...
Чтобы программно обработать вашу первую заметку, вы можете немного лучше обработать исключение, используя следующее: if (!typeof(T).IsSerializable) { throw new ArgumentException("Type {0} is not serializable",typeof(T).Name); }
как бы вы ответили на -> эта очевидная проблема утечки памяти с Deserialize ()?
Более полезно использовать var formatter = new BinaryFormatter {Context = new StreamingContext (StreamingContextStates.Clone)};
хотя в некоторых сценариях это работает - кажется излишним сериализовать все и десериализовать все только для создания копии объекта. Я чувствую, что это должен быть пример того, что делать, когда вам нужно завершить свой проект в следующие 5 минут.
К тому же он очень медленный.
Для тех, кому интересно, посмотрите это простое решение: stackoverflow.com/questions/222598/…
ms.Position = 0 спас меня! Без этого происходили действительно странные вещи.
Красивый! Это было прямо у меня под носом, и я этого даже не заметил!
Спасибо за ответ. Меня устраивает!
Я знаю, что этот пост старый, но он по-прежнему пользуется успехом при поиске глубокого клонирования. Обратите внимание, что, согласно Microsoft (aka.ms/binaryformatter), это решение больше не рекомендуется, поскольку оно небезопасно.
Основываясь на решении Килхоффера ...
В C# 3.0 вы можете создать метод расширения следующим образом:
public static class ExtensionMethods
{
// Deep clone
public static T DeepClone<T>(this T a)
{
using (MemoryStream stream = new MemoryStream())
{
BinaryFormatter formatter = new BinaryFormatter();
formatter.Serialize(stream, a);
stream.Position = 0;
return (T) formatter.Deserialize(stream);
}
}
}
который расширяет любой класс, помеченный как [Serializable], с помощью метода DeepClone
MyClass copy = obj.DeepClone();
К этому добавьте «общедоступный статический T DeepClone <T> (это T a), где T: ISerializable»
@Amir - классу не обязательно реализовывать ISerializable, достаточно пометки с помощью SerializableAttribute. Атрибут использует отражение для выполнения сериализации, а интерфейс позволяет вам написать собственный сериализатор.
Я согласен с вашим утверждением, но мне нравится предложение Амира, поскольку оно обеспечивает проверку во время компиляции. Есть ли способ примирить их?
Пройден модульный тест var stringbuilder = new StringBuilder ("TestData"); var copy = stringbuilder.DeepClone (); Assert.IsFalse (Equals (построитель строк, копия)); Большое спасибо.
@Neil Этот метод в 10 раз медленнее, чем метод NestedMemberwiseClone, см. Мой пост на этой странице.
Привет, @Gravitas. Неудивительно, что методы, написанные вручную, могут превзойти решение, использующее отражение. Было бы интересно сравнить производительность вашего решения с методом сериализации с ручным кодированием (т.е. реализацией ISerializable).
@Gravitas, ручная сериализация была не лучше. Ваш ручной кодированный метод клонирования намного быстрее для простых классов (может быть, больше как 100x)
+1 @Neil - Спасибо, это именно то, что я искал!
780 rep только за добавление this .. Сила методов расширения :)
Привет, @nawfal - ну, в то время в коде Килхоффера тоже была ошибка, которую мне не хватало кармы исправить. Но да, мой лучший вклад на данный момент - это тривиальность.
это банально. нет каламбура, хе-хе
Я написал метод DeepClone на основе ответа Нила. Он отлично работал, пока я не вызвал DeepClone для объекта, класс которого был определен в «плагине» ... сборке, которую я загрузил программно. Это привело к возникновению исключения SerializationException с ошибкой «Невозможно найти сборку xxx». Я решил эту проблему с помощью решения, которое я опубликовал здесь
Возможно, вам нужна только неглубокая копия, в этом случае используйте Object.MemberWiseClone().
В документации к MemberWiseClone() есть хорошие рекомендации по стратегиям глубокого копирования:
http://msdn.microsoft.com/en-us/library/system.object.memberwiseclone.aspx
Хорошая попытка, но он специально попросил глубокий клон.
Вы можете сделать глубокий клон с помощью MemberwiseClone, все, что вам нужно сделать, это добавить вложение. См. Ответ @Gravitas выше.
public static object CopyObject(object input)
{
if (input != null)
{
object result = Activator.CreateInstance(input.GetType());
foreach (FieldInfo field in input.GetType().GetFields(Consts.AppConsts.FullBindingList))
{
if (field.FieldType.GetInterface("IList", false) == null)
{
field.SetValue(result, field.GetValue(input));
}
else
{
IList listObject = (IList)field.GetValue(result);
if (listObject != null)
{
foreach (object item in ((IList)field.GetValue(input)))
{
listObject.Add(CopyObject(item));
}
}
}
}
return result;
}
else
{
return null;
}
}
Этот способ в несколько раз быстрее, чем BinarySerialization, И для этого не требуется атрибут [Serializable].
Вы не продолжаете глубокое копирование своей ветки, отличной от IList, и я думаю, у вас могут возникнуть проблемы с ICollection / IEnumerable.
Использование техники «Nested MemberwiseClone» снова на порядок быстрее (см. Мой пост под @Gravitas).
Что такое Consts.AppConsts.FullBindingList?
Вы можете использовать Вложенный MemberwiseClone для глубокой копии. Его скорость почти такая же, как при копировании структуры значения, и на порядок быстрее, чем (а) отражение или (б) сериализация (как описано в других ответах на этой странице).
Обратите внимание, что если вы используете Вложенный MemberwiseClone для глубокой копии, вам нужно вручную реализовать ShallowCopy для каждого вложенного уровня в классе и DeepCopy, который вызывает все указанные методы ShallowCopy для создания полного клона. Это просто: всего несколько строк, см. Демонстрационный код ниже.
Вот результат кода, показывающий относительную разницу в производительности (4,77 секунды для глубоко вложенного MemberwiseCopy против 39,93 секунды для сериализации). Использование вложенного MemberwiseCopy почти так же быстро, как копирование структуры, а копирование структуры чертовски близко к теоретической максимальной скорости, на которую способен .NET, что, вероятно, довольно близко к скорости того же самого в C или C++ (но было бы необходимо выполнить некоторые эквивалентные тесты, чтобы проверить это утверждение).
Demo 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 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 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 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", sw.Elapsed, total);
}
{
Console.Write("Demo 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", sw.Elapsed, total);
}
{
Console.Write("Demo of deep copy, using class and serialize/deserialize:\n");
int total = 0;
var sw = new Stopwatch();
sw.Start();
var Bob = new Person(30, "Lamborghini");
for (int i = 0; i < 100000; i++)
{
var BobsSon = MyDeepCopy.DeepCopy<Person>(Bob);
total += BobsSon.Age;
}
Console.Write(" Elapsed time: {0},{1}\n", sw.Elapsed, total);
}
Console.ReadKey();
}
Опять же, обратите внимание, что если вы используете Вложенный MemberwiseClone для глубокой копии, вам нужно вручную реализовать ShallowCopy для каждого вложенного уровня в классе и DeepCopy, который вызывает все указанные методы ShallowCopy для создания полного клона. Это просто: всего несколько строк, см. Демонстрационный код выше.
Обратите внимание, что когда дело доходит до клонирования объекта, существует большая разница между «структурой» и «классом»:
Обновлять
Вероятно, можно использовать отражение, чтобы рекурсивно пройти по графу объекта, чтобы сделать глубокую копию. WCF использует этот метод для сериализации объекта, включая всех его дочерних элементов. Хитрость заключается в том, чтобы аннотировать все дочерние объекты с помощью атрибута, который делает их обнаруживаемыми. Однако вы можете потерять некоторые преимущества в производительности.
Обновлять
Цитата по независимому тесту скорости (см. Комментарии ниже):
I've run my own speed test using Neil's serialize/deserialize extension method, Contango's Nested MemberwiseClone, Alex Burtsev's reflection-based extension method and AutoMapper, 1 million times each. Serialize-deserialize was slowest, taking 15.7 seconds. Then came AutoMapper, taking 10.1 seconds. Much faster was the reflection-based method which took 2.4 seconds. By far the fastest was Nested MemberwiseClone, taking 0.1 seconds. Comes down to performance versus hassle of adding code to each class to clone it. If performance isn't an issue go with Alex Burtsev's method. – Simon Tewsi
Хороший пост. Есть идеи, почему сериализация намного медленнее? Кроме того, как будет работать ваша контрольная сумма? Почему бы просто не использовать средство проверки равенства?
@ user420667 Контрольная сумма работает путем ручного преобразования всех параметров в классе в целые числа, а затем складывания указанных целых чисел для создания контрольной суммы. Это полезно, если вы загружаете копии объектов в очередь в одном потоке и считываете их другим потоком. Вам понадобятся методы: ChecksumWrite и ChecksumVerify.
@ user420667 Можно также использовать средство проверки равенства, но только если у вас есть что-то еще для сравнения. Если вы загружаете вещи в очередь, как вы определяете, действительны ли предметы, выходящие на другой конец? Средство проверки равенства не будет работать, тогда как контрольная сумма будет проверять внутреннюю согласованность. После того, как будет доказано, что код работает, скажем, со 100 миллионами элементов в очереди за пару недель развертывания, вы можете быть уверены, что код написан надежно, и вы можете удалить контрольную сумму.
Могу подтвердить, что это намного быстрее, чем метод сериализации. Стоимость: написание большего количества кода; риск обслуживания при добавлении поля без добавления его в метод клонирования; необходимо написать вспомогательные классы для любых сторонних классов (например, Dictionary <>)
@Gravitas: А, ладно. Полагаю, я представлял item1 по сравнению с item2, поэтому казалось излишним делать item1.GetChecksum () только для сравнения его с item2.GetChecksum (), что потребовало бы больше операций, чем простое сравнение на равенство. Но если позже вы сравните его только с самим собой ... я не знаю, это звучит как невыполнимая задача. Моя ChecksumVerify, вероятно, вызовет GetChecksum (), поэтому почти наверняка они будут совпадать.
@Gravitas: Полагаю, вас беспокоит целостность данных в какой-то зашумленной линии передачи, верно?
Вы можете создать метод расширения, который работает с любым объектом, см. Мой ответ stackoverflow.com/a/11308879/235715
@ user420667. Нет, меня больше беспокоит неправильное клонирование объектов, которое приводит к ошибкам (см. Мой предпоследний пункт в моем ответе выше).
Жаль, что ни Java, ни .NET не различают ссылки, которые инкапсулируют идентичность, изменяемое состояние, и то, и другое, или ни то, ни другое. По идее, должен быть только один тип «клона»: новый объект, каждая ссылка которого инкапсулирует то же самое, что и соответствующая ссылка в оригинале. Если ссылка инкапсулирует идентичность, ссылка клона должна относиться к объекту такой же. Если он инкапсулирует изменяемое состояние, но идентификатор нет, клон должен получить ссылку на другой объект с тем же состоянием [в противном случае обе ссылки будут ошибочными ...
... инкапсулируют личность, а также состояние]. Ссылку на объект, которая инкапсулирует как личность, так и состояние, нельзя клонировать, кроме как путем копирования всего остального который содержит ссылку на этот объект - подвиг, который часто бывает трудным или невозможным. Хотя ссылки на некоторые типы объектов обычно используются для инкапсуляции идентичности, а ссылки на другие обычно инкапсулируют изменяемое состояние, знание типа объекта недостаточно для цели, для которой удерживается ссылка.
Я провел свой собственный тест скорости, используя метод расширения сериализации / десериализации Нила, Nested MemberwiseClone Contango, метод расширения на основе отражения Алекса Бурцева и AutoMapper, по 1 миллиону раз каждый. Сериализация-десериализация была самой медленной, занимая 15,7 секунды. Затем появился AutoMapper, занимавший 10,1 секунды. Намного быстрее оказался метод, основанный на отражении, который занял 2,4 секунды. Безусловно, самым быстрым был Nested MemberwiseClone, занимавший 0,1 секунды. Все сводится к производительности, а не к необходимости добавления кода в каждый класс для его клонирования. Если производительность не является проблемой, используйте метод Алекса Бурцева.
Как Nested MemberwiseClone обрабатывает циклические ссылки?
@vargonian Нет циклических ссылок, если кто-то проектирует структуру данных без них. Как правило, структуры данных POCO простые, вложенные в 2 (или, возможно, 3) уровня.
this.Purchase.ShallowCopy(). Это не имеет смысла, если самой Purchase требуется DeepCopy. Правильный шаблон для истинного DeepCopy - вызывать DeepCopy для каждого непримитивного поля. Любой тип, содержащий только примитивы, просто реализует DeepCopy как ShallowCopy (Person)this.MemberwiseClone(). В DeepCopy родительский не знает, является ли каждый непримитивный ребенок глубоким или мелким, поэтому он должен вызывать DeepCopy для дочернего элемента. Ответственность ребенка - продолжать глубокое или мелкое. Чтобы избежать ошибок, лучше всегда вызвать DeepCopy. Пусть ребенок сам отвечает за себя.
@ToolmakerSteve Верно. Кроме того, имейте в виду, что это только демонстрационный код, в основном направленный на то, чтобы показать разницу между глубокими и мелкими копиями. В производственном коде я также рекомендовал бы, чтобы все называлось DeepCopy прямо в стеке, даже если оно выполняет только неглубокую копию. К счастью, это проблема с самоограничением: наличие метода ShallowCopy вряд ли приведет к возникновению каких-либо ошибок, поскольку название дает понять, что он делает, и его можно отредактировать для добавления DeepCopy по запросу. В конце концов, этот код абсолютно надежен и испытан во многих успешных проектах за последние 10 лет.
Я считаю, что подход BinaryFormatter относительно медленный (что стало для меня неожиданностью!). Вы можете использовать ProtoBuf .NET для некоторых объектов, если они соответствуют требованиям ProtoBuf. На странице «Начало работы с ProtoBuf» (http://code.google.com/p/protobuf-net/wiki/GettingStarted):
Примечания к поддерживаемым типам:
Пользовательские классы, которые:
Код предполагает, что типы будут изменяться вокруг выбранных членов. Соответственно, пользовательские структуры не поддерживаются, поскольку они должны быть неизменяемыми.
Если ваш класс соответствует этим требованиям, вы можете попробовать:
public static void deepCopy<T>(ref T object2Copy, ref T objectCopy)
{
using (var stream = new MemoryStream())
{
Serializer.Serialize(stream, object2Copy);
stream.Position = 0;
objectCopy = Serializer.Deserialize<T>(stream);
}
}
Что действительно ОЧЕНЬ быстро ...
Редактировать:
Вот рабочий код для модификации этого (протестирован на .NET 4.6). Он использует System.Xml.Serialization и System.IO. Не нужно отмечать классы как сериализуемые.
public void DeepCopy<T>(ref T object2Copy, ref T objectCopy)
{
using (var stream = new MemoryStream())
{
var serializer = new XS.XmlSerializer(typeof(T));
serializer.Serialize(stream, object2Copy);
stream.Position = 0;
objectCopy = (T)serializer.Deserialize(stream);
}
}
Интересно, насколько быстро это сравнивается с ответом Nested MemberwiseClone выше?
это не сработает, если в вашем классе есть словарь, который необходимо скопировать, поскольку IDictionary нельзя сериализовать
Вы можете попробовать это
public static object DeepCopy(object obj)
{
if (obj == null)
return null;
Type type = obj.GetType();
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(DeepCopy(array.GetValue(i)), i);
}
return Convert.ChangeType(copied, obj.GetType());
}
else if (type.IsClass)
{
object toret = Activator.CreateInstance(obj.GetType());
FieldInfo[] fields = type.GetFields(BindingFlags.Public |
BindingFlags.NonPublic | BindingFlags.Instance);
foreach (FieldInfo field in fields)
{
object fieldValue = field.GetValue(obj);
if (fieldValue == null)
continue;
field.SetValue(toret, DeepCopy(fieldValue));
}
return toret;
}
else
throw new ArgumentException("Unknown type");
}
Спасибо DetoX83 статья за проект кода.
Это работает, только если у вашего объекта есть конструктор по умолчанию!
Лучший способ:
public interface IDeepClonable<T> where T : class
{
T DeepClone();
}
public class MyObj : IDeepClonable<MyObj>
{
public MyObj Clone()
{
var myObj = new MyObj();
myObj._field1 = _field1;//value type
myObj._field2 = _field2;//value type
myObj._field3 = _field3;//value type
if (_child != null)
{
myObj._child = _child.DeepClone(); //reference type .DeepClone() that does the same
}
int len = _array.Length;
myObj._array = new MyObj[len]; // array / collection
for (int i = 0; i < len; i++)
{
myObj._array[i] = _array[i];
}
return myObj;
}
private bool _field1;
public bool Field1
{
get { return _field1; }
set { _field1 = value; }
}
private int _field2;
public int Property2
{
get { return _field2; }
set { _field2 = value; }
}
private string _field3;
public string Property3
{
get { return _field3; }
set { _field3 = value; }
}
private MyObj _child;
private MyObj Child
{
get { return _child; }
set { _child = value; }
}
private MyObj[] _array = new MyObj[4];
}
Используя Генератор клонов CGbR, вы получите тот же результат без написания кода вручную.
Я написал метод расширения глубокой копии объекта, основанный на рекурсивном "MemberwiseClone". Он быстрый (в три раза быстрее, чем BinaryFormatter) и работает с любыми объектами. Вам не нужен конструктор по умолчанию или сериализуемые атрибуты.
Исходный код:
using System.Collections.Generic;
using System.Reflection;
using System.ArrayExtensions;
namespace System
{
public static class ObjectExtensions
{
private static readonly MethodInfo CloneMethod = typeof(Object).GetMethod("MemberwiseClone", BindingFlags.NonPublic | BindingFlags.Instance);
public static bool IsPrimitive(this Type type)
{
if (type == typeof(String)) return true;
return (type.IsValueType & type.IsPrimitive);
}
public static Object Copy(this Object originalObject)
{
return InternalCopy(originalObject, new Dictionary<Object, Object>(new ReferenceEqualityComparer()));
}
private static Object InternalCopy(Object originalObject, IDictionary<Object, Object> visited)
{
if (originalObject == null) return null;
var typeToReflect = originalObject.GetType();
if (IsPrimitive(typeToReflect)) return originalObject;
if (visited.ContainsKey(originalObject)) return visited[originalObject];
if (typeof(Delegate).IsAssignableFrom(typeToReflect)) return null;
var cloneObject = CloneMethod.Invoke(originalObject, null);
if (typeToReflect.IsArray)
{
var arrayType = typeToReflect.GetElementType();
if (IsPrimitive(arrayType) == false)
{
Array clonedArray = (Array)cloneObject;
clonedArray.ForEach((array, indices) => array.SetValue(InternalCopy(clonedArray.GetValue(indices), visited), indices));
}
}
visited.Add(originalObject, cloneObject);
CopyFields(originalObject, visited, cloneObject, typeToReflect);
RecursiveCopyBaseTypePrivateFields(originalObject, visited, cloneObject, typeToReflect);
return cloneObject;
}
private static void RecursiveCopyBaseTypePrivateFields(object originalObject, IDictionary<object, object> visited, object cloneObject, Type typeToReflect)
{
if (typeToReflect.BaseType != null)
{
RecursiveCopyBaseTypePrivateFields(originalObject, visited, cloneObject, typeToReflect.BaseType);
CopyFields(originalObject, visited, cloneObject, typeToReflect.BaseType, BindingFlags.Instance | BindingFlags.NonPublic, info => info.IsPrivate);
}
}
private static void CopyFields(object originalObject, IDictionary<object, object> visited, object cloneObject, Type typeToReflect, BindingFlags bindingFlags = BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.FlattenHierarchy, Func<FieldInfo, bool> filter = null)
{
foreach (FieldInfo fieldInfo in typeToReflect.GetFields(bindingFlags))
{
if (filter != null && filter(fieldInfo) == false) continue;
if (IsPrimitive(fieldInfo.FieldType)) continue;
var originalFieldValue = fieldInfo.GetValue(originalObject);
var clonedFieldValue = InternalCopy(originalFieldValue, visited);
fieldInfo.SetValue(cloneObject, clonedFieldValue);
}
}
public static T Copy<T>(this T original)
{
return (T)Copy((Object)original);
}
}
public class ReferenceEqualityComparer : EqualityComparer<Object>
{
public override bool Equals(object x, object y)
{
return ReferenceEquals(x, y);
}
public override int GetHashCode(object obj)
{
if (obj == null) return 0;
return obj.GetHashCode();
}
}
namespace ArrayExtensions
{
public static class ArrayExtensions
{
public static void ForEach(this Array array, Action<Array, int[]> action)
{
if (array.LongLength == 0) return;
ArrayTraverse walker = new ArrayTraverse(array);
do action(array, walker.Position);
while (walker.Step());
}
}
internal class ArrayTraverse
{
public int[] Position;
private int[] maxLengths;
public ArrayTraverse(Array array)
{
maxLengths = new int[array.Rank];
for (int i = 0; i < array.Rank; ++i)
{
maxLengths[i] = array.GetLength(i) - 1;
}
Position = new int[array.Rank];
}
public bool Step()
{
for (int i = 0; i < Position.Length; ++i)
{
if (Position[i] < maxLengths[i])
{
Position[i]++;
for (int j = 0; j < i; j++)
{
Position[j] = 0;
}
return true;
}
}
return false;
}
}
}
}
Я пробовал это, но получил сообщение об ошибке, в котором говорилось, что мне нужны сериализуемые атрибуты в моем классе, который я клонирую ...
@theJerm Я думаю, вы вызвали Clone вместо Copy, есть метод Clone, который использует BinaryFormatter, как описано в принятом ответе для сравнения тестов
Спасибо, Алекс, да, мне нужно было вызвать копию, и это сработало!
Для ReferenceEqualityComparer.GetHashCode(object obj) вы должны использовать RuntimeHelpers.GetHashCode(obj), иначе он будет использовать реальный хэш-код объекта. См. stackoverflow.com/a/11240110/495262
Что касается IsPrimitive: по какой причине вы возвращаете true для строки. Кроме того, есть ли причина, по которой вы используете один & вместо && в заявлении: return (type.IsValueType & type.IsPrimitive);?
Основываясь на чтении кода, я предполагаю, что это не работает для типов, содержащих Delegates. Вы знаете, работают ли другие подходы и с делегатами? Я бы предположил, что нет.
@MattSmith Вы абсолютно правы насчет GetHashCode, проверил, да исключение StackOverflow - gist.github.com/Burtsev-Alexey/11227277
@MattSmith Это сработало для делегатов, но я намеренно отключил его (установив null), см. github.com/Burtsev-Alexey/net-object-deep-copy/issues/7, подписчики были клонированы, в конце концов, если у вас были подключены два объекта A и B (по подписке на событие), вы получили бы объекты A ' и B 'связаны, это правильно, но это не то, чего хотят большинство людей, когда клонируют объекты.
@AlexBurtsev, Что касается делегатов - хорошо, я изменил его, чтобы не копировать делегаты, а копировать ссылку (аналогично тому, как вы обрабатываете строки), поскольку (как и строки) делегаты неизменяемы. Подход сериализации действительно обрабатывает делегатов, но я не уверен, есть ли у них проблема, аналогичная той, что вы испытывали с делегатами.
@MattSmith Строки обрабатываются как примитивы, потому что 1: они неизменяемы, 2: вызов защищенного MemberwiseClone в строке приведет к повреждению памяти, строковые данные превратятся в случайные символы, и вскоре среда выполнения .NET выйдет из строя с внутренней ошибкой, заявив, что это но в .NET :-)
@MattSmith Я полагаю, вы понимаете, что при повторном использовании одного и того же делегата в исходном и клонированном объекте они будут использовать те же объекты, на которые ссылались в делегате, например, у A есть делегат, который изменяет B, при клонировании A вы получите A ', который изменяет то же самое B, если вы хотите получить истинную копию (снимок) графов объектов (память), просто удалите строку, которая устанавливает делегат в значение null. Тогда вы получите A, изменяющий B, и A, который `` изменяет B ''
@AlexBurtsev, Хорошие моменты. То, что вы хотите, в некоторой степени зависит от того, к каким объектам осуществляется доступ в делегате. Если вы обращаетесь к копируемым объектам, вам, вероятно, понадобится скопированный делегат. Если вы обращаетесь к объекту, внешнему по отношению к скопированному объекту, вы, вероятно, хотите, чтобы делегат не копировался. Тем не менее, у меня нет варианта использования для делегатов, поэтому, возможно, лучше пока оставить его нереализованным (т.е. я выброшу исключение).
Кстати, когда я включаю делегатов для моего простого тестового примера, он переполняется. Вот мой образец объекта, который я пытаюсь скопировать: shar.es/T86JR
Я раздвоил и обновил ваш код для совместимости с новым API отражения на основе TypeInfo (используется в приложениях Windows Phone и Windows Store и их соответствующих профилях PCL). github.com/Gameleon12/net-object-deep-copy/blob/master/…
Добавьте пример того, как использовать ваш метод расширения, это поможет конкретизировать ваш ответ.
Еще быстрее (для меня) использовать JSON.NET и сериализовать, а затем десериализовать. Может не работать для всех (я думаю о частных полях и т. д.).
@AlexBurtsev, ваш код, использующий пространство имен System.ArrayExtensions, где я могу его получить?
@ Alex141 - только что столкнулся с той же проблемой. Весь соответствующий код находится в указанном файле, внизу есть пространство имен ArrayExtensions.
Это очень умная и мощная реализация, однако вы должны учесть несколько вещей, прежде чем решить, подходит ли она для вашей модели данных. Memberwiseclone() работает так быстро, потому что не вызывает конструкторов. Так что, если ваши конструкторы выполняют тяжелую работу, такую как подписка на события, вам не повезло. Он полагается на копирование частных полей объекта, минуя бизнес-логику в свойствах и методах. Например, я видел, как поле hashCode копируется в коллекцию HashSet, хотя все экземпляры были изменены.
Я получаю StackOverflowException при клонировании объектов, которые взаимно ссылаются друг на друга ... Кто-нибудь знает обходной путь?
@yu_ominae циклические ссылки поддерживаются и отлично работают. Ваша проблема в другом месте. Если вы можете поделиться исходным кодом класса, который пытаетесь клонировать, откройте проблему на веб-сайте проекта. Иногда я сталкиваюсь с SO-исключениями при клонировании того, что не должно быть клонировано, например внутренней инфраструктуры .NET, на которую могут случайно ссылаться, особенно указатели и их отражения. Например, попытка клонирования сущности NHibernate, содержащей коллекцию, прикрепленную к сеансу, приведет к большому беспорядку, потому что коллекция NH, реализующая IList <T>, содержит внутри поле DbConnection.
@AlexBurtsev Я пытался клонировать объекты коллекции, отражающие объекты в другом приложении. Доступ осуществляется через com-вызовы, так что, возможно, у меня есть ссылка, которая вызывает взрыв. Метод .Copy() работал нормально, если я установил точку останова и проверил содержимое списка в часах перед возобновлением выполнения. Странный. Я обнаружил, что звонок на .ToList() действительно дает то, что я хочу сейчас, так что все в порядке. Спасибо за ответ :)
Могу ли я использовать это, но при этом убедиться, что определенное поле копируется только по ссылке?
@ Дэвид Не из коробки.
Можете ли вы объяснить, в чем разница между глубиной и мелкостью? Я понимаю, что memberwiseClone неглубокий, и это то, что вы здесь вызываете. Если я чего-то не упускаю.
Привет, Алекс, спасибо за расширение. Это также события / делегаты DeepCopy?
Кажется, есть проблемы с типом PropertyInfo. Есть ли способ пометить свойства этого типа как исключенные или разрешенные для ссылки?
Ни единого комментария во всем. ВАУ.
По-прежнему можно изменять вложенные списки :(
вы не копируете массивы, если их примитив + строка, но можно изменить одну копию, тогда другая копия увидит это изменение, которое не является глубокой копией.
Предупреждение: такой подход тормозит XDocuments. Я больше не могу получить доступ к атрибутам по имени, даже если пространство имен не определено.
Похоже, что List <BsonElement> неправильно отражается в fieldInfo. Из: List<BsonElement> { BsonElement ("status=Connected"), BsonElement("lastConnectedTimeUtc=2019-05-15T10:50:40.5570932Z") } делает: BsonElement[] { BsonElement ("status=Connected"), BsonElement("lastConnectedTimeUtc=2019-05-15T10:50:40.5570932Z"), BsonElement(" = "), BsonElement(" = ") }
Я использовал это в сочетании с fluentassertions, чтобы создать клон объекта, а затем проверить originalObject.Should (). BeEquivalentTo (objectClone). Однако это не удается, если объект содержит структуры Noda Time. Решением было добавить в InternalCopy такую строку: if (typeToReflect == typeof(LocalDate) || typeToReflect == typeof(OffsetDateTime)) return originalObject;.
@ g.t.w.d - "насколько это глубокое или мелкое". Если это остановился после вызова MemberwiseClone, то оно будет мелким. Он использует это в качестве первого шага, а затем использует информацию об отражении для внесения некоторых рекурсивных изменений в клон. Это превращает мелкий клон в глубокий. (Я предполагаю, что причина первоначального создания MemberwiseClone заключается в том, чтобы избежать необходимости обрабатывать поля, которые на самом деле являются неглубокими - типы полей, которые не нуждаются в рекурсии.)
Обратите внимание, что этот подход можно легко оптимизировать для использования настраиваемого интерфейса, который вы реализуете для ускорения ваших собственных классов. interface IDeepCopy { object DeepCopy(IDictionary<object, object> visited); } Каждый разработчик использует object DeepCopy(visited) { ..check-visited-add-to-visited..; MyType clone = MemberwiseClone; ... }, где "..." рекурсивно повторяется по мере необходимости. Например. clone.myField1 = myField1.DeepCopy(visited); или для полей, отличных от IDeepCopy, выполните вызов InternalCopy, указанный в ответе.
У меня есть идея попроще. Используйте LINQ с новым выбором.
public class Fruit
{
public string Name {get; set;}
public int SeedCount {get; set;}
}
void SomeMethod()
{
List<Fruit> originalFruits = new List<Fruit>();
originalFruits.Add(new Fruit {Name = "Apple", SeedCount=10});
originalFruits.Add(new Fruit {Name = "Banana", SeedCount=0});
//Deep Copy
List<Fruit> deepCopiedFruits = from f in originalFruits
select new Fruit {Name=f.Name, SeedCount=f.SeedCount};
}
Это не проще, когда у вас есть изменяемые ссылочные типы, множество свойств, списки с подсписками и т. д. Это может работать в нескольких простых сценариях, но все равно подвержено ошибкам, когда ваш класс Fruit добавляет другое свойство, а вы забываете изменить свой метод.
Присвоение справочника Name = f.Name - это поверхностное копирование.
если у нас есть 100 свойств, то весь наш день будет потрачен на написание такого кода для одного объекта и так далее ...
@Tarec, если Name является строкой, а строки неизменяемы, это не имеет значения.
Документация MSDN, похоже, намекает, что Clone должен выполнять глубокую копию, но это никогда явно не указывается:
Интерфейс ICloneable содержит один член, Clone, который предназначен для поддержки клонирования помимо того, что предоставляется MemberWiseClone… Метод MemberwiseClone создает неглубокую копию…
Вы можете найти мой пост полезным.
http://pragmaticcoding.com/index.php/cloning-objects-in-c/
Проблема с ICloneable заключается в том, что метод Clone явно не указывает, выполняет ли он поверхностное или глубокое копирование, поэтому вызывающие абоненты никогда не могут быть уверены. Следовательно, есть некоторая [дискуссия | blogs.msdn.com/brada/archive/2004/05/03/125427.a spx] о том, чтобы сделать ICloneable устаревшим в .NET Framework.
Что делает глубокая копия? Копирует ли он битовый поток?