Я хочу сделать что-то вроде:
MyObject myObj = GetMyObj(); // Create and fill a new object
MyObject newObj = myObj.Clone();
А затем внесите в новый объект изменения, которые не отражаются в исходном объекте.
Мне нечасто нужна эта функциональность, поэтому, когда это было необходимо, я прибегал к созданию нового объекта, а затем копировал каждое свойство по отдельности, но всегда оставалось ощущение, что есть лучший или более элегантный способ обработки ситуация.
Как я могу клонировать или глубоко скопировать объект, чтобы клонированный объект можно было изменить без отражения каких-либо изменений в исходном объекте?
Вам стоит взглянуть на AutoMapper
Ваше решение намного сложнее, я потерялся, читая его ... хе-хе-хе. Я использую интерфейс DeepClone. открытый интерфейс IDeepCloneable <T> {T DeepClone (); }
@ Pedro77: Меня беспокоит IDeepCloneable, что не все коллекции ссылок на вещи, которые можно глубоко клонировать, должны быть; правильное поведение при клонировании List<T> зависит не только от T, но и от назначения списков. Если ни один из элементов в списках никогда не подвергнется воздействию чего-либо, что могло бы их изменить, то даже если элементы в списках можно клонировать, было бы лучше скопировать ссылки напрямую.
На этот вопрос также дан ответ здесь stackoverflow.com/q/129389/235715
@ Pedro77 - Хотя, что интересно, в этой статье говорится, что нужно создать метод clone в классе, а затем вызвать внутренний частный конструктор, которому передается this. Так что копирование ужасно [sic], а вот копировать осторожно (а статью определенно стоит прочитать) - нет. ; ^)
Если вам это нужно, возможно, у вас неправильная реализация. А если вы используете инъекцию зависимостей, это вообще не имеет смысла.
В конце концов, этот вопрос и все ответы примерно так же полезны, как «Как написать код для класса?». Есть много ответов, но, несмотря на голоса, нет единого правильного ответа. Это НЕ означает, что никакой ответ бесполезен или вопрос не работает, но остерегайтесь поляризованных ответов. Самым большим недостатком здесь является упор на предоставление подробной документации и на то, чтобы пользователь / разработчик класса брал на себя ответственность за понимание деталей любой операции копирования.
Почему бы просто не получить новый экземпляр? Или, если вы хотите скопировать измененный объект, а не просто создать его экземпляр, вы также можете создать метод, который выполняет все это, и просто вызвать этот метод дважды.
Вызовите метод MemberwiseClone, чтобы создать неглубокую копию объекта, а затем назначьте новые объекты, значения которых совпадают со значениями исходного объекта, любым свойствам или полям, значения которых являются ссылочными типами. Метод DeepCopy в примере иллюстрирует этот подход. msdn.microsoft.com/en-us/library/…
Отметьте этот Отвечать: stackoverflow.com/a/52097307/4707576 about: Клонирование объектов без сериализации





Как правило, вы реализуете интерфейс ICloneable и выполняете Clone самостоятельно. Объекты C# имеют встроенный метод MemberwiseClone, который выполняет неглубокую копию, которая может помочь вам для всех примитивов.
Для глубокой копии он не может знать, как это сделать автоматически.
ICloneable не имеет универсального интерфейса, поэтому использовать этот интерфейс не рекомендуется.
Ваше здоровье.
ICloneable не имеет универсального интерфейса, поэтому использовать этот интерфейс не рекомендуется.
Наилучшие ответы - простые и краткие.
Я предпочитаю конструктор копирования клону. Намерение яснее.
.Net не имеет конструкторов копирования.
Конечно, это так: новый MyObject (objToCloneFrom) Просто объявите ctor, который принимает объект для клонирования в качестве параметра.
Это не одно и то же. Вам нужно добавить его в каждый класс вручную, и вы даже не знаете, получаете ли вы полную копию.
+1 за копию ctor. Вам также нужно вручную написать функцию clone () для каждого типа объекта, и удачи вам с этим, когда ваша иерархия классов станет глубже на несколько уровней.
Однако с конструкторами копирования вы теряете иерархию. agiledeveloper.com/articles/cloning072002.htm
Мне нравится конструктор копирования; но предположим, что у вас есть AbstractBaseType с 3 производными типами, каждый из которых вложен, так что Type1:AbstractBaseType имеет член Type2:AbstractBaseType, который имеет член Type3:AbstractBaseType. Теперь вам нужно проверить типы.
@IAbstract: Зачем нужно проверять типы? Просто положитесь на наследование: pastebin.com/XWGQFBhr ...
Потомкам может потребоваться другая логика, а также некоторая логика if-then или switch. В противном случае вам необходимо предоставить соответствующий базовый конструктор для глубокого клонирования. Конструкторы клонов просто беспорядочные.
@IAbstract: <s> Зачем нужно проверять типы? Просто положитесь на наследование: pastebin.com/XWGQFBhr ... </s> Неважно, после прочтения ссылки Уилла я понимаю проблему, которую вы пытались выразить: члены типа AbstractBaseType не могут быть клонированы с помощью некоторого конструктора копирования в ABT, их копии нужно будет создать с помощью конструктора копирования их конкретного типа ...
Проблема с рукописными методами Copy Constructor и Clone / Copy заключается в обслуживании. Обслуживание, подверженное ошибкам. Представьте DataContract с более чем 50 полями. Представьте, что вы добавляете новое поле или поля. Он может очень быстро превратиться в херню кластера. Любые изменения, которые вы вносите в класс, вы также должны не забывать вносить в конструктор копирования. Вот почему лучше оставить копирование чем-то вроде сериализации или отражения. Было бы неплохо, если бы C# добавил функциональность для самой Deep Copy, но я считаю, что они ее оставили, потому что определение того, что такое Deep Copy, не вырезано и не высушено.
Использование конструктора означает, что копирование / клонирование не может быть частью интерфейса.
@DaveVandenEynde в C++ тоже нужно добавить вручную.
Короткий ответ: вы наследуете интерфейс ICloneable, а затем реализуете функцию .clone. Клонирование должно выполнять поэлементное копирование и выполнять глубокое копирование для любого члена, который требует этого, а затем возвращать полученный объект. Это рекурсивная операция (она требует, чтобы все члены класса, который вы хотите клонировать, были либо типами значений, либо реализовали ICloneable, и чтобы их члены были либо типами значений, либо реализовали ICloneable и т. д.).
Более подробное объяснение клонирования с использованием ICloneable можно найти в Эта статья.
Ответ длинный - «это зависит». Как упоминалось другими, ICloneable не поддерживается универсальными шаблонами, требует особого внимания к циклическим ссылкам на классы и фактически рассматривается некоторыми как "ошибка" в .NET Framework. Метод сериализации зависит от сериализуемости ваших объектов, а это может не быть, и вы можете не контролировать их. В сообществе до сих пор ведутся споры о том, какая практика является «лучшей». На самом деле, ни одно из решений не является универсальным для всех передовых практик для всех ситуаций, как изначально интерпретировалось ICloneable.
См. Этот Статья в Уголке разработчика для еще нескольких опций (кредит Яна).
ICloneable не имеет универсального интерфейса, поэтому использовать этот интерфейс не рекомендуется.
Ваше решение работает до тех пор, пока ему не потребуется обрабатывать циклические ссылки, затем все начинает усложняться, лучше попробовать реализовать глубокое клонирование с использованием глубокой сериализации.
К сожалению, не все объекты также можно сериализовать, поэтому вы не всегда можете использовать этот метод. Ссылка Яна на данный момент является наиболее полным ответом.
Принимая во внимание, что один из подходов - реализовать интерфейс ICloneable (описанный здесь, поэтому я не буду отрыгаться), вот хороший копировщик объектов глубокого клонирования, который я нашел на Кодовый проект некоторое время назад и включил его в наш код.
Как упоминалось в другом месте, для этого требуется, чтобы ваши объекты были сериализуемыми.
using System;
using System.IO;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
/// <summary>
/// Reference Article http://www.codeproject.com/KB/tips/SerializedObjectCloner.aspx
/// Provides a method for performing a deep copy of an object.
/// Binary Serialization is used to perform the copy.
/// </summary>
public static class ObjectCopier
{
/// <summary>
/// Perform a deep copy of the object via serialization.
/// </summary>
/// <typeparam name = "T">The type of object being copied.</typeparam>
/// <param name = "source">The object instance to copy.</param>
/// <returns>A deep copy of the 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 (ReferenceEquals(self, null)) return default;
using var Stream stream = new MemoryStream();
IFormatter formatter = new BinaryFormatter();
formatter.Serialize(stream, source);
stream.Seek(0, SeekOrigin.Begin);
return (T)formatter.Deserialize(stream);
}
}
Идея состоит в том, что он сериализует ваш объект, а затем десериализует его в новый объект. Преимущество в том, что вам не нужно беспокоиться о клонировании всего, когда объект становится слишком сложным.
Если вы предпочитаете использовать новый методы расширения C# 3.0, измените метод так, чтобы он имел следующую сигнатуру:
public static T Clone<T>(this T source)
{
// ...
}
Теперь вызов метода просто становится objectBeingCloned.Clone();.
РЕДАКТИРОВАТЬ (10 января 2015 г.) Подумал, что еще раз вернусь к этому, чтобы упомянуть, что недавно я начал использовать (Newtonsoft) Json для этого, он более легкий должно быть и позволяет избежать накладных расходов на теги [Serializable]. (NB @atconway указал в комментариях, что частные члены не клонируются с использованием метода JSON)
/// <summary>
/// Perform a deep Copy of the object, using Json as a serialization 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 (ReferenceEquals(self, null)) return default;
// 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);
}
Сериализация / десериализация связана со значительными накладными расходами, в которых нет необходимости. См. Интерфейс ICloneable и методы клонирования .MemberWise () в C#.
@David, разрешено, но если объекты легкие, а производительность при их использовании не слишком высока для ваших требований, то это полезный совет. Признаюсь, я не использовал его интенсивно с большими объемами данных в цикле, но я никогда не видел ни одной проблемы с производительностью.
@johnc Мне нравится ваш ответ, потому что он будет работать почти каждый раз, но, как специалист по встроенным системам и помешанный на графике ассемблера x86 середины 90-х, я всегда думаю об оптимизации. Просто помните, что десериализация включает в себя дорогостоящую обработку и отражение строк и что некоторые типы свойств, такие как словари, не могут быть сериализованы / десериализованы. Тем не менее, очень хороший ответ, за который я проголосовал. («Просто помни» - это арахисовая галерея).
Я просто добавлю, что не всегда есть доступ к объекту, чтобы иметь возможность реализовать интерфейс ICloneable, поэтому это решение пригодится.
Вместо «if (! Typeof (T) .IsSerializable)» вы можете написать «public static T Clone <T> (T source), где T: ISerializable
@Amir: на самом деле, нет: typeof(T).IsSerializable также верно, если тип был помечен атрибутом [Serializable]. Необязательно реализовывать интерфейс ISerializable.
Я рекомендую эту сигнатуру метода: public static T Copy <T> (этот элемент T), где T: ISerializable
Как уже упоминалось, здесь, вам нужно будет пометить несериализуемые частные поля / события как [NonSerialized] (или [field: NonSerialized]), чтобы это работало.
@epalm, конечно, требуется сносное знание сериализации, но это довольно легкая кривая обучения
Просто подумал, что упомянул, что, хотя этот метод полезен, и я сам использовал его много раз, он совсем не совместим со средним доверием - так что будьте осторожны, если вы пишете код, который требует совместимости. BinaryFormatter имеет доступ к частным полям и, следовательно, не может работать в наборе разрешений по умолчанию для сред с частичным доверием. Вы можете попробовать другой сериализатор, но убедитесь, что вызывающий объект знает, что клон может быть несовершенным, если входящий объект полагается на закрытые поля.
@David Lively, хотя я знаю, что это не быстрее: если вы сериализуете в JSON вместо этого, вы действительно можете сериализовать словарь, я делаю это все время.
Джонк, к чести @RubenBartelink, правильным номером будет 8 (I предполагать), а не 78611 (хотя мне понравилась умная ссылка на собственный пост). Но очевидно, что вы писали этот очень элегантный ответ, в то время как другие люди давали одни лайнеры и ссылки и, даже 3 ссылки на такую тему - это уже довольно большое количество, чтобы прочитать.
@Cawas Ах, теперь я наконец-то это вижу! Я понятия не имею, были ли голоса так искажены в тот день, но я по-прежнему предпочитаю ответ Яна П, поскольку он указывает на источник (изначально я не предполагал), и я готов потратить 2 минуты выбрать стратегию, когда в цитируемой статье содержится очень глубокий анализ различных способов ее реализации.
@RubenBartelink, а как насчет мой новый ответ? :П
@PederRice По этой причине также может быть невозможно пометить класс как Serializable. В этом случае вам понадобится решение, использующее отражение.
BinaryFormatter небезопасен, посмотрите официальные документы: docs.microsoft.com/en-us/dotnet/api/…
Частные члены не клонируются с использованием метода JSON, этого можно избежать с помощью пакета nuget PrivateSetterContractResolver
Причина не использовать ICloneable - нет, потому что у него нет универсального интерфейса. Причина не использовать его в том, что он расплывчатый. Неясно, получаете ли вы поверхностную или глубокую копию; это зависит от разработчика.
Да, MemberwiseClone является поверхностной копией, но противоположность MemberwiseClone - это не Clone; это был бы, возможно, DeepClone, которого не существует. Когда вы используете объект через его интерфейс ICloneable, вы не можете знать, какой вид клонирования выполняет базовый объект. (И комментарии XML не проясняют это, потому что вы получите комментарии к интерфейсу, а не к методу Clone объекта.)
Обычно я просто создаю метод Copy, который делает именно то, что я хочу.
Я не понимаю, почему ICloneable считается расплывчатым. Учитывая такой тип, как Dictionary (Of T, U), я бы ожидал, что ICloneable.Clone должен выполнять любой уровень глубокого и неглубокого копирования, необходимый для того, чтобы новый словарь был независимым словарем, содержащим те же T и U (содержимое структуры, и / или ссылки на объекты) в качестве оригинала. Где двусмысленность? Конечно, общий ICloneable (Of T), который унаследовал ISelf (Of T), который включал метод «Self», был бы намного лучше, но я не вижу двусмысленности в отношении глубокого и поверхностного клонирования.
Ваш пример иллюстрирует проблему. Предположим, у вас есть Dictionary <string, Customer>. Должен ли клонированный Словарь содержать объекты Заказчика одно и тоже в качестве исходных или копии этих объектов Заказчика? Для любого из них есть разумные варианты использования. Но ICloneable не дает понять, какой из них вы получите. Вот почему это бесполезно.
@Kyralessa В статье Microsoft MSDN на самом деле говорится об этой самой проблеме незнания, запрашиваете ли вы глубокую или неглубокую копию.
Ответ от дубликата stackoverflow.com/questions/129389/… описывает расширение Copy, основанное на рекурсивном MembershipClone
Я придумал это, чтобы преодолеть недостаток .СЕТЬ, когда нужно вручную глубоко копировать List <T>.
Я использую это:
static public IEnumerable<SpotPlacement> CloneList(List<SpotPlacement> spotPlacements)
{
foreach (SpotPlacement sp in spotPlacements)
{
yield return (SpotPlacement)sp.Clone();
}
}
И в другом месте:
public object Clone()
{
OrderItem newOrderItem = new OrderItem();
...
newOrderItem._exactPlacements.AddRange(SpotPlacement.CloneList(_exactPlacements));
...
return newOrderItem;
}
Я попытался придумать один лайнер, который делает это, но это невозможно из-за того, что yield не работает внутри блоков анонимных методов.
Еще лучше использовать общий клонер 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();
}
}
}
У меня были проблемы с использованием ICloneable в Silverlight, но мне понравилась идея серализации, я могу серализовать XML, поэтому я сделал следующее:
static public class SerializeHelper
{
//Michael White, Holly Springs Consulting, 2009
//[email protected]
public static T DeserializeXML<T>(string xmlData) where T:new()
{
if (string.IsNullOrEmpty(xmlData))
return default(T);
TextReader tr = new StringReader(xmlData);
T DocItms = new T();
XmlSerializer xms = new XmlSerializer(DocItms.GetType());
DocItms = (T)xms.Deserialize(tr);
return DocItms == null ? default(T) : DocItms;
}
public static string SeralizeObjectToXML<T>(T xmlObject)
{
StringBuilder sbTR = new StringBuilder();
XmlSerializer xmsTR = new XmlSerializer(xmlObject.GetType());
XmlWriterSettings xwsTR = new XmlWriterSettings();
XmlWriter xmwTR = XmlWriter.Create(sbTR, xwsTR);
xmsTR.Serialize(xmwTR,xmlObject);
return sbTR.ToString();
}
public static T CloneObject<T>(T objClone) where T:new()
{
string GetString = SerializeHelper.SeralizeObjectToXML<T>(objClone);
return SerializeHelper.DeserializeXML<T>(GetString);
}
}
Я тоже видел, как это реализовано через отражение. По сути, существовал метод, который перебирал элементы объекта и соответствующим образом копировал их в новый объект. Когда он достиг ссылочных типов или коллекций, я думаю, он сам себя рекурсивно вызвал. Отражение дорогое, но сработало неплохо.
Простой метод расширения для копирования всех общедоступных свойств. Работает для любых объектов и не требует, чтобы класс был [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 ) } );
}
};
}
Это, к сожалению, ошибочно. Это эквивалентно вызову objectOne.MyProperty = objectTwo.MyProperty (т.е. он просто скопирует ссылку). Он не будет клонировать значения свойств.
to Alex Norcliffe: автор вопроса, заданного о «копировании каждого свойства», а не о клонировании. в большинстве случаев точное дублирование свойств не требуется.
Я думаю об использовании этого метода, но с рекурсией. поэтому, если значение свойства является ссылкой, создайте новый объект и снова вызовите CopyTo. Я просто вижу одну проблему: все используемые классы должны иметь конструктор без параметров. Кто-нибудь уже пробовал это? Мне также интересно, действительно ли это будет работать со свойствами, содержащими классы .net, такие как DataRow и DataTable?
Вот реализация глубокой копии:
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;
}
Это похоже на поэлементное клонирование, потому что не знает свойств ссылочного типа
Если вам нужна невероятно высокая производительность, не выбирайте эту реализацию: она использует отражение, поэтому она будет не такой быстрой. И наоборот, «преждевременная оптимизация - это все зло», поэтому игнорируйте сторону производительности, пока не запустите профилировщик.
CreateInstanceOfType не определяется?
Он не работает с целым числом: «Нестатический метод требует цели».
Следуй этим шагам:
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.
После долгого чтения о многих вариантах, связанных здесь, и возможных решениях этой проблемы, я считаю, что все параметры довольно хорошо обобщены по ссылке Ян П (все другие варианты являются их вариациями), и лучшее решение предоставлено Ссылка Pedro77 в комментариях к вопросу.
Поэтому я просто скопирую здесь соответствующие части из этих двух ссылок. Таким образом мы можем получить:
В первую очередь, это все наши варианты:
В article Fast Deep Copy по деревьям выражений также есть сравнение производительности клонирования с помощью деревьев сериализации, отражения и выражений.
Г-н Венкат Субраманиам (дублирующая ссылка здесь) подробно объясняет, почему.
Вся его статья вращается вокруг примера, который пытается быть применимым для большинства случаев, используя 3 объекта: Человек, Мозг и Город. Мы хотим клонировать человека, у которого будет свой мозг, но тот же город. Вы можете представить себе все проблемы, которые может решить любой из вышеперечисленных методов, или прочитать статью.
Это моя слегка измененная версия его вывода:
Copying an object by specifying
Newfollowed by the class name often leads to code that is not extensible. Using clone, the application of prototype pattern, is a better way to achieve this. However, using clone as it is provided in C# (and Java) can be quite problematic as well. It is better to provide a protected (non-public) copy constructor and invoke that from the clone method. This gives us the ability to delegate the task of creating an object to an instance of a class itself, thus providing extensibility and also, safely creating the objects using the protected copy constructor.
Надеюсь, эта реализация прояснит ситуацию:
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
Обратите внимание: если мы будем вести счет количества объектов, реализованный здесь клон будет вести правильный счет количества объектов.
MS рекомендует не использовать ICloneable для публичных участников. «Поскольку вызывающие объекты Clone не могут зависеть от метода, выполняющего предсказуемую операцию клонирования, мы не рекомендуем реализовывать ICloneable в общедоступных API». msdn.microsoft.com/en-us/library/… Однако, основываясь на объяснении, данном Венкатом Субраманиамом в вашей связанной статье, я думаю, что в этой ситуации имеет смысл использовать до тех пор, пока создатели объектов ICloneable имеют глубокое понимание того, какие свойства должны быть глубокими, а какие - неглубокими копиями. (т.е. глубокая копия Brain, мелкая копия City)
Во-первых, я далек от эксперта в этой теме (публичные API). Я считать на этот раз, что замечание MS имеет большой смысл. И я не думаю, что можно безопасно предполагать, что пользователи этого API будет иметь такое глубокое понимание. Таким образом, имеет смысл реализовать его на общедоступный API только в том случае, если это действительно не имеет значения для тех, кто собирается его использовать. Я Угадай, имеющий какой-то UML, очень явно делающий различие по каждому свойству, мог бы помочь. Но я бы хотел получить известие от кого-то более опытного. :П
Вы можете использовать Генератор клонов CGbR и получить аналогичный результат без написания кода вручную.
Реализация промежуточного языка полезна
В C# нет final
Спасибо за сравнение производительности разных подходов из статьи codeproject.com/Articles/1111658/…
@BateTech Это противоречит принципу, согласно которому люди не должны «просто запоминать» вещи. Вместо использования ICloneable имеет смысл определить свой собственный интерфейс и методы, чтобы делать именно то, что вы хотите. Затем вы можете четко указать в комментариях XML, какое именно клонирование / копирование выполняется. Между прочим, за 15 лет использования .NET в самых разных сферах и сферах деятельности мне почти никогда не приходилось ничего клонировать / копировать.
@RyanLundy Я согласен до некоторой степени, но я бы сказал, что огромный процент успешного программирования - это «просто запоминание / знание» вещей (включая не забывать добавлять XML-комментарии, какие интерфейсы использовать, анит-шаблоны и т. д.). Независимо от того, используете ли вы iCloneable или iMyCustomDeepClone, вам все равно придется иметь дело с тем, какие свойства должны быть глубокими или мелкими копиями в реализации этого интерфейса, bc во многих случаях он не будет полностью глубоким или полностью неглубоким в свойстве уровень. Цель моего комментария заключалась в том, что тот, кто реализует интерфейс, нуждается в этом понимании.
Я также хотел бы добавить в список AutoMapper. Это можно было бы рассматривать как сторонний инструмент, но, поскольку я уже нахожу его потрясающим, вот еще одно его применение.
Если вы уже используете стороннее приложение, такое как ValueInjecter или Automapper, вы можете сделать что-то вроде этого:
MyObject oldObj; // The existing object to clone
MyObject newObj = new MyObject();
newObj.InjectFrom(oldObj); // Using ValueInjecter syntax
Используя этот метод, вам не нужно внедрять ISerializable или ICloneable в свои объекты. Это обычное дело для шаблона MVC / MVVM, поэтому были созданы такие простые инструменты.
Я хотел клонировать очень простые объекты, в основном примитивы и списки. Если ваш объект не поддерживает сериализацию 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);
}
}
решение даже быстрее, чем решение BinaryFormatter, Сравнение производительности сериализации .NET
Спасибо за это. По сути, я смог сделать то же самое с сериализатором BSON, который поставляется с драйвером MongoDB для C#.
Для меня это лучший способ, но я использую Newtonsoft.Json.JsonConvert, но он такой же
Подход JSON подходит для небольших плоских объектов, но исходный вопрос касался любого объекта, включая глубокие. Сериализатор NFX.Slim работает на порядки быстрее с любым типом .NET, если у него нет делегатов и неуправляемых указателей, вот источник, который работает очень похоже на BinaryFormnatter, только как минимум в 5 раз быстрее: github.com/aumcode/nfx/blob/master/Source/NFX/Serialization/… Набор тестов: github.com/aumcode/serbench доказывает, что единственный серийный номер, который работает быстрее, - это Protobuf, которому не хватает динамизма типов и ссылок
Это не работает, если ваш объект является интерфейсом.
Могу я добавить, что если вы хотите сериализовать пользовательские объекты, вам нужно будет украсить все свойства [DataMember], а класс - [DataContract]
Чтобы это работало, объект для клонирования должен быть сериализуемым, как уже упоминалось - это также означает, например, что он может не иметь циклических зависимостей.
Думаю, это лучшее решение, так как реализация применима на большинстве языков программирования.
В DeserializeObject () вы должны добавить дополнительные параметры new JsonSerializerSettings {ObjectCreationHandling = ObjectCreationHandling.Replace}, потому что конструктор по умолчанию может изменить что-то, чего нет в исходном ?
Это скопирует все доступные для чтения и записи свойства объекта в другой.
public class PropertyCopy<TSource, TTarget>
where TSource: class, new()
where TTarget: class, new()
{
public static TTarget Copy(TSource src, TTarget trg, params string[] properties)
{
if (src==null) return trg;
if (trg == null) trg = new TTarget();
var fulllist = src.GetType().GetProperties().Where(c => c.CanWrite && c.CanRead).ToList();
if (properties != null && properties.Count() > 0)
fulllist = fulllist.Where(c => properties.Contains(c.Name)).ToList();
if (fulllist == null || fulllist.Count() == 0) return trg;
fulllist.ForEach(c =>
{
c.SetValue(trg, c.GetValue(src));
});
return trg;
}
}
и вот как вы его используете:
var cloned = Utils.PropertyCopy<TKTicket, TKTicket>.Copy(_tmp, dbsave,
"Creation",
"Description",
"IdTicketStatus",
"IdUserCreated",
"IdUserInCharge",
"IdUserRequested",
"IsUniqueTicketGenerated",
"LastEdit",
"Subject",
"UniqeTicketRequestId",
"Visibility");
или скопировать все:
var cloned = Utils.PropertyCopy<TKTicket, TKTicket>.Copy(_tmp, dbsave);
Я только что создал проект Библиотека CloneExtensions. Он выполняет быстрое и глубокое клонирование с использованием простых операций присваивания, генерируемых компиляцией кода среды выполнения Expression Tree.
Как это использовать?
Вместо того, чтобы писать свои собственные методы Clone или Copy с тоном назначений между полями и свойствами, заставьте программу делать это самостоятельно, используя дерево выражений. Метод GetClone<T>(), помеченный как метод расширения, позволяет вам просто вызвать его в своем экземпляре:
var newInstance = source.GetClone();
Вы можете выбрать, что копировать из source в newInstance, используя перечисление CloningFlags:
var newInstance
= source.GetClone(CloningFlags.Properties | CloningFlags.CollectionItems);
Что можно клонировать?
Следующие члены класса / структуры клонируются внутри:
Насколько это быстро?
Решение быстрее, чем отражение, потому что информация об элементах должна быть собрана только один раз, прежде чем 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>?
Каковы шансы, что это попадет в NuGet? Вроде лучшее решение. Как это по сравнению с NClone?
Думаю, за этот ответ нужно проголосовать еще раз. Внедрение ICloneable вручную утомительно и подвержено ошибкам, использование отражения или сериализации происходит медленно, если важна производительность и вам нужно скопировать тысячи объектов за короткий период времени.
Вовсе нет, вы ошибаетесь насчет отражения, вы должны просто правильно кэшировать это. Отметьте мой ответ ниже stackoverflow.com/a/34368738/4711853
Я создал версию принятого ответа, которая работает как с «[Serializable]», так и с «[DataContract]». Прошло много времени с тех пор, как я его написал, но, если я правильно помню, [DataContract] нуждался в другом сериализаторе.
Требуется Система, System.IO, System.Runtime.Serialization, System.Runtime.Serialization.Formatters.Binary, System.Xml;
public static class ObjectCopier
{
/// <summary>
/// Perform a deep Copy of an object that is marked with '[Serializable]' or '[DataContract]'
/// </summary>
/// <typeparam name = "T">The type of object being copied.</typeparam>
/// <param name = "source">The object instance to copy.</param>
/// <returns>The copied object.</returns>
public static T Clone<T>(T source)
{
if (typeof(T).IsSerializable == true)
{
return CloneUsingSerializable<T>(source);
}
if (IsDataContract(typeof(T)) == true)
{
return CloneUsingDataContracts<T>(source);
}
throw new ArgumentException("The type must be Serializable or use DataContracts.", "source");
}
/// <summary>
/// Perform a deep Copy of an object that is marked with '[Serializable]'
/// </summary>
/// <remarks>
/// Found on http://stackoverflow.com/questions/78536/cloning-objects-in-c-sharp
/// Uses code found on CodeProject, which allows free use in third party apps
/// - http://www.codeproject.com/KB/tips/SerializedObjectCloner.aspx
/// </remarks>
/// <typeparam name = "T">The type of object being copied.</typeparam>
/// <param name = "source">The object instance to copy.</param>
/// <returns>The copied object.</returns>
public static T CloneUsingSerializable<T>(T source)
{
if (!typeof(T).IsSerializable)
{
throw new ArgumentException("The type must be serializable.", "source");
}
// Don't serialize a null object, simply return the default for that object
if (Object.ReferenceEquals(source, null))
{
return default(T);
}
IFormatter formatter = new BinaryFormatter();
Stream stream = new MemoryStream();
using (stream)
{
formatter.Serialize(stream, source);
stream.Seek(0, SeekOrigin.Begin);
return (T)formatter.Deserialize(stream);
}
}
/// <summary>
/// Perform a deep Copy of an object that is marked with '[DataContract]'
/// </summary>
/// <typeparam name = "T">The type of object being copied.</typeparam>
/// <param name = "source">The object instance to copy.</param>
/// <returns>The copied object.</returns>
public static T CloneUsingDataContracts<T>(T source)
{
if (IsDataContract(typeof(T)) == false)
{
throw new ArgumentException("The type must be a data contract.", "source");
}
// ** Don't serialize a null object, simply return the default for that object
if (Object.ReferenceEquals(source, null))
{
return default(T);
}
DataContractSerializer dcs = new DataContractSerializer(typeof(T));
using(Stream stream = new MemoryStream())
{
using (XmlDictionaryWriter writer = XmlDictionaryWriter.CreateBinaryWriter(stream))
{
dcs.WriteObject(writer, source);
writer.Flush();
stream.Seek(0, SeekOrigin.Begin);
using (XmlDictionaryReader reader = XmlDictionaryReader.CreateBinaryReader(stream, XmlDictionaryReaderQuotas.Max))
{
return (T)dcs.ReadObject(reader);
}
}
}
}
/// <summary>
/// Helper function to check if a class is a [DataContract]
/// </summary>
/// <param name = "type">The type of the object to check.</param>
/// <returns>Boolean flag indicating if the class is a DataContract (true) or not (false) </returns>
public static bool IsDataContract(Type type)
{
object[] attributes = type.GetCustomAttributes(typeof(DataContractAttribute), false);
return attributes.Length == 1;
}
}
как насчет того, чтобы просто переделать внутри метода который должен вызывать в основном автоматический конструктор копирования
T t = new T();
T t2 = (T)t; //eh something like that
List<myclass> cloneum;
public void SomeFuncB(ref List<myclass> _mylist)
{
cloneum = new List<myclass>();
cloneum = (List < myclass >) _mylist;
cloneum.Add(new myclass(3));
_mylist = new List<myclass>();
}
мне кажется, работает
Пробовал преобразовать объект со свойствами с простыми типами и ссылочными типами. Сделал только неглубокую копию свойства ссылочного типа.
Чтобы клонировать объект класса, вы можете использовать метод Object.MemberwiseClone,
просто добавьте эту функцию в свой класс:
public class yourClass
{
// ...
// ...
public yourClass DeepCopy()
{
yourClass othercopy = (yourClass)this.MemberwiseClone();
return othercopy;
}
}
затем, чтобы выполнить глубокую независимую копию, просто вызовите метод DeepCopy:
yourClass newLine = oldLine.DeepCopy();
надеюсь это поможет.
Метод MemberwiseClone создает неглубокую копию, а НЕ глубокую копию. msdn.microsoft.com/en-us/library/…
@odyth важный комментарий в качестве фактического кода. Делайте неглубокую копию, здесь хорошая статья о клонировании и примеры для каждого типа geeksforgeeks.org/shallow-copy-and-deep-copy-in-c-sharp.
Обновлено: проект прекращен
Если вы хотите истинное клонирование неизвестных типов, вы можете взглянуть на fastclone.
Это клонирование на основе выражений работает примерно в 10 раз быстрее, чем двоичная сериализация, и поддерживает полную целостность графа объектов.
Это означает: если вы несколько раз ссылаетесь на один и тот же объект в своей иерархии, клон также будет иметь ссылку на один экземпляр.
Нет необходимости в интерфейсах, атрибутах или каких-либо других модификациях клонируемых объектов.
Это кажется довольно полезным
Начать работу с одного снимка кода проще, чем над системой в целом, особенно закрытой. Понятно, что ни одна библиотека не может решить все проблемы одним выстрелом. Следует немного расслабиться.
Я пробовал ваше решение, и оно работает хорошо, спасибо! Думаю, за этот ответ нужно проголосовать еще раз. Внедрение ICloneable вручную утомительно и подвержено ошибкам, использование отражения или сериализации происходит медленно, если важна производительность и вам нужно скопировать тысячи объектов за короткий период времени.
Я попробовал, и у меня ничего не вышло. Вызывает исключение MemberAccess.
Он не работает с более новыми версиями .NET и больше не поддерживается
Мне нравятся такие копиконструкторы:
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));
}
}
Если у вас есть еще что скопировать, добавьте их
Если ваше дерево объектов является сериализуемым, вы также можете использовать что-то вроде этого
static public MyClass Clone(MyClass myClass)
{
MyClass clone;
XmlSerializer ser = new XmlSerializer(typeof(MyClass), _xmlAttributeOverrides);
using (var ms = new MemoryStream())
{
ser.Serialize(ms, myClass);
ms.Position = 0;
clone = (MyClass)ser.Deserialize(ms);
}
return clone;
}
имейте в виду, что это решение довольно простое, но оно не так эффективно, как другие решения.
И убедитесь, что если класс растет, клонируются только те поля, которые также сериализуются.
Невероятно, сколько усилий вы можете потратить с интерфейсом IClonable, особенно если у вас сложная иерархия классов. Также MemberwiseClone работает как-то странно - он не клонирует даже обычные структуры типа List.
И, конечно же, самая интересная дилемма для сериализации - это сериализация обратных ссылок - например, иерархии классов, в которых у вас есть родительско-дочерние отношения. Сомневаюсь, что двоичный сериализатор сможет вам в этом случае помочь. (Это закончится рекурсивными циклами + переполнением стека).
Мне как-то понравилось предложенное здесь решение: Как сделать глубокую копию объекта в .NET (особенно в C#)?
однако - он не поддерживал Списки, добавлял, что поддержка, также учитывала повторное родительство. Для правила только для родителей, которое я создал, это поле или свойство должно быть названо «parent», тогда оно будет проигнорировано DeepClone. Возможно, вы захотите определить свои собственные правила для обратных ссылок - для древовидной иерархии это может быть «левый / правый» и т. д.
Вот весь фрагмент кода, включая тестовый код:
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Text;
namespace TestDeepClone
{
class Program
{
static void Main(string[] args)
{
A a = new A();
a.name = "main_A";
a.b_list.Add(new B(a) { name = "b1" });
a.b_list.Add(new B(a) { name = "b2" });
A a2 = (A)a.DeepClone();
a2.name = "second_A";
// Perform re-parenting manually after deep copy.
foreach( var b in a2.b_list )
b.parent = a2;
Debug.WriteLine("ok");
}
}
public class A
{
public String name = "one";
public List<String> list = new List<string>();
public List<String> null_list;
public List<B> b_list = new List<B>();
private int private_pleaseCopyMeAsWell = 5;
public override string ToString()
{
return "A(" + name + ")";
}
}
public class B
{
public B() { }
public B(A _parent) { parent = _parent; }
public A parent;
public String name = "two";
}
public static class ReflectionEx
{
public static Type GetUnderlyingType(this MemberInfo member)
{
Type type;
switch (member.MemberType)
{
case MemberTypes.Field:
type = ((FieldInfo)member).FieldType;
break;
case MemberTypes.Property:
type = ((PropertyInfo)member).PropertyType;
break;
case MemberTypes.Event:
type = ((EventInfo)member).EventHandlerType;
break;
default:
throw new ArgumentException("member must be if type FieldInfo, PropertyInfo or EventInfo", "member");
}
return Nullable.GetUnderlyingType(type) ?? type;
}
/// <summary>
/// Gets fields and properties into one array.
/// Order of properties / fields will be preserved in order of appearance in class / struct. (MetadataToken is used for sorting such cases)
/// </summary>
/// <param name = "type">Type from which to get</param>
/// <returns>array of fields and properties</returns>
public static MemberInfo[] GetFieldsAndProperties(this Type type)
{
List<MemberInfo> fps = new List<MemberInfo>();
fps.AddRange(type.GetFields());
fps.AddRange(type.GetProperties());
fps = fps.OrderBy(x => x.MetadataToken).ToList();
return fps.ToArray();
}
public static object GetValue(this MemberInfo member, object target)
{
if (member is PropertyInfo)
{
return (member as PropertyInfo).GetValue(target, null);
}
else if (member is FieldInfo)
{
return (member as FieldInfo).GetValue(target);
}
else
{
throw new Exception("member must be either PropertyInfo or FieldInfo");
}
}
public static void SetValue(this MemberInfo member, object target, object value)
{
if (member is PropertyInfo)
{
(member as PropertyInfo).SetValue(target, value, null);
}
else if (member is FieldInfo)
{
(member as FieldInfo).SetValue(target, value);
}
else
{
throw new Exception("destinationMember must be either PropertyInfo or FieldInfo");
}
}
/// <summary>
/// Deep clones specific object.
/// Analogue can be found here: https://stackoverflow.com/questions/129389/how-do-you-do-a-deep-copy-an-object-in-net-c-specifically
/// This is now improved version (list support added)
/// </summary>
/// <param name = "obj">object to be cloned</param>
/// <returns>full copy of object.</returns>
public static object DeepClone(this object obj)
{
if (obj == null)
return null;
Type type = obj.GetType();
if (obj is IList)
{
IList list = ((IList)obj);
IList newlist = (IList)Activator.CreateInstance(obj.GetType(), list.Count);
foreach (object elem in list)
newlist.Add(DeepClone(elem));
return newlist;
} //if
if (type.IsValueType || type == typeof(string))
{
return obj;
}
else if (type.IsArray)
{
Type elementType = Type.GetType(type.FullName.Replace("[]", string.Empty));
var array = obj as Array;
Array copied = Array.CreateInstance(elementType, array.Length);
for (int i = 0; i < array.Length; i++)
copied.SetValue(DeepClone(array.GetValue(i)), i);
return Convert.ChangeType(copied, obj.GetType());
}
else if (type.IsClass)
{
object toret = Activator.CreateInstance(obj.GetType());
MemberInfo[] fields = type.GetFieldsAndProperties();
foreach (MemberInfo field in fields)
{
// Don't clone parent back-reference classes. (Using special kind of naming 'parent'
// to indicate child's parent class.
if (field.Name == "parent")
{
continue;
}
object fieldValue = field.GetValue(obj);
if (fieldValue == null)
continue;
field.SetValue(toret, DeepClone(fieldValue));
}
return toret;
}
else
{
// Don't know that type, don't know how to clone it.
if (Debugger.IsAttached)
Debugger.Break();
return null;
}
} //DeepClone
}
}
Другими словами, пойдите с другим ответом, если у вас нет узкого места производительности, которое нужно исправить, и вы можете доказать это с помощью профилировщика.
Следующий метод выполнения глубокого клонирования:
Для максимальной скорости вы можете использовать Вложенный MemberwiseClone для глубокой копии. Это почти такая же скорость, как копирование структуры значения, и намного быстрее, чем (а) отражение или (б) сериализация (как описано в других ответах на этой странице).
Обратите внимание, что если вы используете Вложенный MemberwiseClone для глубокой копии, вам нужно вручную реализовать ShallowCopy для каждого вложенного уровня в классе и DeepCopy, который вызывает все указанные методы ShallowCopy для создания полного клона. Это просто: всего несколько строк, см. Демонстрационный код ниже.
Вот результат кода, показывающий относительную разницу в производительности для 100000 клонов:
Использование Nested MemberwiseClone в классе почти так же быстро, как копирование структуры, а копирование структуры чертовски близко к теоретической максимальной скорости, на которую способен .NET.
Demo 1 of shallow and deep copy, using classes and MemberwiseClone:
Create Bob
Bob.Age=30, Bob.Purchase.Description=Lamborghini
Clone Bob >> BobsSon
Adjust BobsSon details
BobsSon.Age=2, BobsSon.Purchase.Description=Toy car
Proof of deep copy: If BobsSon is a true clone, then adjusting BobsSon details will not affect Bob:
Bob.Age=30, Bob.Purchase.Description=Lamborghini
Elapsed time: 00:00:04.7795670,30000000
Demo 2 of shallow and deep copy, using structs and value copying:
Create Bob
Bob.Age=30, Bob.Purchase.Description=Lamborghini
Clone Bob >> BobsSon
Adjust BobsSon details:
BobsSon.Age=2, BobsSon.Purchase.Description=Toy car
Proof of deep copy: If BobsSon is a true clone, then adjusting BobsSon details will not affect Bob:
Bob.Age=30, Bob.Purchase.Description=Lamborghini
Elapsed time: 00:00:01.0875454,30000000
Demo 3 of deep copy, using class and serialize/deserialize:
Elapsed time: 00:00:39.9339425,30000000
Чтобы понять, как сделать глубокую копию с помощью MemberwiseCopy, вот демонстрационный проект, который использовался для генерации времени, указанного выше:
// Nested MemberwiseClone example.
// Added to demo how to deep copy a reference class.
[Serializable] // Not required if using MemberwiseClone, only used for speed comparison using serialization.
public class Person
{
public Person(int age, string description)
{
this.Age = age;
this.Purchase.Description = description;
}
[Serializable] // Not required if using MemberwiseClone
public class PurchaseType
{
public string Description;
public PurchaseType ShallowCopy()
{
return (PurchaseType)this.MemberwiseClone();
}
}
public PurchaseType Purchase = new PurchaseType();
public int Age;
// Add this if using nested MemberwiseClone.
// This is a class, which is a reference type, so cloning is more difficult.
public Person ShallowCopy()
{
return (Person)this.MemberwiseClone();
}
// Add this if using nested MemberwiseClone.
// This is a class, which is a reference type, so cloning is more difficult.
public Person DeepCopy()
{
// Clone the root ...
Person other = (Person) this.MemberwiseClone();
// ... then clone the nested class.
other.Purchase = this.Purchase.ShallowCopy();
return other;
}
}
// Added to demo how to copy a value struct (this is easy - a deep copy happens by default)
public struct PersonStruct
{
public PersonStruct(int age, string description)
{
this.Age = age;
this.Purchase.Description = description;
}
public struct PurchaseType
{
public string Description;
}
public PurchaseType Purchase;
public int Age;
// This is a struct, which is a value type, so everything is a clone by default.
public PersonStruct ShallowCopy()
{
return (PersonStruct)this;
}
// This is a struct, which is a value type, so everything is a clone by default.
public PersonStruct DeepCopy()
{
return (PersonStruct)this;
}
}
// Added only for a speed comparison.
public class MyDeepCopy
{
public static T DeepCopy<T>(T obj)
{
object result = null;
using (var ms = new MemoryStream())
{
var formatter = new BinaryFormatter();
formatter.Serialize(ms, obj);
ms.Position = 0;
result = (T)formatter.Deserialize(ms);
ms.Close();
}
return (T)result;
}
}
Затем вызовите демонстрацию из основного:
void MyMain(string[] args)
{
{
Console.Write("Demo 1 of shallow and deep copy, using classes and MemberwiseCopy:\n");
var Bob = new Person(30, "Lamborghini");
Console.Write(" Create Bob\n");
Console.Write(" Bob.Age = {0}, Bob.Purchase.Description = {1}\n", Bob.Age, Bob.Purchase.Description);
Console.Write(" Clone Bob >> BobsSon\n");
var BobsSon = Bob.DeepCopy();
Console.Write(" Adjust BobsSon details\n");
BobsSon.Age = 2;
BobsSon.Purchase.Description = "Toy car";
Console.Write(" BobsSon.Age = {0}, BobsSon.Purchase.Description = {1}\n", BobsSon.Age, BobsSon.Purchase.Description);
Console.Write(" Proof of deep copy: If BobsSon is a true clone, then adjusting BobsSon details will not affect Bob:\n");
Console.Write(" Bob.Age = {0}, Bob.Purchase.Description = {1}\n", Bob.Age, Bob.Purchase.Description);
Debug.Assert(Bob.Age == 30);
Debug.Assert(Bob.Purchase.Description == "Lamborghini");
var sw = new Stopwatch();
sw.Start();
int total = 0;
for (int i = 0; i < 100000; i++)
{
var n = Bob.DeepCopy();
total += n.Age;
}
Console.Write(" Elapsed time: {0},{1}\n\n", sw.Elapsed, total);
}
{
Console.Write("Demo 2 of shallow and deep copy, using structs:\n");
var Bob = new PersonStruct(30, "Lamborghini");
Console.Write(" Create Bob\n");
Console.Write(" Bob.Age = {0}, Bob.Purchase.Description = {1}\n", Bob.Age, Bob.Purchase.Description);
Console.Write(" Clone Bob >> BobsSon\n");
var BobsSon = Bob.DeepCopy();
Console.Write(" Adjust BobsSon details:\n");
BobsSon.Age = 2;
BobsSon.Purchase.Description = "Toy car";
Console.Write(" BobsSon.Age = {0}, BobsSon.Purchase.Description = {1}\n", BobsSon.Age, BobsSon.Purchase.Description);
Console.Write(" Proof of deep copy: If BobsSon is a true clone, then adjusting BobsSon details will not affect Bob:\n");
Console.Write(" Bob.Age = {0}, Bob.Purchase.Description = {1}\n", Bob.Age, Bob.Purchase.Description);
Debug.Assert(Bob.Age == 30);
Debug.Assert(Bob.Purchase.Description == "Lamborghini");
var sw = new Stopwatch();
sw.Start();
int total = 0;
for (int i = 0; i < 100000; i++)
{
var n = Bob.DeepCopy();
total += n.Age;
}
Console.Write(" Elapsed time: {0},{1}\n\n", sw.Elapsed, total);
}
{
Console.Write("Demo 3 of deep copy, using class and serialize/deserialize:\n");
int total = 0;
var sw = new Stopwatch();
sw.Start();
var Bob = new Person(30, "Lamborghini");
for (int i = 0; i < 100000; i++)
{
var BobsSon = MyDeepCopy.DeepCopy<Person>(Bob);
total += BobsSon.Age;
}
Console.Write(" Elapsed time: {0},{1}\n", sw.Elapsed, total);
}
Console.ReadKey();
}
Опять же, обратите внимание, что если вы используете Вложенный MemberwiseClone для глубокой копии, вам нужно вручную реализовать ShallowCopy для каждого вложенного уровня в классе и DeepCopy, который вызывает все указанные методы ShallowCopy для создания полного клона. Это просто: всего несколько строк, см. Демонстрационный код выше.
Обратите внимание, что когда дело доходит до клонирования объекта, существует большая разница между «структура» и «учебный класс»:
См. различия между типами значений и ссылочными типами.
Отличный вариант использования этого кода - подача клонов вложенного класса или структуры в очередь для реализации шаблона производитель / потребитель.
ConcurrentQueue.Это очень хорошо работает на практике и позволяет нам отделить множество потоков (производителей) от одного или нескольких потоков (потребителей).
И этот метод также невероятно быстр: если мы используем вложенные структуры, он в 35 раз быстрее, чем сериализация / десериализация вложенных классов, и позволяет нам использовать все потоки, доступные на машине.
Судя по всему, ExpressMapper работает так же быстро, если не быстрее, чем ручное кодирование, подобное приведенному выше. Возможно, мне придется посмотреть, как они сравниваются с профилировщиком.
Если вы копируете структуру, вы получаете неглубокую копию, вам все равно может потребоваться конкретная реализация для глубокой копии.
@ Лассе В. Карлсен. Да, вы абсолютно правы, я обновил ответ, чтобы было понятнее. Этот метод можно использовать для создания глубоких копий структур классов и. Вы можете запустить включенный пример демонстрационного кода, чтобы показать, как это делается, в нем есть пример глубокого клонирования вложенной структуры и еще один пример глубокого клонирования вложенного класса.
При использовании Marc Gravells protobuf-net в качестве сериализатора принятый ответ требует некоторых незначительных изменений, поскольку копируемый объект не будет отнесен к [Serializable] и, следовательно, не может быть сериализован, а метод Clone вызовет исключение.
Я модифицировал его для работы с protobuf-net:
public static T Clone<T>(this T source)
{
if (Attribute.GetCustomAttribute(typeof(T), typeof(ProtoBuf.ProtoContractAttribute))
== null)
{
throw new ArgumentException("Type has no ProtoContract!", "source");
}
if (Object.ReferenceEquals(source, null))
{
return default(T);
}
IFormatter formatter = ProtoBuf.Serializer.CreateFormatter<T>();
using (Stream stream = new MemoryStream())
{
formatter.Serialize(stream, source);
stream.Seek(0, SeekOrigin.Begin);
return (T)formatter.Deserialize(stream);
}
}
Это проверяет наличие атрибута [ProtoContract] и использует собственное средство форматирования protobufs для сериализации объекта.
Хорошо, в этом посте есть очевидный пример с отражением, НО отражение обычно медленное, пока вы не начнете правильно его кешировать.
если вы кешируете его правильно, то он будет глубоко клонировать 1000000 объектов на 4,6 секунды (измеряется Watcher).
static readonly Dictionary<Type, PropertyInfo[]> ProperyList = new Dictionary<Type, PropertyInfo[]>();
чем вы берете кешированные свойства или добавляете новые в словарь и просто используете их
foreach (var prop in propList)
{
var value = prop.GetValue(source, null);
prop.SetValue(copyInstance, value, null);
}
полная проверка кода в моем сообщении в другом ответе
https://stackoverflow.com/a/34365709/4711853
Вызов prop.GetValue(...) по-прежнему является отражением и не может быть кэширован. В дереве выражений он компилируется, поэтому быстрее
Поскольку мне не удалось найти клонер, который бы отвечал всем моим требованиям в разных проектах, я создал глубокий клонер, который можно настроить и адаптировать к различным структурам кода, вместо того, чтобы адаптировать мой код для удовлетворения требований клонаторов. Это достигается путем добавления аннотаций к коду, который должен быть клонирован, или вы просто оставляете код таким, каким он должен быть по умолчанию. Он использует отражение, кеширование типов и основан на быстрее. Процесс клонирования очень быстр для огромного количества данных и высокой иерархии объектов (по сравнению с другими алгоритмами, основанными на отражении / сериализации).
https://github.com/kalisohn/CloneBehave
Также доступно как пакет nuget: https://www.nuget.org/packages/Clone.Behave/1.0.0
Например: Следующий код будет DeepClone Address, но выполнить только поверхностную копию поля _currentJob.
public class Person
{
[DeepClone(DeepCloneBehavior.Shallow)]
private Job _currentJob;
public string Name { get; set; }
public Job CurrentJob
{
get{ return _currentJob; }
set{ _currentJob = value; }
}
public Person Manager { get; set; }
}
public class Address
{
public Person PersonLivingHere { get; set; }
}
Address adr = new Address();
adr.PersonLivingHere = new Person("John");
adr.PersonLivingHere.BestFriend = new Person("James");
adr.PersonLivingHere.CurrentJob = new Job("Programmer");
Address adrClone = adr.Clone();
//RESULT
adr.PersonLivingHere == adrClone.PersonLivingHere //false
adr.PersonLivingHere.Manager == adrClone.PersonLivingHere.Manager //false
adr.PersonLivingHere.CurrentJob == adrClone.PersonLivingHere.CurrentJob //true
adr.PersonLivingHere.CurrentJob.AnyProperty == adrClone.PersonLivingHere.CurrentJob.AnyProperty //true
Этот метод решил для меня проблему:
private static MyObj DeepCopy(MyObj source)
{
var DeserializeSettings = new JsonSerializerSettings { ObjectCreationHandling = ObjectCreationHandling.Replace };
return JsonConvert.DeserializeObject<MyObj >(JsonConvert.SerializeObject(source), DeserializeSettings);
}
Используйте это так: MyObj a = DeepCopy(b);
Сохраняйте простоту и используйте 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, который такой же короткий, как этот, и работает намного лучше.
Это только мелкая копия.
Мы видели много идей, от сериализации и ручной реализации до отражения, и я хочу предложить совершенно другой подход с использованием Генератор кода CGbR. Метод создания клонов эффективен с точки зрения памяти и ЦП и, следовательно, в 300 раз быстрее стандартного DataContractSerializer.
Все, что вам нужно, это частичное определение класса с ICloneable, а генератор сделает все остальное:
public partial class Root : ICloneable
{
public Root(int number)
{
_number = number;
}
private int _number;
public Partial[] Partials { get; set; }
public IList<ulong> Numbers { get; set; }
public object Clone()
{
return Clone(true);
}
private Root()
{
}
}
public partial class Root
{
public Root Clone(bool deep)
{
var copy = new Root();
// All value types can be simply copied
copy._number = _number;
if (deep)
{
// In a deep clone the references are cloned
var tempPartials = new Partial[Partials.Length];
for (var i = 0; i < Partials.Length; i++)
{
var value = Partials[i];
value = value.Clone(true);
tempPartials[i] = value;
}
copy.Partials = tempPartials;
var tempNumbers = new List<ulong>(Numbers.Count);
for (var i = 0; i < Numbers.Count; i++)
{
var value = Numbers[i];
tempNumbers.Add(value);
}
copy.Numbers = tempNumbers;
}
else
{
// In a shallow clone only references are copied
copy.Partials = Partials;
copy.Numbers = Numbers;
}
return copy;
}
}
Примечание: В последней версии больше нулевых проверок, но я исключил их для лучшего понимания.
Вот быстрое и простое решение, которое сработало для меня, не прибегая к сериализации / десериализации.
public class MyClass
{
public virtual MyClass DeepClone()
{
var returnObj = (MyClass)MemberwiseClone();
var type = returnObj.GetType();
var fieldInfoArray = type.GetRuntimeFields().ToArray();
foreach (var fieldInfo in fieldInfoArray)
{
object sourceFieldValue = fieldInfo.GetValue(this);
if (!(sourceFieldValue is MyClass))
{
continue;
}
var sourceObj = (MyClass)sourceFieldValue;
var clonedObj = sourceObj.DeepClone();
fieldInfo.SetValue(returnObj, clonedObj);
}
return returnObj;
}
}
РЕДАКТИРОВАТЬ: требует
using System.Linq;
using System.Reflection;
Вот как я это использовал
public MyClass Clone(MyClass theObjectIneededToClone)
{
MyClass clonedObj = theObjectIneededToClone.DeepClone();
}
Лучше всего реализовать метод расширения, например
public static T DeepClone<T>(this T originalObject)
{ /* the cloning code */ }
а затем используйте его в любом месте решения,
var copy = anyObject.DeepClone();
У нас могут быть следующие три реализации:
Все связанные методы хорошо работают и были тщательно протестированы.
клонирование кода с использованием деревьев выражений, которые вы опубликовали codeproject.com/Articles/1111658/…, не работает с более новыми версиями .Net framework с исключением безопасности, Операция может дестабилизировать время выполнения, это в основном исключение из-за искаженного дерева выражений, которое используется для генерации Func во время выполнения, пожалуйста, проверьте Если у вас есть какое-то решение.На самом деле я видел проблему только со сложными объектами с глубокой иерархией, простой легко копируется
Реализация ExpressionTree кажется очень хорошей. Он даже работает с круговыми ссылками и закрытыми членами. Атрибуты не нужны. Лучший ответ, который я нашел.
Лучший ответ, сработал очень хорошо, ты спас мне день
Думаю, ты можешь попробовать это.
MyObject myObj = GetMyObj(); // Create and fill a new object
MyObject newObj = new MyObject(myObj); //DeepClone it
Я знаю, что этот вопрос, и отвечать сидит здесь какое-то время, и последующее - не совсем ответ, а скорее наблюдение, на которое я недавно наткнулся, когда проверял, действительно ли частные лица не клонируются (я бы не был собой, если бы не ;) когда я с радостью скопировал @johnc обновленный ответ.
Я просто сделал себе метод расширения (который в значительной степени скопирован из вышеупомянутого ответа):
public static class CloneThroughJsonExtension
{
private static readonly JsonSerializerSettings DeserializeSettings = new JsonSerializerSettings { ObjectCreationHandling = ObjectCreationHandling.Replace };
public static T CloneThroughJson<T>(this T source)
{
return ReferenceEquals(source, null) ? default(T) : JsonConvert.DeserializeObject<T>(JsonConvert.SerializeObject(source), DeserializeSettings);
}
}
и наивно отбросил такой класс (на самом деле их было больше, но они не связаны):
public class WhatTheHeck
{
public string PrivateSet { get; private set; } // matches ctor param name
public string GetOnly { get; } // matches ctor param name
private readonly string _indirectField;
public string Indirect => $"Inception of: {_indirectField} "; // matches ctor param name
public string RealIndirectFieldVaule => _indirectField;
public WhatTheHeck(string privateSet, string getOnly, string indirect)
{
PrivateSet = privateSet;
GetOnly = getOnly;
_indirectField = indirect;
}
}
и такой код:
var clone = new WhatTheHeck("Private-Set-Prop cloned!", "Get-Only-Prop cloned!", "Indirect-Field clonned!").CloneThroughJson();
Console.WriteLine($"1. {clone.PrivateSet}");
Console.WriteLine($"2. {clone.GetOnly}");
Console.WriteLine($"3.1. {clone.Indirect}");
Console.WriteLine($"3.2. {clone.RealIndirectFieldVaule}");
привело к:
1. Private-Set-Prop cloned!
2. Get-Only-Prop cloned!
3.1. Inception of: Inception of: Indirect-Field cloned!
3.2. Inception of: Indirect-Field cloned!
Я подумал: «ЧТО ЗА Х ...», поэтому я взял репозиторий Newtonsoft.Json на Github и начал копать. В результате получается следующее: при десериализации типа, который имеет только один ctor и его имена параметров совпадают (без учета регистра) с именами общедоступных свойств, они будут переданы ctor в качестве этих параметров. Некоторые подсказки можно найти в кодах здесь и здесь.
Нижняя линия
Я знаю, что это довольно необычный случай, и пример кода немного оскорбительный, но эй! Это застало меня врасплох, когда я проверял, не ждет ли в кустах какой-нибудь дракон, чтобы выскочить и укусить меня за задницу. ;)
Картограф выполняет глубокую копию. По каждому члену вашего объекта он создает новый объект и присваивает все его значения. Он работает рекурсивно с каждым непримитивным внутренним членом.
Предлагаю вам один из самых быстрых, активно разрабатываемых на данный момент. Предлагаю UltraMapper https://github.com/maurosampietro/UltraMapper
Пакеты Nuget: https://www.nuget.org/packages/UltraMapper/
Ссылка на решение приветствуется, но убедитесь, что ваш ответ полезен и без нее: добавить контекст вокруг ссылки, чтобы ваши коллеги-пользователи имели некоторое представление о том, что это такое и почему оно есть, а затем процитируйте наиболее релевантную часть страницы, на которую вы ссылаетесь. если целевая страница недоступна. Ответы, которые представляют собой не более чем ссылку, могут быть удалены.
Я нашел новый способ сделать это - Emit.
Мы можем использовать Emit, чтобы добавить IL в приложение и запустить его. Но я не думаю, что это хороший способ, потому что я хочу усовершенствовать это, когда пишу свой ответ.
Emit может видеть официальный документ и Гид
Вы должны выучить некоторый IL, чтобы читать код. Я напишу код, который сможет скопировать свойство в классе.
public static class Clone
{
// ReSharper disable once InconsistentNaming
public static void CloneObjectWithIL<T>(T source, T los)
{
//see http://lindexi.oschina.io/lindexi/post/C-%E4%BD%BF%E7%94%A8Emit%E6%B7%B1%E5%85%8B%E9%9A%86/
if (CachedIl.ContainsKey(typeof(T)))
{
((Action<T, T>) CachedIl[typeof(T)])(source, los);
return;
}
var dynamicMethod = new DynamicMethod("Clone", null, new[] { typeof(T), typeof(T) });
ILGenerator generator = dynamicMethod.GetILGenerator();
foreach (var temp in typeof(T).GetProperties().Where(temp => temp.CanRead && temp.CanWrite))
{
//do not copy static that will except
if (temp.GetAccessors(true)[0].IsStatic)
{
continue;
}
generator.Emit(OpCodes.Ldarg_1);// los
generator.Emit(OpCodes.Ldarg_0);// s
generator.Emit(OpCodes.Callvirt, temp.GetMethod);
generator.Emit(OpCodes.Callvirt, temp.SetMethod);
}
generator.Emit(OpCodes.Ret);
var clone = (Action<T, T>) dynamicMethod.CreateDelegate(typeof(Action<T, T>));
CachedIl[typeof(T)] = clone;
clone(source, los);
}
private static Dictionary<Type, Delegate> CachedIl { set; get; } = new Dictionary<Type, Delegate>();
}
Код может быть полной копией, но может копировать свойство. Если вы хотите сделать глубокую копию, то вы можете изменить ее для IL - это слишком сложно, и я не могу этого сделать.
Еще один ответ JSON.NET. Эта версия работает с классами, которые не реализуют ISerializable.
public static class Cloner
{
public static T Clone<T>(T source)
{
if (ReferenceEquals(source, null))
return default(T);
var settings = new JsonSerializerSettings { ContractResolver = new ContractResolver() };
return JsonConvert.DeserializeObject<T>(JsonConvert.SerializeObject(source, settings), settings);
}
class ContractResolver : DefaultContractResolver
{
protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
{
var props = type.GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)
.Select(p => base.CreateProperty(p, memberSerialization))
.Union(type.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)
.Select(f => base.CreateProperty(f, memberSerialization)))
.ToList();
props.ForEach(p => { p.Writable = true; p.Readable = true; });
return props;
}
}
}
Расширение C#, которое также будет поддерживать типы "not ISerializable".
public static class AppExtensions
{
public static T DeepClone<T>(this T a)
{
using (var stream = new MemoryStream())
{
var serializer = new System.Xml.Serialization.XmlSerializer(typeof(T));
serializer.Serialize(stream, a);
stream.Position = 0;
return (T)serializer.Deserialize(stream);
}
}
}
использование
var obj2 = obj1.DeepClone()
Все общие подходы технически допустимы, но я просто хотел добавить примечание от себя, поскольку нам редко действительно нужна настоящая глубокая копия, и я категорически против использования общего глубокого копирования в реальных бизнес-приложениях, поскольку это делает так, что у вас может быть много места, где объекты копируются, а затем явно изменяются, легко потеряться.
В большинстве реальных ситуаций вы также хотите иметь как можно более детальный контроль над процессом копирования, поскольку вы не только связаны со структурой доступа к данным, но и на практике скопированные бизнес-объекты редко должны быть на 100% одинаковыми. Подумайте о примере referenceId, который используется ORM для идентификации ссылок на объекты, полная глубокая копия также будет копировать этот идентификатор, поэтому в то время как в памяти объекты будут другими, как только вы отправите его в хранилище данных, он будет жаловаться, поэтому вы будете В любом случае необходимо изменить эти свойства вручную после копирования, и если объект изменяется, вам необходимо настроить его во всех местах, где используется общее глубокое копирование.
Расширяя ответ @cregox с помощью ICloneable, что на самом деле представляет собой глубокую копию? Это просто новый выделенный объект в куче, который идентичен исходному объекту, но занимает другое пространство памяти, как таковой, а не с использованием общих функций клонирования, почему бы просто не создать новый объект?
Я лично использую идею статических фабричных методов для объектов моей предметной области.
Пример:
public class Client
{
public string Name { get; set; }
protected Client()
{
}
public static Client Clone(Client copiedClient)
{
return new Client
{
Name = copiedClient.Name
};
}
}
public class Shop
{
public string Name { get; set; }
public string Address { get; set; }
public ICollection<Client> Clients { get; set; }
public static Shop Clone(Shop copiedShop, string newAddress, ICollection<Client> clients)
{
var copiedClients = new List<Client>();
foreach (var client in copiedShop.Clients)
{
copiedClients.Add(Client.Clone(client));
}
return new Shop
{
Name = copiedShop.Name,
Address = newAddress,
Clients = copiedClients
};
}
}
Если кто-то ищет, как он может структурировать создание экземпляров объекта, сохраняя при этом полный контроль над процессом копирования, это решение, с которым я лично очень успешно справился. Защищенные конструкторы также делают это, другие разработчики вынуждены использовать фабричные методы, которые дают аккуратную единую точку создания экземпляра объекта, инкапсулируя логику построения внутри объекта. Вы также можете перегрузить метод и при необходимости использовать несколько логических схем клонирования для разных мест.
Поскольку почти все ответы на этот вопрос были неудовлетворительными или явно не работают в моей ситуации, я создал AnyClone, который полностью реализован с отражением и решил все потребности здесь. Мне не удалось заставить сериализацию работать в сложном сценарии со сложной структурой, а IClonable далеко не идеален - на самом деле в этом даже не должно быть необходимости.
Стандартные атрибуты игнорирования поддерживаются с использованием [IgnoreDataMember], [NonSerialized]. Поддерживает сложные коллекции, свойства без сеттеров, поля только для чтения и т. д.
Я надеюсь, что это поможет кому-то еще, кто столкнулся с теми же проблемами, что и я.
Глубокое клонирование - это копирование государственный. Для .netгосударственный означает поля.
Скажем, у кого-то есть иерархия:
static class RandomHelper
{
private static readonly Random random = new Random();
public static int Next(int maxValue) => random.Next(maxValue);
}
class A
{
private readonly int random = RandomHelper.Next(100);
public override string ToString() => $"{typeof(A).Name}.{nameof(random)} = {random}";
}
class B : A
{
private readonly int random = RandomHelper.Next(100);
public override string ToString() => $"{typeof(B).Name}.{nameof(random)} = {random} {base.ToString()}";
}
class C : B
{
private readonly int random = RandomHelper.Next(100);
public override string ToString() => $"{typeof(C).Name}.{nameof(random)} = {random} {base.ToString()}";
}
Клонирование можно сделать:
static class DeepCloneExtension
{
// consider instance fields, both public and non-public
private static readonly BindingFlags bindingFlags =
BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance;
public static T DeepClone<T>(this T obj) where T : new()
{
var type = obj.GetType();
var result = (T)Activator.CreateInstance(type);
do
// copy all fields
foreach (var field in type.GetFields(bindingFlags))
field.SetValue(result, field.GetValue(obj));
// for every level of hierarchy
while ((type = type.BaseType) != typeof(object));
return result;
}
}
Демо1:
Console.WriteLine(new C());
Console.WriteLine(new C());
var c = new C();
Console.WriteLine($"{Environment.NewLine}Image: {c}{Environment.NewLine}");
Console.WriteLine(new C());
Console.WriteLine(new C());
Console.WriteLine($"{Environment.NewLine}Clone: {c.DeepClone()}{Environment.NewLine}");
Console.WriteLine(new C());
Console.WriteLine(new C());
Результат:
C.random = 92 B.random = 66 A.random = 71
C.random = 36 B.random = 64 A.random = 17
Image: C.random = 96 B.random = 18 A.random = 46
C.random = 60 B.random = 7 A.random = 37
C.random = 78 B.random = 11 A.random = 18
Clone: C.random = 96 B.random = 18 A.random = 46
C.random = 33 B.random = 63 A.random = 38
C.random = 4 B.random = 5 A.random = 79
Обратите внимание, все новые объекты имеют случайные значения для поля random, но clone точно соответствует image.
Демо2:
class D
{
public event EventHandler Event;
public void RaiseEvent() => Event?.Invoke(this, EventArgs.Empty);
}
// ...
var image = new D();
Console.WriteLine($"Created obj #{image.GetHashCode()}");
image.Event += (sender, e) => Console.WriteLine($"Event from obj #{sender.GetHashCode()}");
Console.WriteLine($"Subscribed to event of obj #{image.GetHashCode()}");
image.RaiseEvent();
image.RaiseEvent();
var clone = image.DeepClone();
Console.WriteLine($"obj #{image.GetHashCode()} cloned to obj #{clone.GetHashCode()}");
clone.RaiseEvent();
image.RaiseEvent();
Результат:
Created obj #46104728
Subscribed to event of obj #46104728
Event from obj #46104728
Event from obj #46104728
obj #46104728 cloned to obj #12289376
Event from obj #12289376
Event from obj #46104728
Обратите внимание, поле поддержки событий также копируется, и клиент также подписывается на событие клона.
Это действительно так. Это может вызвать серьезные побочные эффекты, поэтому его следует использовать осторожно.
Используя System.Text.Json:
https://devblogs.microsoft.com/dotnet/try-the-new-system-text-json-apis/
public static T DeepCopy<T>(this T source)
{
return source == null ? default : JsonSerializer.Parse<T>(JsonSerializer.ToString(source));
}
Новый API использует Span<T>. Это должно быть быстро, было бы неплохо провести тесты.
Примечание: нет необходимости в ObjectCreationHandling.Replace, как в Json.NET, поскольку он заменяет значения коллекции по умолчанию. Вам следует забыть о Json.NET сейчас, поскольку все будет заменено новым официальным API.
Я не уверен, что это будет работать с частными полями.
Прочитав все ответы, я был удивлен, что никто не упомянул об этом отличном пакете:
Немного проработав README, вот почему мы выбрали его на работе:
- It can deep or shallow copy
- In deep cloning all object graph is maintained.
- Uses code-generation in runtime, as result cloning is blazingly fast
- Objects copied by internal structure, no methods or ctors called
- You don't need to mark classes somehow (like Serializable-attribute, or implement interfaces)
- No requirement to specify object type for cloning. Object can be casted to interface or as an abstract object (e.g. you can clone array of ints as abstract Array or IEnumerable; even null can be cloned without any errors)
- Cloned object doesn't have any ability to determine that he is clone (except with very specific methods)
var deepClone = new { Id = 1, Name = "222" }.DeepClone();
var shallowClone = new { Id = 1, Name = "222" }.ShallowClone();
README содержит сравнение производительности различных библиотек и методов клонирования: Производительность DeepCloner.
Это довольно старый вопрос. Я думаю, что этот ответ должен повыситься, чтобы люди действительно могли увидеть здесь ценность.
Отказ от ответственности: я являюсь автором упомянутого пакета.
Я был удивлен, как в лучших ответах на этот вопрос в 2019 году до сих пор используются сериализация или отражение.
BinaryFormatter требует атрибута Serializable, JsonConverter требует конструктора или атрибутов без параметров, ни один из них не обрабатывает поля или интерфейсы, доступные только для чтения, и оба работают в 10-30 раз медленнее, чем необходимо.
Вместо этого вы можете использовать Деревья выражений или Отражение. для генерации кода клонирования только один раз, а затем использовать этот скомпилированный код вместо медленного отражения или сериализации.
Столкнувшись с проблемой сам и не увидев удовлетворительного решения, я решил создать пакет, который делает именно это и работает со всеми типами и работает почти так же быстро, как специально написанный код.
Вы можете найти проект на GitHub: https://github.com/marcelltoth/ObjectCloner
Вы можете установить его из NuGet. Либо получите пакет ObjectCloner и используйте его как:
var clone = ObjectCloner.DeepClone(original);
или, если вы не против загрязнения вашего типа объекта расширениями, также получите ObjectCloner.Extensions и напишите:
var clone = original.DeepClone();
Простой тест клонирования иерархии классов показал производительность ~ в 3 раза быстрее, чем при использовании Reflection, ~ в 12 раз быстрее, чем сериализация Newtonsoft.Json и ~ в 36 раз быстрее, чем настоятельно рекомендуемый BinaryFormatter.
Причина, по которой сериализация по-прежнему популярна в 2019 году, заключается в том, что генерация кода работает ТОЛЬКО в надежных средах. Это означает, что он не будет работать в Unity или iOS и, вероятно, никогда не будет. Таким образом, генерация кода не переносима.
Я использовал версию NewtonSoft 12.0.3, у моего класса нет конструктора параметров, и он работает для меня
Самый короткий способ, но нужна зависимость:
using Newtonsoft.Json;
public static T Clone<T>(T source) =>
JsonConvert.DeserializeObject<T>(JsonConvert.SerializeObject(source));
Для процесса клонирования объект может быть сначала преобразован в массив байтов, а затем преобразован обратно в объект.
public static class Extentions
{
public static T Clone<T>(this T obj)
{
byte[] buffer = BinarySerialize(obj);
return (T)BinaryDeserialize(buffer);
}
public static byte[] BinarySerialize(object obj)
{
using (var stream = new MemoryStream())
{
var formatter = new BinaryFormatter();
formatter.Serialize(stream, obj);
return stream.ToArray();
}
}
public static object BinaryDeserialize(byte[] buffer)
{
using (var stream = new MemoryStream(buffer))
{
var formatter = new BinaryFormatter();
return formatter.Deserialize(stream);
}
}
}
Для процесса сериализации объект должен быть сериализован.
[Serializable]
public class MyObject
{
public string Name { get; set; }
}
Использование:
MyObject myObj = GetMyObj();
MyObject newObj = myObj.Clone();
Создайте расширение:
public static T Clone<T>(this T theObject)
{
string jsonData = JsonConvert.SerializeObject(theObject);
return JsonConvert.DeserializeObject<T>(jsonData);
}
И назовите это так:
NewObject = OldObject.Clone();
Дополнение к @Konrad и @craastad с использованием встроенного System.Text.Json для .NET >5
Метод:
public static T Clone<T>(T source)
{
var serialized = JsonSerializer.Serialize(source);
return JsonSerializer.Deserialize<T>(serialized);
}
Способ расширения:
public static class SystemExtension
{
public static T Clone<T>(this T source)
{
var serialized = JsonSerializer.Serialize(source);
return JsonSerializer.Deserialize<T>(serialized);
}
}
Если вы проголосуете против, пожалуйста, добавьте комментарий, почему, иначе сложно улучшить ответы.
Может быть полезно: «Почему копирование объекта - ужасное занятие?» agiledeveloper.com/articles/cloning072002.htm