أريد أن أفعل شيئًا مثل:
MyObject myObj = GetMyObj(); // Create and fill a new object
MyObject newObj = myObj.Clone();
ثم قم بإجراء تغييرات على الكائن الجديد لا تنعكس في الكائن الأصلي.
لا أحتاج في كثير من الأحيان إلى هذه الوظيفة ، لذلك عندما تكون ضرورية ، لجأت إلى إنشاء كائن جديد ثم نسخ كل خاصية على حدة ، لكن ذلك يجعلني أشعر دائمًا بأن هناك طريقة أفضل أو أكثر أناقة للتعامل الوضع.
كيف يمكنني استنساخ كائن أو نسخه عميقة بحيث يمكن تعديل الكائن المستنسخ دون أن تظهر أي تغييرات في الكائن الأصلي؟
في حين أن الممارسة المعتادة هي تطبيق واجهة { ICloneable
(الموصوفة هنا ، لذلك لن أعود إلى الخلف) ، فإليك ناسخة لكائن استنساخ عميق عميق وجدتها في مشروع Code منذ بعض الوقت ودمجها في الاشياء لدينا.
كما ذكر في أي مكان آخر ، فإنه يتطلب كائنات قابلة للتسلسل.
using System;
using System.IO;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
/// <summary>
/// Reference Article http://www.codeproject.com/KB/tips/SerializedObjectCloner.aspx
/// Provides a method for performing a deep copy of an object.
/// Binary Serialization is used to perform the copy.
/// </summary>
public static class ObjectCopier
{
/// <summary>
/// Perform a deep Copy of the object.
/// </summary>
/// <typeparam name="T">The type of object being copied.</typeparam>
/// <param name="source">The object instance to copy.</param>
/// <returns>The copied object.</returns>
public static T Clone<T>(T source)
{
if (!typeof(T).IsSerializable)
{
throw new ArgumentException("The type must be serializable.", "source");
}
// Don't serialize a null object, simply return the default for that object
if (Object.ReferenceEquals(source, null))
{
return default(T);
}
IFormatter formatter = new BinaryFormatter();
Stream stream = new MemoryStream();
using (stream)
{
formatter.Serialize(stream, source);
stream.Seek(0, SeekOrigin.Begin);
return (T)formatter.Deserialize(stream);
}
}
}
تكمن الفكرة في أنه يسلسل الكائن الخاص بك ثم يزيل تسلسله إلى كائن جديد. الفائدة هي أنه لا داعي للقلق بشأن استنساخ كل شيء عندما يصبح الكائن معقدًا للغاية.
وباستخدام طرق التمديد (أيضًا من المصدر المشار إليه في الأصل):
في حال كنت تفضل استخدام طرق التمديد لـ C # 3.0 ، قم بتغيير الطريقة للحصول على التوقيع التالي:
public static T Clone<T>(this T source)
{
//...
}
الآن استدعاء الأسلوب يصبح ببساطة objectBeingCloned.Clone();
.
EDIT(10 كانون الثاني (يناير) 2015) اعتقدت أنني سأعيد النظر في هذا الأمر ، أن أذكر أنني بدأت مؤخرًا باستخدام (Newtonsoft) Json للقيام بذلك ، يجب أن يكون أخف وزنا ، ويتجنب النفقات العامة من [التسلسل] العلامات. (NBatconway أشار في التعليقات إلى أنه لا يتم استنساخ الأعضاء الخاصين باستخدام طريقة JSON)
/// <summary>
/// Perform a deep Copy of the object, using Json as a serialisation method. NOTE: Private members are not cloned using this method.
/// </summary>
/// <typeparam name="T">The type of object being copied.</typeparam>
/// <param name="source">The object instance to copy.</param>
/// <returns>The copied object.</returns>
public static T CloneJson<T>(this T source)
{
// Don't serialize a null object, simply return the default for that object
if (Object.ReferenceEquals(source, null))
{
return default(T);
}
// initialize inner objects individually
// for example in default constructor some list property initialized with some values,
// but in 'source' these items are cleaned -
// without ObjectCreationHandling.Replace default constructor values will be added to result
var deserializeSettings = new JsonSerializerSettings {ObjectCreationHandling = ObjectCreationHandling.Replace};
return JsonConvert.DeserializeObject<T>(JsonConvert.SerializeObject(source), deserializeSettings);
}
كنت أريد شبيهًا بالكائنات البسيطة جدًا والتي تكون في معظمها من العناصر البدائية والقوائم. إذا كان الكائن الخاص بك خارج الصندوق JSON القابل للتسلسل ، فستقوم هذه الطريقة بالخدعة. هذا لا يتطلب أي تعديل أو تنفيذ واجهات على الفئة المستنسخة ، فقط JSON التسلسل مثل JSON.NET.
public static T Clone<T>(T source)
{
var serialized = JsonConvert.SerializeObject(source);
return JsonConvert.DeserializeObject<T>(serialized);
}
أيضا ، يمكنك استخدام هذه الطريقة التمديد
public static class SystemExtension
{
public static T Clone<T>(this T source)
{
var serialized = JsonConvert.SerializeObject(source);
return JsonConvert.DeserializeObject<T>(serialized);
}
}
سبب عدم استخدام ICloneable is لا لأنه لا يحتوي على واجهة عامة. السبب في عدم استخدامه لأنه غامض . لا يوضح ما إذا كنت تحصل على نسخة ضحلة أو عميقة ؛ الأمر متروك للمنفذ.
نعم ، MemberwiseClone
يصنع نسخة ضحلة ، لكن عكس MemberwiseClone
ليس Clone
؛ سيكون ، ربما ، DeepClone
، غير موجود. عند استخدام كائن من خلال واجهة ICloneable الخاصة به ، لا يمكنك معرفة أي نوع من استنساخ الكائن الأساسي ينفذ. (ولن توضح تعليقات XML ، لأنك ستحصل على تعليقات الواجهة بدلاً من التعليقات على طريقة استنساخ الكائن.)
ما أقوم به عادةً هو ببساطة إنشاء طريقة Copy
تعمل بالضبط ما أريده.
بعد قراءة الكثير حول العديد من الخيارات المرتبطة هنا ، والحلول الممكنة لهذه المشكلة ، أعتقد جميع الخيارات ملخصة بشكل جيد في رابط Ian P (جميع الخيارات الأخرى هي صيغ مختلفة لتلك ) والحل الأفضل يتم توفيره بواسطة Pedro77 رابط على تعليقات السؤال.
لذلك أنا فقط نسخ الأجزاء ذات الصلة من تلك المراجع 2 هنا. بهذه الطريقة يمكننا:
أولاً وقبل كل شيء ، تلك هي كل خياراتنا:
تحتوي المقالة Fast Deep Copy by Expression Trees أيضًا على مقارنة أداء الاستنساخ بواسطة تسلسل وتنعكس وأشجار التعبير.
السيد فينكات سوبرامانيام (الرابط الزائد هنا) يشرح بتفصيل كبير لماذا .
تدور جميع مقالاته حول مثال يحاول تطبيقه في معظم الحالات ، باستخدام 3 كائنات: شخص ، مخ و مدينة. نريد استنساخ شخص ، والذي سيكون له دماغه الخاص ولكن في نفس المدينة. يمكنك إما تصوير جميع المشكلات التي يمكن لأي من الطرق الأخرى المذكورة أعلاه إحضار المقالة أو قراءتها.
هذا هو بلدي نسخة معدلة قليلا من استنتاجه:
غالبًا ما يؤدي نسخ كائن عن طريق تحديد
New
متبوعًا باسم الفئة إلى رمز غير قابل للتمديد. باستخدام استنساخ ، وتطبيق نمط النموذج ، هو أفضل وسيلة لتحقيق ذلك. ومع ذلك ، قد يكون استخدام النسخ كما هو موضح في C # (و Java) مشكلة كبيرة أيضًا. من الأفضل توفير مُنشئ نسخ محمي (غير عام) واستدعاء ذلك من طريقة النسخ. هذا يعطينا القدرة على تفويض مهمة إنشاء كائن إلى مثيل لفئة في حد ذاتها ، وبالتالي توفير القابلية للتوسعة وأيضًا إنشاء كائنات بأمان باستخدام مُنشئ النسخ المحمي.
نأمل أن يوضح هذا التطبيق الأمور:
public class Person : ICloneable
{
private final Brain brain; // brain is final since I do not want
// any transplant on it once created!
private int age;
public Person(Brain aBrain, int theAge)
{
brain = aBrain;
age = theAge;
}
protected Person(Person another)
{
Brain refBrain = null;
try
{
refBrain = (Brain) another.brain.clone();
// You can set the brain in the constructor
}
catch(CloneNotSupportedException e) {}
brain = refBrain;
age = another.age;
}
public String toString()
{
return "This is person with " + brain;
// Not meant to sound rude as it reads!
}
public Object clone()
{
return new Person(this);
}
…
}
الآن النظر في وجود فئة مشتقة من شخص.
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 [email protected]
This is person with [email protected]
SkilledPerson: This is person with [email protected]
SkilledPerson: This is person with [email protected]
لاحظ أنه ، إذا احتفظنا بعدد عدد الكائنات ، فسيحتفظ النسخ كما هو مطبق هنا بالعدد الصحيح لعدد الكائنات.
انا افضل نسخة منشئ على استنساخ. القصد أكثر وضوحا.
طريقة تمديد بسيطة لنسخ جميع الممتلكات العامة. يعمل لأي كائنات و لا يتطلب أن يكون الفصل [Serializable]
. يمكن تمديدها لمستوى الوصول الأخرى.
public static void CopyTo( this object S, object T )
{
foreach( var pS in S.GetType().GetProperties() )
{
foreach( var pT in T.GetType().GetProperties() )
{
if( pT.Name != pS.Name ) continue;
( pT.GetSetMethod() ).Invoke( T, new object[]
{ pS.GetGetMethod().Invoke( S, null ) } );
}
};
}
حسنًا ، كنت أواجه مشكلات عند استخدام ICloneable في Silverlight ، لكني أحببت فكرة التسلسل ، يمكنني إجراء تسلسل على XML ، لذلك فعلت هذا:
static public class SerializeHelper
{
//Michael White, Holly Springs Consulting, 2009
//[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);
}
}
لقد قمت للتو بإنشاءCloneExtensions
libraryproject. ينفذ عملية استنساخ سريعة وعميقة باستخدام عمليات التعيين البسيطة التي تم إنشاؤها بواسطة تجميع شفرة وقت تشغيل Expression Tree.
كيفية استخدامها؟
بدلاً من كتابة أساليب Clone
أو Copy
الخاصة بك بنبرة مهام بين الحقول والخصائص ، يجعل البرنامج يقوم بذلك بنفسك ، باستخدام Expression Tree. طريقة GetClone<T>()
التي تم تحديدها كطريقة للتمديد تسمح لك ببساطة بالاتصال بها على سبيل المثال:
var newInstance = source.GetClone();
يمكنك اختيار ما يجب نسخه من source
إلى newInstance
باستخدام CloningFlags
enum:
var newInstance
= source.GetClone(CloningFlags.Properties | CloningFlags.CollectionItems);
ما الذي يمكن استنساخه؟
يتم استنساخ أعضاء الفئة/الهيكل التالية داخليًا:
ما مدى سرعة هو؟
الحل أسرع ثم الانعكاس ، لأنه يجب جمع معلومات الأعضاء مرة واحدة فقط ، قبل استخدام GetClone<T>
لأول مرة لنوع معين T
.
كما أنه أسرع من الحل المستند إلى التسلسل عندما تقوم بنسخ أكثر من مثيلات من النوع نفسه T
.
وأكثر من ذلك ...
اقرأ المزيد حول التعبيرات التي تم إنشاؤها على documentation .
نموذج قائمة تصحيح التعبير لـ 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>
؟
إذا كنت تستخدم بالفعل تطبيقًا لجهة خارجية مثل ValueInjecter أو Automapper ، يمكنك القيام بشيء من هذا القبيل:
MyObject oldObj; // The existing object to clone
MyObject newObj = new MyObject();
newObj.InjectFrom(oldObj); // Using ValueInjecter syntax
باستخدام هذه الطريقة ، لن تحتاج إلى تطبيق ISerializable أو ICloneable على الكائنات الخاصة بك. هذا أمر شائع مع نمط MVC/MVVM ، لذلك تم إنشاء أدوات بسيطة مثل هذه.
الإجابة المختصرة هي أنك ترث من واجهة ICloneable ثم تقوم بتنفيذ وظيفة .clone. يجب أن يقوم Clone بنسخ بطريقة عضو وتنفيذ نسخة عميقة على أي عضو يتطلب ذلك ، ثم يُرجع الكائن الناتج. هذه عملية متكررة (تتطلب أن يكون جميع أعضاء الفئة التي ترغب في استنساخها إما أنواع قيمة أو يقومون بتطبيق ICloneable وأن يكون أعضاؤهم إما أنواع قيمة أو يطبقون ICloneable ، وهكذا).
للحصول على شرح أكثر تفصيلاً حول الاستنساخ باستخدام ICloneable ، تحقق من هذه المقالة .
طويل الجواب "يعتمد". كما ذكر الآخرون ، ICloneable غير مدعوم من قبل الأدوية العامة ، ويتطلب اعتبارات خاصة لمراجع الفصول الدائرية ، وينظر إليه البعض بالفعل على أنه { "خطأ" في .NET Framework. تعتمد طريقة التسلسل على أن الكائنات الخاصة بك قابلة للتسلسل ، وهو ما قد لا يكون كذلك ، وقد لا يكون لديك أي سيطرة عليه. لا يزال هناك الكثير من النقاش في المجتمع حول الممارسة "الأفضل". في الواقع ، لا يوجد أي من الحلول هي المقاس الواحد الذي يناسب جميع أفضل الممارسات لجميع المواقف مثل ICloneable التي تم تفسيرها في الأصل على أنها.
راجع هذا مقالة ركن المطور للحصول على مزيد من الخيارات (ائتمان لإيان).
الأفضل هو تنفيذ طريقة التمديد مثل
public static T DeepClone<T>(this T originalObject)
{ /* the cloning code */ }
ثم استخدمه في أي مكان في الحل بواسطة
var copy = anyObject.DeepClone();
يمكننا الحصول على التطبيقات الثلاثة التالية:
جميع الطرق المرتبطة تعمل بشكل جيد وتم اختبارها بعمق.
في صحتك.
إذا كنت تريد الاستنساخ الحقيقي لأنواع غير معروفة ، يمكنك إلقاء نظرة على fastclone .
هذا الاستنساخ القائم على التعبير يعمل حوالي 10 مرات أسرع من التسلسل الثنائي والحفاظ على سلامة الرسم البياني للكامل.
هذا يعني: إذا قمت بإحالة عدة مرات إلى نفس الكائن في التدرج الهرمي الخاص بك ، فسيتم أيضًا استنساخ حالة واحدة يتم الرجوع إليها.
ليست هناك حاجة للواجهات أو السمات أو أي تعديل آخر على الكائنات التي يتم استنساخها.
اجعل الأمور بسيطة واستخدام 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();
خطرت لي هذا للتغلب على . NET عيب الحاجة إلى نسخة عميقة يدويا قائمة <T>.
انا استعمل هذا:
static public IEnumerable<SpotPlacement> CloneList(List<SpotPlacement> spotPlacements)
{
foreach (SpotPlacement sp in spotPlacements)
{
yield return (SpotPlacement)sp.Clone();
}
}
وفي مكان آخر:
public object Clone()
{
OrderItem newOrderItem = new OrderItem();
...
newOrderItem._exactPlacements.AddRange(SpotPlacement.CloneList(_exactPlacements));
...
return newOrderItem;
}
حاولت الوصول إلى oneliner الذي يقوم بذلك ، لكن هذا غير ممكن ، بسبب عدم العمل داخل كتل أسلوب مجهولة.
الأفضل من ذلك ، استخدم قائمة عامة <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();
}
}
}
وبعبارة أخرى ، انتقل مع إجابة أخرى إلا إذا كان لديك عنق الزجاجة في الأداء الذي يحتاج إلى إصلاح ، ويمكنك إثبات ذلك مع منشئ ملفات التعريف .
الطريقة التالية لأداء استنساخ عميق هي:
للحصول على السرعة القصوى ، يمكنك استخدام Nested MemberwiseClone للقيام بنسخة عميقة . لها تقريبا نفس سرعة نسخ هيكل القيمة ، وأسرع بكثير من (أ) الانعكاس أو (ب) التسلسل (كما هو موضح في الإجابات الأخرى في هذه الصفحة).
لاحظ أن إذا أنت تستخدم عضو متقلب عضو للحصول على نسخة عميقة ، يجب عليك تطبيق ShallowCopy يدويًا لكل مستوى متداخل في الفصل ، و DeepCopy الذي يستدعي جميع أساليب ShallowCopy المذكورة لإنشاء استنساخ كامل. هذا بسيط: فقط بضعة أسطر في المجموع ، راجع رمز العرض التوضيحي أدناه.
فيما يلي ناتج الكود الذي يظهر الفرق في الأداء النسبي لـ 100،000 نسخة:
إن استخدام Nested MemberwiseClone في الفصل الدراسي بأسرع سرعة نسخ بنية ، كما أن نسخ بنية ما يكون أقرب إلى الحد الأقصى للسرعة النظرية.
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();
}
مرة أخرى ، لاحظ أن إذا تستخدمه عضو متقلب عضو للحصول على نسخة عميقة ، يجب عليك تطبيق ShallowCopy يدويًا لكل مستوى متداخل في الفصل ، و DeepCopy الذي يستدعي جميع أساليب ShallowCopy المذكورة لإنشاء استنساخ كامل. هذا بسيط: فقط بضعة أسطر في المجموع ، راجع رمز العرض التوضيحي أعلاه.
لاحظ أنه عندما يتعلق الأمر باستنساخ كائن ، يوجد فرق كبير بين " struct " و " class ":
انظر الاختلافات بين أنواع القيم وأنواع المراجع .
إحدى حالات الاستخدام الممتازة لهذا الرمز هي تغذية الحيوانات المستنسخة من فئة أو بنية متداخلة في قائمة انتظار ، لتنفيذ نمط المنتج/المستهلك.
ConcurrentQueue
.يعمل هذا بشكل جيد للغاية في الممارسة العملية ، ويسمح لنا بفصل العديد من الخيوط (المنتجين) من واحد أو أكثر من سلاسل الرسائل (المستهلكون).
وهذه الطريقة سريعة جدًا: إذا استخدمنا هياكل متداخلة ، فستكون أسرع بمقدار 35 مرة من تسلسل/إلغاء تسلسل الفئات المتداخلة ، وتتيح لنا الاستفادة من جميع سلاسل العمليات المتوفرة على الجهاز.
من الواضح أن ExpressMapper سريع ، إن لم يكن أسرع ، من تشفير اليد كما هو مذكور أعلاه. قد أضطر إلى معرفة كيفية مقارنتها بملف التعريف.
لقد رأيت تنفيذها من خلال التفكير كذلك. في الأساس كانت هناك طريقة من شأنها أن تتكرر من خلال أعضاء كائن ونسخها بشكل مناسب إلى الكائن الجديد. عندما وصلت إلى أنواع أو مجموعات المراجع ، أعتقد أنها قامت بإجراء مكالمة متكررة على نفسها. التفكير باهظ الثمن ، لكنه كان جيدًا.
نظرًا لأنني لم أتمكن من العثور على cloner الذي يلبي جميع متطلباتي في مشاريع مختلفة ، فقد أنشأت cloner عميقًا يمكن تهيئته وتكييفه وفقًا لهياكل رمز مختلفة بدلاً من تكييف الشفرة الخاصة بي لتلبية متطلبات cloners. تم تحقيق ذلك عن طريق إضافة التعليقات التوضيحية إلى الكود الذي سيتم استنساخه أو ترك الكود لأنه يحتوي على السلوك الافتراضي. ويستخدم الانعكاس ، اكتب ذاكرة التخزين المؤقت ويستند بشكل أسرع . تعتبر عملية الاستنساخ سريعة جدًا بالنسبة إلى كمية هائلة من البيانات وتسلسل هرمي للكائن العالي (مقارنة بخوارزميات تستند إلى الانعكاس/التسلسل).
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
بشكل عام ، يمكنك تنفيذ واجهة ICloneable وتنفيذ استنساخ نفسك. تحتوي الكائنات C # على طريقة MemberwiseClone مضمّنة تقوم بتنفيذ نسخة ضحلة يمكن أن تساعدك في الحصول على جميع العناصر الأولية.
بالنسبة للنسخة العميقة ، لا توجد طريقة لمعرفة كيفية القيام بذلك تلقائيًا.
هنا هو تنفيذ نسخة عميقة:
public static object CloneObject(object opSource)
{
//grab the type and create a new instance of that type
Type opSourceType = opSource.GetType();
object opTarget = CreateInstanceOfType(opSourceType);
//grab the properties
PropertyInfo[] opPropertyInfo = opSourceType.GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
//iterate over the properties and if it has a 'set' method assign it from the source TO the target
foreach (PropertyInfo item in opPropertyInfo)
{
if (item.CanWrite)
{
//value types can simply be 'set'
if (item.PropertyType.IsValueType || item.PropertyType.IsEnum || item.PropertyType.Equals(typeof(System.String)))
{
item.SetValue(opTarget, item.GetValue(opSource, null), null);
}
//object/complex types need to recursively call this method until the end of the tree is reached
else
{
object opPropertyValue = item.GetValue(opSource, null);
if (opPropertyValue == null)
{
item.SetValue(opTarget, null, null);
}
else
{
item.SetValue(opTarget, CloneObject(opPropertyValue), null);
}
}
}
}
//return the new item
return opTarget;
}
هذه الطريقة حل المشكلة بالنسبة لي:
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);
أنا أحب Copyconstructors مثل هذا:
public AnyObject(AnyObject anyObject)
{
foreach (var property in typeof(AnyObject).GetProperties())
{
property.SetValue(this, property.GetValue(anyObject));
}
foreach (var field in typeof(AnyObject).GetFields())
{
field.SetValue(this, field.GetValue(anyObject));
}
}
إذا كان لديك المزيد من الأشياء لنسخ إضافتها
لقد رأينا الكثير من الأفكار من التسلسل على التنفيذ اليدوي إلى التفكير وأريد أن أقترح طريقة مختلفة تمامًا باستخدام CGbR Code Generator . طريقة استنساخ توليد هي الذاكرة وحدة المعالجة المركزية وفعالة لذلك 300X أسرع مثل 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();
}
أعتقد أنك يمكن أن تجرب هذا.
MyObject myObj = GetMyObj(); // Create and fill a new object
MyObject newObj = new MyObject(myObj); //DeepClone it
اتبع هذه الخطوات:
ISelf<T>
بخاصية Self
للقراءة فقط والتي تُرجع T
و ICloneable<out T>
، والتي تستمد من ISelf<T>
وتتضمن طريقة T Clone()
.CloneBase
الذي يقوم بتنفيذ protected virtual generic VirtualClone
cast MemberwiseClone
إلى النوع الذي تم تمريره.VirtualClone
عن طريق استدعاء طريقة الاستنساخ الأساسي ثم القيام بكل ما يجب القيام به لاستنساخ تلك الجوانب من النوع المشتق بشكل صحيح والتي لم تتم معالجة طريقة VirtualClone الأصل بعد.لتحقيق أقصى قدر من التنوع في الميراث ، يجب أن تكون الفئات التي تعرض وظائف الاستنساخ العام sealed
، ولكنها مستمدة من فئة أساسية مماثلة على خلاف ذلك باستثناء نقص الاستنساخ. بدلاً من تمرير متغيرات من النوع clonable الصريح ، تأخذ معلمة من النوع ICloneable<theNonCloneableType>
. سيسمح هذا للروتين الذي يتوقع مشتق قابل للاستنساخ لـ Foo
بالعمل مع مشتق قابل للاستنساخ لـ DerivedFoo
، ولكنه يسمح أيضًا بإنشاء مشتقات غير قابلة للاستنساخ Foo
.
لقد قمت بإنشاء إصدار من الإجابة المقبولة التي تعمل مع كل من "[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;
}
}
إذا كانت شجرة الكائنات الخاصة بك متسلسلة ، فيمكنك أيضًا استخدام شيء مثل هذا
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;
}
كن على علم بأن هذا الحل سهل للغاية ولكنه ليس بنفس الأداء الذي قد تكون عليه الحلول الأخرى.
وتأكد من أنه في حالة نمو الفئة ، فسيظل هناك فقط تلك الحقول المستنسخة ، والتي يتم تسلسلها أيضًا.
لاستنساخ كائن الفئة الخاصة بك ، يمكنك استخدام الأسلوب Object.MemberwiseClone ،
فقط أضف هذه الوظيفة إلى صفك:
public class yourClass
{
// ...
// ...
public yourClass DeepCopy()
{
yourClass othercopy = (yourClass)this.MemberwiseClone();
return othercopy;
}
}
ثم لأداء نسخة مستقلة عميقة ، ما عليك سوى استدعاء طريقة DeepCopy:
yourClass newLine = oldLine.DeepCopy();
أتمنى أن يساعدك هذا.
حسنًا ، هناك بعض الأمثلة الواضحة مع انعكاس في هذا المنشور ، ولكن عادة ما يكون الانعكاس بطيئًا ، حتى تبدأ في التخزين المؤقت بشكل صحيح.
إذا كنت ستقوم بتخزينها مؤقتًا بشكل صحيح ، فستكون أكثر استنساخًا عميقًا لكائن 1000000 بمقدار 4،6 ثانية (تقاس بواسطة Watcher).
static readonly Dictionary<Type, PropertyInfo[]> ProperyList = new Dictionary<Type, PropertyInfo[]>();
مما كنت تأخذ الخصائص المخزنة مؤقتا أو إضافة جديدة إلى القاموس واستخدامها ببساطة
foreach (var prop in propList)
{
var value = prop.GetValue(source, null);
prop.SetValue(copyInstance, value, null);
}
تحقق من الرمز الكامل في مشاركتي في إجابة أخرى
نظرًا لأن جميع الإجابات على هذا السؤال كانت تقريبًا غير مرضية أو لا تعمل بشكل واضح في وضعي ، فقد قمت بتأليف AnyClone والذي يتم تنفيذه بالكامل مع التفكير وحل جميع الاحتياجات هنا. لم أتمكن من جعل التسلسل يعمل في سيناريو معقد بهيكل معقد ، و IClonable
أقل من مثالي - في الواقع لا ينبغي أن يكون ضروريًا.
يتم دعم سمات التجاهل القياسية باستخدام [IgnoreDataMember]
و [NonSerialized]
. يدعم المجموعات المعقدة ، والممتلكات دون المستوطنين ، والحقول للقراءة فقط إلخ.
آمل أن يساعد شخص آخر هناك واجه نفس المشاكل التي واجهتها.
عند استخدام 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 لتسلسل الكائن.
ملحق C # الذي سيدعم أنواع "لا قابل للتسجيل " أيضًا.
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()
من غير المعقول مقدار الجهد الذي يمكنك إنفاقه مع واجهة IClonable - خاصةً إذا كان لديك تسلسل هرمي للفئة الثقيلة. أيضا MemberwiseClone يعمل بطريقة غريبة - فهو لا يستنسخ بالضبط حتى نوع القائمة العادية من الهياكل.
وبالطبع فإن معضلة التسلسل الأكثر إثارة للاهتمام هي إجراء تسلسل للمراجع الخلفية - على سبيل المثال تسلسل هرمي للفئة حيث لديك علاقات بين الوالدين والطفل. أشك في أن برنامج التسلسل الثنائي سيكون قادرًا على مساعدتك في هذه الحالة. (سينتهي الأمر بحلقات متكررة + تجاوز سعة مكدس).
أعجبني الحل المقترح هنا: كيف يمكنك القيام بنسخة عميقة من كائن في .NET (C # على وجه التحديد)؟
ومع ذلك - لم يدعم القوائم ، وأضاف أن الدعم ، كما أخذت في الاعتبار إعادة الأبوة والأمومة. بالنسبة إلى الأبوة فقط ، يجب تسمية هذا الحقل أو الخاصية باسم "أصل" ، بحيث يتم تجاهلها بواسطة 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
}
}
إجابة 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;
}
}
}
يقوم معين معين بنسخة عميقة. بالنسبة إلى كائن عضو في Foreach ، فإنه ينشئ كائنًا جديدًا ويعين جميع قيمه. وهي تعمل بشكل متكرر على كل عضو داخلي غير بدائي.
أقترح عليك واحدة من الأسرع ، وضعت حاليا بنشاط. أقترح UltraMapper https://github.com/maurosampietro/UltraMapper
حزم Nuget: https://www.nuget.org/packages/UltraMapper/
جميع الأساليب العامة صالحة من الناحية الفنية ، لكنني أردت فقط إضافة ملاحظة من نفسي لأننا نادراً ما نحتاج في الواقع إلى نسخة عميقة حقيقية ، وأعارض بشدة استخدام نسخ عميقة عامة في تطبيقات الأعمال الفعلية لأن ذلك يجعلها كثيرة وبالتالي قد يكون لديك الكثير الأماكن التي يتم نسخ الكائنات وتعديلها بشكل صريح ، من السهل أن تضيع.
في معظم مواقف الحياة الواقعية ، ترغب أيضًا في الحصول على أكبر قدر ممكن من التحكم الدقيق في عملية النسخ نظرًا لأنك لا تقترن فقط بإطار الوصول إلى البيانات ولكن أيضًا في الممارسة العملية ، نادراً ما تكون كائنات الأعمال المنسوخة 100٪ كما هي. فكر في مثال لمرجع ، يستخدمه ORM لتحديد مراجع الكائنات ، وستعمل نسخة عميقة كاملة أيضًا على نسخ هذا المعرف ، بينما ستكون الكائنات في الذاكرة مختلفة ، بمجرد إرساله إلى مخزن البيانات ، سوف يشكو ، لذلك سوف يجب عليك تعديل هذه الخصائص يدويًا بعد النسخ على أي حال ، وإذا تغير الكائن تحتاج إلى تعديله في جميع الأماكن التي تستخدم النسخ العميق العمومي.
التوسع في إجابةcregox مع ICloneable ، ما هو في الواقع نسخة عميقة؟ إنه مجرد كائن تم تخصيصه حديثًا على الكومة مماثل للكائن الأصلي ولكنه يشغل مساحة ذاكرة مختلفة ، بدلاً من استخدام وظيفة cloner عامة ، لماذا لا تقوم فقط بإنشاء كائن جديد؟
أنا شخصيا استخدم فكرة أساليب المصنع الثابتة على كائنات المجال الخاص بي.
مثال:
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
};
}
}
إذا كان شخص ما يبحث عن كيف يمكنه تنظيم إنشاء مثيل للعنصر مع الاحتفاظ بالتحكم الكامل في عملية النسخ ، فهذا حل نجحت شخصيًا معه. كما أن المُنشئين المحميين يقومون بذلك ، يضطر مطورو البرامج الآخرون إلى استخدام أساليب المصنع التي تعطي نقطة واحدة دقيقة من إنشاء مثيل الكائن الذي يحوي منطق البناء داخل الكائن. يمكنك أيضًا زيادة تحميل الطريقة والحصول على العديد من منطق الاستنساخ لأماكن مختلفة إذا لزم الأمر.
لقد وجدت طريقة جديدة للقيام بذلك وهي 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 ، فمن الصعب للغاية أنني لا أستطيع القيام بذلك.
ماذا عن مجرد إعادة صياغة داخل الطريقة التي يجب أن تستدعي أساسا منشئ نسخة تلقائية
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>();
}
يبدو أن العمل بالنسبة لي
سيؤدي هذا إلى نسخ جميع الخصائص القابلة للقراءة والكتابة لكائن إلى آخر.
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);