سؤال

أريد أن أفعل شيئًا مثل:

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

ثم قم بإجراء تغييرات على الكائن الجديد لا تنعكس في الكائن الأصلي.

لا أحتاج في كثير من الأحيان إلى هذه الوظيفة، لذلك عندما يكون ذلك ضروريًا، لجأت إلى إنشاء كائن جديد ثم نسخ كل خاصية على حدة، لكن هذا يتركني دائمًا أشعر بوجود طريقة أفضل أو أكثر أناقة للتعامل الوضع.

كيف يمكنني استنساخ كائن أو نسخه بعمق بحيث يمكن تعديل الكائن المستنسخ دون أن تنعكس أي تغييرات على الكائن الأصلي؟

هل كانت مفيدة؟

المحلول

في حين أن الممارسة القياسية هي تنفيذ ICloneable واجهة (الموصوفة هنا, ، لذلك لن أتقيأ)، إليك آلة ناسخة لطيفة لكائنات مستنسخة عميقة وجدتها مشروع الكود منذ فترة وأدرجناها في أغراضنا.

كما ذكرنا في مكان آخر، فإنه يتطلب أن تكون كائناتك قابلة للتسلسل.

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

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

        // Don't serialize a null object, simply return the default for that object
        if (Object.ReferenceEquals(source, null))
        {
            return default(T);
        }

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

الفكرة هي أنه يقوم بتسلسل الكائن الخاص بك ثم إلغاء تسلسله إلى كائن جديد.الفائدة هي أنه لا داعي للقلق بشأن استنساخ كل شيء عندما يصبح الكائن معقدًا للغاية.

ومع استخدام طرق الامتداد (أيضًا من المصدر الأصلي المشار إليه):

في حال كنت تفضل استخدام الجديد طرق التمديد في C# 3.0، قم بتغيير الطريقة للحصول على التوقيع التالي:

public static T Clone<T>(this T source)
{
   //...
}

الآن يصبح استدعاء الأسلوب ببساطة objectBeingCloned.Clone();.

يحرر (10 كانون الثاني (يناير) 2015) أعتقد أنني سأعيد النظر في هذا، لأذكر أنني بدأت مؤخرًا في استخدام (Newtonsoft) Json للقيام بذلك، إنه يجب ان يكون أخف وزنًا، ويتجنب الحمل الزائد للعلامات [القابلة للتسلسل].(ملحوظة: أشارatconway في التعليقات إلى أنه لا يتم استنساخ الأعضاء الخاصين باستخدام طريقة JSON)

/// <summary>
/// Perform a deep Copy of the object, using Json as a serialisation method. NOTE: Private members are not cloned using this method.
/// </summary>
/// <typeparam name="T">The type of object being copied.</typeparam>
/// <param name="source">The object instance to copy.</param>
/// <returns>The copied object.</returns>
public static T CloneJson<T>(this T source)
{            
    // Don't serialize a null object, simply return the default for that object
    if (Object.ReferenceEquals(source, null))
    {
        return default(T);
    }

    // initialize inner objects individually
    // for example in default constructor some list property initialized with some values,
    // but in 'source' these items are cleaned -
    // without ObjectCreationHandling.Replace default constructor values will be added to result
    var deserializeSettings = new JsonSerializerSettings {ObjectCreationHandling = ObjectCreationHandling.Replace};

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

نصائح أخرى

كنت أرغب في استنساخ كائنات بسيطة جدًا تتكون في الغالب من الأوليات والقوائم.إذا كان الكائن الخاص بك خارج الصندوق وقابلاً للتسلسل 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 يكون لا لأنه لا يحتوي على واجهة عامة. سبب عدم استخدامه هو أنه غامض.لا يوضح ما إذا كنت ستحصل على نسخة سطحية أم عميقة؛هذا متروك للمنفذ.

نعم، MemberwiseClone يجعل نسخة ضحلة، ولكن على العكس من ذلك MemberwiseClone ليس كذلك Clone;سيكون، ربما، DeepClone, ، وهو غير موجود.عند استخدام كائن من خلال واجهة ICloneable الخاصة به، لا يمكنك معرفة نوع الاستنساخ الذي يتم تنفيذه للكائن الأساسي.(ولن توضح تعليقات XML الأمر، لأنك ستحصل على تعليقات الواجهة بدلاً من تلك الموجودة في طريقة Clone للكائن.)

ما أفعله عادةً هو ببساطة إجراء Copy الطريقة التي تفعل بالضبط ما أريد.

بعد الكثير من القراءة حول العديد من الخيارات المرتبطة هنا، والحلول الممكنة لهذه المشكلة، على ما أعتقد يتم تلخيص كافة الخيارات بشكل جيد في إيان بالرابط (جميع الخيارات الأخرى هي أشكال مختلفة من تلك) ويتم توفير الحل الأفضل بواسطة بيدرو77الرابط على تعليقات السؤال.

لذلك سأقوم فقط بنسخ الأجزاء ذات الصلة من هذين المرجعين هنا.بهذه الطريقة يمكن أن يكون لدينا:

أفضل ما يمكنك فعله لاستنساخ الكائنات في لغة C Sharp!

أولاً وقبل كل شيء، هذه هي كل خياراتنا:

ال مقالة النسخ العميق السريع بواسطة أشجار التعبير يحتوي أيضًا على مقارنة أداء الاستنساخ عن طريق أشجار التسلسل والانعكاس والتعبير.

لماذا اخترت ICloneable (أي.يدويا)

يشرح السيد فينكات سوبرامانيام (رابط زائد هنا) بتفصيل كبير السبب.

تدور كل مقالته حول مثال يحاول أن يكون قابلاً للتطبيق في معظم الحالات، باستخدام 3 كائنات: شخص, مخ و مدينة.نريد استنساخ شخص سيكون له عقله الخاص ولكن نفس المدينة.يمكنك إما تصوير جميع المشكلات التي يمكن أن تسببها أي من الطرق الأخرى المذكورة أعلاه أو قراءة المقالة.

هذه هي نسختي المعدلة قليلاً من استنتاجه:

نسخ كائن عن طريق التحديد New غالبًا ما يؤدي اسم الفئة المتبوعًا إلى تعليمات برمجية غير قابلة للتوسيع.يعد استخدام الاستنساخ، وتطبيق نموذج النموذج الأولي، طريقة أفضل لتحقيق ذلك.ومع ذلك، فإن استخدام الاستنساخ كما هو منصوص عليه في C# (وJava) يمكن أن يكون مشكلة كبيرة أيضًا.من الأفضل توفير مُنشئ نسخة محمي (غير عام) واستدعاء ذلك من طريقة الاستنساخ.يمنحنا هذا القدرة على تفويض مهمة إنشاء كائن إلى مثيل للفئة نفسها، وبالتالي توفير القابلية للتوسعة وأيضًا إنشاء الكائنات بأمان باستخدام مُنشئ النسخة المحمية.

نأمل أن يؤدي هذا التنفيذ إلى توضيح الأمور:

public class Person : ICloneable
{
    private final Brain brain; // brain is final since I do not want 
                // any transplant on it once created!
    private int age;
    public Person(Brain aBrain, int theAge)
    {
        brain = aBrain; 
        age = theAge;
    }
    protected Person(Person another)
    {
        Brain refBrain = null;
        try
        {
            refBrain = (Brain) another.brain.clone();
            // You can set the brain in the constructor
        }
        catch(CloneNotSupportedException e) {}
        brain = refBrain;
        age = another.age;
    }
    public String toString()
    {
        return "This is person with " + brain;
        // Not meant to sound rude as it reads!
    }
    public Object clone()
    {
        return new Person(this);
    }
    …
}

الآن فكر في الحصول على فئة مشتقة من Person.

public class SkilledPerson extends Person
{
    private String theSkills;
    public SkilledPerson(Brain aBrain, int theAge, String skills)
    {
        super(aBrain, theAge);
        theSkills = skills;
    }
    protected SkilledPerson(SkilledPerson another)
    {
        super(another);
        theSkills = another.theSkills;
    }

    public Object clone()
    {
        return new SkilledPerson(this);
    }
    public String toString()
    {
        return "SkilledPerson: " + super.toString();
    }
}

يمكنك تجربة تشغيل الكود التالي:

public class User
{
    public static void play(Person p)
    {
        Person another = (Person) p.clone();
        System.out.println(p);
        System.out.println(another);
    }
    public static void main(String[] args)
    {
        Person sam = new Person(new Brain(), 1);
        play(sam);
        SkilledPerson bob = new SkilledPerson(new SmarterBrain(), 1, "Writer");
        play(bob);
    }
}

الناتج الناتج سيكون:

This is person with Brain@1fcc69
This is person with Brain@253498
SkilledPerson: This is person with SmarterBrain@1fef6f
SkilledPerson: This is person with SmarterBrain@209f4e

لاحظ أنه إذا احتفظنا بعدد الكائنات، فإن النسخ كما تم تنفيذه هنا سيحتفظ بالعدد الصحيح لعدد الكائنات.

أفضّل مُنشئ النسخ على الاستنساخ.القصد أوضح.

طريقة تمديد بسيطة لنسخ كافة الخصائص العامة.يعمل لأي كائنات و لا تتطلب أن تكون الطبقة [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
    //michael@hollyspringsconsulting.com
    public static T DeserializeXML<T>(string xmlData) where T:new()
    {
        if (string.IsNullOrEmpty(xmlData))
            return default(T);

        TextReader tr = new StringReader(xmlData);
        T DocItms = new T();
        XmlSerializer xms = new XmlSerializer(DocItms.GetType());
        DocItms = (T)xms.Deserialize(tr);

        return DocItms == null ? default(T) : DocItms;
    }

    public static string SeralizeObjectToXML<T>(T xmlObject)
    {
        StringBuilder sbTR = new StringBuilder();
        XmlSerializer xmsTR = new XmlSerializer(xmlObject.GetType());
        XmlWriterSettings xwsTR = new XmlWriterSettings();

        XmlWriter xmwTR = XmlWriter.Create(sbTR, xwsTR);
        xmsTR.Serialize(xmwTR,xmlObject);

        return sbTR.ToString();
    }

    public static T CloneObject<T>(T objClone) where T:new()
    {
        string GetString = SerializeHelper.SeralizeObjectToXML<T>(objClone);
        return SerializeHelper.DeserializeXML<T>(GetString);
    }
}

لقد خلقت للتو CloneExtensions مكتبة مشروع.إنه ينفذ استنساخًا سريعًا وعميقًا باستخدام عمليات التعيين البسيطة التي تم إنشاؤها بواسطة تجميع التعليمات البرمجية لوقت تشغيل Expression Tree.

كيفية استخدامها؟

بدلا من الكتابة الخاصة بك Clone أو Copy الأساليب مع نغمة التعيينات بين الحقول والخصائص تجعل البرنامج يقوم بذلك بنفسك باستخدام Expression Tree. GetClone<T>() الطريقة التي تم وضع علامة عليها كطريقة ملحقة تسمح لك باستدعائها ببساطة على المثيل الخاص بك:

var newInstance = source.GetClone();

يمكنك اختيار ما تريد النسخ منه source ل newInstance استخدام CloningFlags التعداد:

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

ما الذي يمكن استنساخه؟

  • البدائية (int ، uint ، byte ، double ، char ، إلخ) ، والأنواع غير القابلة للتغيير المعروفة (dateTime ، timepan ، string) والمندوبين (بما في ذلك الإجراء ، func ، إلخ)
  • لاغية
  • صفائف T[]
  • الفئات والبنيات المخصصة، بما في ذلك الفئات والبنيات العامة.

يتم استنساخ أعضاء الفئة/البنية التالية داخليًا:

  • قيم الحقول العامة، وليس للقراءة فقط
  • قيم الممتلكات العامة مع كل من الوصول والضبط
  • عناصر المجموعة للأنواع التي تنفذ ICollection

ما مدى سرعة ذلك؟

الحل أسرع من التفكير، لأنه يجب جمع معلومات الأعضاء مرة واحدة فقط، قبل ذلك GetClone<T> يستخدم لأول مرة لنوع معين T.

كما أنه أسرع من الحل القائم على التسلسل عندما تقوم باستنساخ أكثر من مثيلين من نفس النوع T.

و اكثر...

اقرأ المزيد حول التعبيرات التي تم إنشاؤها على توثيق.

نموذج قائمة تصحيح التعبير لـ List<int>:

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


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

}

ما له نفس المعنى مثل كود c# التالي:

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

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

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

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

    return target;
}

أليس الأمر مثل الطريقة التي تكتب بها بنفسك Clone طريقة ل List<int>?

إذا كنت تستخدم بالفعل تطبيقًا تابعًا لجهة خارجية مثل ValueInjecter أو مخطط تلقائي, ، يمكنك القيام بشيء مثل هذا:

MyObject oldObj; // The existing object to clone

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

باستخدام هذه الطريقة، لن تحتاج إلى تطبيق ISerializable أو ICloneable على كائناتك.وهذا أمر شائع مع نمط MVC/MVVM، لذلك تم إنشاء أدوات بسيطة مثل هذه.

يرى حل الاستنساخ العميق لـ valueinjecter على CodePlex.

الإجابة المختصرة هي أنك ترث واجهة ICloneable ثم تقوم بتنفيذ وظيفة .clone.يجب أن يقوم الاستنساخ بعمل نسخة حسب الأعضاء وإجراء نسخة عميقة على أي عضو يتطلب ذلك، ثم إرجاع الكائن الناتج.هذه عملية متكررة (تتطلب أن يكون جميع أعضاء الفئة التي تريد استنساخها إما من أنواع القيم أو ينفذون ICloneable وأن يكون أعضاؤهم إما من أنواع القيم أو يطبقون ICloneable، وهكذا).

للحصول على شرح أكثر تفصيلاً حول الاستنساخ باستخدام ICloneable، راجع ذلك هذا المقال.

ال طويل الجواب هو "هذا يعتمد".كما ذكر آخرون، فإن ICloneable غير مدعوم من قبل الأدوية العامة، ويتطلب اعتبارات خاصة لمراجع الفئات الدائرية، وينظر إليه البعض بالفعل على أنه "خطأ" في .NET Framework.تعتمد طريقة التسلسل على كون الكائنات الخاصة بك قابلة للتسلسل، وهو ما قد لا يكون كذلك وقد لا يكون لديك أي سيطرة عليه.لا يزال هناك الكثير من الجدل في المجتمع حول الممارسة "الأفضل".في الواقع، لا يمثل أي من الحلول أفضل الممارسات ذات الحجم الواحد الذي يناسب الجميع لجميع المواقف مثل ICloneable الذي تم تفسيره في الأصل.

انظر هذا مقالة ركن المطورين لمزيد من الخيارات (الفضل لإيان).

الأفضل هو تنفيذ طريقة التمديد يحب

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

ثم استخدمه في أي مكان في الحل

var copy = anyObject.DeepClone();

يمكن أن يكون لدينا التطبيقات الثلاثة التالية:

  1. عن طريق التسلسل (أقصر كود)
  2. بواسطة الانعكاس - أسرع بـ 5 مرات
  3. بواسطة أشجار التعبير - أسرع 20 مرة

جميع الطرق المرتبطة تعمل بشكل جيد وتم اختبارها بعمق.

  1. تحتاج في الأساس إلى تنفيذ واجهة ICloneable ومن ثم تحقيق نسخ بنية الكائن.
  2. إذا كانت نسخة عميقة من جميع الأعضاء، فأنت بحاجة إلى التأكد (وليس الارتباط بالحل الذي تختاره) من أن جميع الأطفال قابلون للاستنساخ أيضًا.
  3. في بعض الأحيان، يجب أن تكون على دراية ببعض القيود أثناء هذه العملية، على سبيل المثال، إذا قمت بنسخ كائنات ORM، فإن معظم الأطر تسمح بكائن واحد فقط مرتبط بالجلسة ويجب ألا تقوم بنسخ هذا الكائن، أو إذا كان من الممكن أن تحتاج إلى الاهتمام حول جلسة إرفاق هذه الكائنات.

هتافات.

إذا كنت تريد الاستنساخ الحقيقي لأنواع غير معروفة يمكنك إلقاء نظرة عليهاcom.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();

لقد توصلت إلى هذا للتغلب على أ .شبكة عيب الاضطرار إلى النسخ العميق لقائمة <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;
}

لقد حاولت أن أتوصل إلى خط واحد يقوم بذلك، لكن هذا غير ممكن، نظرًا لعدم عمل العائد داخل كتل الطريقة المجهولة.

والأفضل من ذلك، استخدام 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();
        }
    }
}

س.لماذا سأختار هذه الإجابة؟

  • اختر هذه الإجابة إذا كنت تريد أسرع سرعة يستطيع .NET تحقيقها.
  • تجاهل هذه الإجابة إذا كنت تريد طريقة سهلة جدًا للاستنساخ.

بعبارة أخرى، انتقل إلى إجابة أخرى ما لم يكن لديك اختناق في الأداء يحتاج إلى إصلاح، ويمكنك إثبات ذلك باستخدام ملف التعريف.

10x أسرع من الطرق الأخرى

الطريقة التالية لإجراء استنساخ عميق هي:

  • 10x أسرع من أي شيء يتضمن التسلسل/إلغاء التسلسل؛
  • قريب جدًا من السرعة القصوى النظرية التي يستطيع .NET تحقيقها.

والطريقة...

للحصول على السرعة القصوى، يمكنك استخدام Nested MemberwiseClone لعمل نسخة عميقة.إنها تقريبًا نفس سرعة نسخ بنية القيمة، وهي أسرع بكثير من (أ) الانعكاس أو (ب) التسلسل (كما هو موضح في الإجابات الأخرى في هذه الصفحة).

لاحظ أن لو انت تستخدم Nested MemberwiseClone للحصول على نسخة عميقة, ، يجب عليك تنفيذ ShallowCopy يدويًا لكل مستوى متداخل في الفصل، وDeepCopy الذي يستدعي جميع أساليب ShallowCopy المذكورة لإنشاء نسخة كاملة.هذا بسيط:فقط بضعة أسطر في المجمل، راجع الكود التجريبي أدناه.

فيما يلي مخرجات الكود التي توضح اختلاف الأداء النسبي لـ 100000 نسخة:

  • 1.08 ثانية لـ Nested MemberwiseClone على الهياكل المتداخلة
  • 4.77 ثانية لـ Nested MemberwiseClone في الفئات المتداخلة
  • 39.93 ثانية للتسلسل/إلغاء التسلسل

يعد استخدام Nested MemberwiseClone في الفصل الدراسي بنفس سرعة نسخ البنية، كما أن نسخ البنية قريب جدًا من السرعة القصوى النظرية التي يستطيع .NET تحقيقها.

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

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

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

لفهم كيفية عمل نسخة عميقة باستخدام MemberwiseCopy، إليك المشروع التجريبي الذي تم استخدامه لإنشاء الأوقات المذكورة أعلاه:

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

ثم اتصل بالعرض التوضيحي من الرئيسي:

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

مرة أخرى، لاحظ ذلك لو انت تستخدم Nested MemberwiseClone للحصول على نسخة عميقة, ، يجب عليك تنفيذ ShallowCopy يدويًا لكل مستوى متداخل في الفصل، وDeepCopy الذي يستدعي جميع أساليب ShallowCopy المذكورة لإنشاء نسخة كاملة.هذا بسيط:فقط بضعة أسطر في المجمل، راجع الكود التجريبي أعلاه.

أنواع القيمة مقابلأنواع المراجع

لاحظ أنه عندما يتعلق الأمر باستنساخ كائن، هناك فرق كبير بين "البنية"و"فصل":

  • اذا كان لديك "البنية"، انه نوع القيمة لذا يمكنك فقط نسخه، وسيتم استنساخ المحتويات (ولكن سيتم إجراء استنساخ سطحي فقط ما لم تستخدم التقنيات المذكورة في هذا المنشور).
  • اذا كان لديك "فصل"، انه نوع مرجع, ، لذا إذا قمت بنسخه، فكل ما تفعله هو نسخ المؤشر إليه.لإنشاء نسخة حقيقية، عليك أن تكون أكثر إبداعًا واستخدامًا الاختلافات بين أنواع القيمة وأنواع المراجع مما يؤدي إلى إنشاء نسخة أخرى من الكائن الأصلي في الذاكرة.

يرى الاختلافات بين أنواع القيمة وأنواع المراجع.

المجاميع الاختبارية للمساعدة في تصحيح الأخطاء

  • يمكن أن يؤدي استنساخ الكائنات بشكل غير صحيح إلى أخطاء يصعب تحديدها.في كود الإنتاج، أميل إلى تنفيذ المجموع الاختباري للتأكد مرة أخرى من أن الكائن قد تم استنساخه بشكل صحيح، ولم يتم إتلافه من خلال مرجع آخر إليه.يمكن إيقاف تشغيل هذا المجموع الاختباري في وضع الإصدار.
  • أجد هذه الطريقة مفيدة جدًا:في كثير من الأحيان، تريد فقط استنساخ أجزاء من الكائن، وليس كل شيء.

مفيد حقًا لفصل العديد من المواضيع عن العديد من المواضيع الأخرى

إحدى حالات الاستخدام الممتازة لهذا الكود هي تغذية نسخ فئة أو بنية متداخلة في قائمة انتظار، لتنفيذ نمط المنتج/المستهلك.

  • يمكن أن يكون لدينا مؤشر ترابط واحد (أو أكثر) يعدل الفصل الذي يمتلكه، ثم يدفع نسخة كاملة من هذا الفصل إلى ConcurrentQueue.
  • لدينا بعد ذلك خيط واحد (أو أكثر) يسحب نسخًا من هذه الفئات ويتعامل معها.

يعمل هذا بشكل جيد للغاية في الممارسة العملية، ويسمح لنا بفصل العديد من الخيوط (المنتجين) عن واحد أو أكثر من الخيوط (المستهلكين).

وهذه الطريقة سريعة للغاية أيضًا:إذا استخدمنا بنيات متداخلة، فهي أسرع بمقدار 35 مرة من إجراء تسلسل/إلغاء تسلسل الفئات المتداخلة، وتسمح لنا بالاستفادة من جميع الخيوط المتوفرة على الجهاز.

تحديث

من الواضح أن ExpressMapper سريع، إن لم يكن أسرع، من الترميز اليدوي كما هو مذكور أعلاه.قد أضطر إلى رؤية كيفية مقارنتها مع ملف التعريف.

بشكل عام، يمكنك تنفيذ واجهة 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;
}

نظرًا لأنني لم أتمكن من العثور على مستنسخ يلبي جميع متطلباتي في مشاريع مختلفة، فقد قمت بإنشاء مستنسخ عميق يمكن تهيئته وتكييفه مع هياكل التعليمات البرمجية المختلفة بدلاً من تكييف الكود الخاص بي لتلبية متطلبات المستنسخين.يتم تحقيق ذلك عن طريق إضافة التعليقات التوضيحية إلى الكود الذي سيتم استنساخه أو يمكنك فقط ترك الكود كما هو ليكون له السلوك الافتراضي.ويستخدم الانعكاس، واكتب ذاكرة التخزين المؤقت ويستند إلى com.fastflect.تكون عملية الاستنساخ سريعة جدًا بالنسبة لكمية هائلة من البيانات وتسلسل هرمي عالي للكائنات (مقارنة بالخوارزميات الأخرى القائمة على الانعكاس/التسلسل).

https://github.com/kalisohn/CloneBehave

متوفرة أيضًا كحزمة nuget:https://www.nuget.org/packages/Clone.Behave/1.0.0

على سبيل المثال:التعليمة البرمجية التالية سوف تقوم بنسخ العنوان بشكل عميق، ولكنها ستؤدي فقط إلى نسخة سطحية من حقل _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);

أنا أحب 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.تعد طريقة الاستنساخ فعالة في الذاكرة ووحدة المعالجة المركزية، وبالتالي فهي أسرع بمقدار 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();
}

اتبع الخطوات التالية:

  • تعريف ان 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.

أعتقد أنه يمكنك تجربة هذا.

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

لقد قمت بإنشاء نسخة من الإجابة المقبولة التي تعمل مع كل من "[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;
    }

} 

لاستنساخ كائن الفئة الخاصة بك، يمكنك استخدام أسلوب 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);
}

تحقق من الكود الكامل في رسالتي في إجابة أخرى

https://stackoverflow.com/a/34365709/4711853

إذا كانت شجرة الكائنات الخاصة بك قابلة للتسلسل، فيمكنك أيضًا استخدام شيء مثل هذا

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

كن على علم بأن هذا الحل سهل جدًا ولكنه ليس بنفس الأداء الذي قد تكون عليه الحلول الأخرى.

وتأكد من أنه إذا زاد الفصل، فلن يكون هناك سوى تلك الحقول المستنسخة، والتي سيتم تسلسلها أيضًا.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top