it-swarm.asia

كائنات استنساخ عميق

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

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

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

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

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

2028
NakedBrunch

في حين أن الممارسة المعتادة هي تطبيق واجهة { 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);
}
1592
johnc

كنت أريد شبيهًا بالكائنات البسيطة جدًا والتي تكون في معظمها من العناصر البدائية والقوائم. إذا كان الكائن الخاص بك خارج الصندوق 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);
    }
}
241
craastad

سبب عدم استخدام ICloneable is لا لأنه لا يحتوي على واجهة عامة. السبب في عدم استخدامه لأنه غامض . لا يوضح ما إذا كنت تحصل على نسخة ضحلة أو عميقة ؛ الأمر متروك للمنفذ.

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

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

162
Ryan Lundy

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

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

أفضل شيء يمكن القيام به لاستنساخ الكائنات في c حاد!

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

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

لماذا اخترت 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);
    }
    …
}

الآن النظر في وجود فئة مشتقة من شخص.

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]

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

102
cregox

انا افضل نسخة منشئ على استنساخ. القصد أكثر وضوحا.

77
Nick

طريقة تمديد بسيطة لنسخ جميع الممتلكات العامة. يعمل لأي كائنات و لا يتطلب أن يكون الفصل [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 ) } );
        }
    };
}
38
Konstantin Salavatov

حسنًا ، كنت أواجه مشكلات عند استخدام 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);
    }
}
30
Michael White

لقد قمت للتو بإنشاء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);

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

  • البدائية (int ، uint ، byte ، double ، char ، إلخ.) ، الأنواع الثابتة غير المعروفة (DateTime ، TimeSpan ، String) والمفوضون (بما في ذلك Action ، Func ، إلخ)
  • قيم الفارغة
  • T [] المصفوفات
  • فئات وهياكل مخصصة ، بما في ذلك فئات وهياكل عامة.

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

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

ما مدى سرعة هو؟

الحل أسرع ثم الانعكاس ، لأنه يجب جمع معلومات الأعضاء مرة واحدة فقط ، قبل استخدام 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>؟

27
MarcinJuraszek

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

MyObject oldObj; // The existing object to clone

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

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

راجع محلول استنساخ القيمة العميق في CodePlex .

26
Michael Cox

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

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

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

راجع هذا مقالة ركن المطور للحصول على مزيد من الخيارات (ائتمان لإيان).

20
Zach Burlingame

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

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

ثم استخدمه في أي مكان في الحل بواسطة

var copy = anyObject.DeepClone();

يمكننا الحصول على التطبيقات الثلاثة التالية:

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

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

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

في صحتك.

16
dimarzionist

إذا كنت تريد الاستنساخ الحقيقي لأنواع غير معروفة ، يمكنك إلقاء نظرة على fastclone .

هذا الاستنساخ القائم على التعبير يعمل حوالي 10 مرات أسرع من التسلسل الثنائي والحفاظ على سلامة الرسم البياني للكامل.

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

ليست هناك حاجة للواجهات أو السمات أو أي تعديل آخر على الكائنات التي يتم استنساخها.

15
Michael Sander

اجعل الأمور بسيطة واستخدام 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();
11
Stacked

خطرت لي هذا للتغلب على . 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();
        }
    }
}
10
Daniel Mošmondor

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

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

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

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

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

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

والطريقة ...

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

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

فيما يلي ناتج الكود الذي يظهر الفرق في الأداء النسبي لـ 100،000 نسخة:

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

إن استخدام 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 ":

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

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

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

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

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

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

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

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

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

تحديث

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

7
Contango

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

7
xr280xr

نظرًا لأنني لم أتمكن من العثور على 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
7
kalisohn

بشكل عام ، يمكنك تنفيذ واجهة ICloneable وتنفيذ استنساخ نفسك. تحتوي الكائنات C # على طريقة MemberwiseClone مضمّنة تقوم بتنفيذ نسخة ضحلة يمكن أن تساعدك في الحصول على جميع العناصر الأولية.

بالنسبة للنسخة العميقة ، لا توجد طريقة لمعرفة كيفية القيام بذلك تلقائيًا.

7
HappyDude

هنا هو تنفيذ نسخة عميقة:

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

هذه الطريقة حل المشكلة بالنسبة لي:

private static MyObj DeepCopy(MyObj source)
        {

            var DeserializeSettings = new JsonSerializerSettings { ObjectCreationHandling = ObjectCreationHandling.Replace };

            return JsonConvert.DeserializeObject<MyObj >(JsonConvert.SerializeObject(source), DeserializeSettings);

        }

استخدمه مثل هذا: MyObj a = DeepCopy(b);

6
JerryGoyal

أنا أحب 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));
        }
    }

إذا كان لديك المزيد من الأشياء لنسخ إضافتها

5
LuckyLikey

مولد الأكواد

لقد رأينا الكثير من الأفكار من التسلسل على التنفيذ اليدوي إلى التفكير وأريد أن أقترح طريقة مختلفة تمامًا باستخدام 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;
    }
}

ملاحظة: أحدث إصدار لديه المزيد من الشيكات الفارغة ، لكنني تركتها لفهم أفضل.

5
Toxantron

هنا حل سريع وسهل كان يعمل بالنسبة لي دون ترحيل التسلسل/إزالة التسلسل.

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();
}
5
Daniele D.

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

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

اتبع هذه الخطوات:

  • حدد 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.

4
supercat

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

} 
3
Jeroen Ritmeijer

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

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

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

وتأكد من أنه في حالة نمو الفئة ، فسيظل هناك فقط تلك الحقول المستنسخة ، والتي يتم تسلسلها أيضًا.

3
LuckyLikey

لاستنساخ كائن الفئة الخاصة بك ، يمكنك استخدام الأسلوب Object.MemberwiseClone ،

فقط أضف هذه الوظيفة إلى صفك:

public class yourClass
{
    // ...
    // ...

    public yourClass DeepCopy()
    {
        yourClass othercopy = (yourClass)this.MemberwiseClone();
        return othercopy;
    }
}

ثم لأداء نسخة مستقلة عميقة ، ما عليك سوى استدعاء طريقة DeepCopy:

yourClass newLine = oldLine.DeepCopy();

أتمنى أن يساعدك هذا.

3
Chtiwi Malek

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

إذا كنت ستقوم بتخزينها مؤقتًا بشكل صحيح ، فستكون أكثر استنساخًا عميقًا لكائن 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

3
Roma Borodov

نظرًا لأن جميع الإجابات على هذا السؤال كانت تقريبًا غير مرضية أو لا تعمل بشكل واضح في وضعي ، فقد قمت بتأليف AnyClone والذي يتم تنفيذه بالكامل مع التفكير وحل جميع الاحتياجات هنا. لم أتمكن من جعل التسلسل يعمل في سيناريو معقد بهيكل معقد ، و IClonable أقل من مثالي - في الواقع لا ينبغي أن يكون ضروريًا.

يتم دعم سمات التجاهل القياسية باستخدام [IgnoreDataMember] و [NonSerialized]. يدعم المجموعات المعقدة ، والممتلكات دون المستوطنين ، والحقول للقراءة فقط إلخ.

آمل أن يساعد شخص آخر هناك واجه نفس المشاكل التي واجهتها.

2
Michael Brown

عند استخدام 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 لتسلسل الكائن.

1
Basti M

ملحق 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()
1
Sameera R.

من غير المعقول مقدار الجهد الذي يمكنك إنفاقه مع واجهة 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
    }

}
1
TarmoPikaro

إجابة 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;
        }
    }
}
1
Matthew Watson

يقوم معين معين بنسخة عميقة. بالنسبة إلى كائن عضو في Foreach ، فإنه ينشئ كائنًا جديدًا ويعين جميع قيمه. وهي تعمل بشكل متكرر على كل عضو داخلي غير بدائي.

أقترح عليك واحدة من الأسرع ، وضعت حاليا بنشاط. أقترح UltraMapper https://github.com/maurosampietro/UltraMapper

حزم Nuget: https://www.nuget.org/packages/UltraMapper/

1
Mauro Sampietro

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

في معظم مواقف الحياة الواقعية ، ترغب أيضًا في الحصول على أكبر قدر ممكن من التحكم الدقيق في عملية النسخ نظرًا لأنك لا تقترن فقط بإطار الوصول إلى البيانات ولكن أيضًا في الممارسة العملية ، نادراً ما تكون كائنات الأعمال المنسوخة 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
            };
        }
    }

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

0
Piotr Jerzy Mamenas

لقد وجدت طريقة جديدة للقيام بذلك وهي 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 ، فمن الصعب للغاية أنني لا أستطيع القيام بذلك.

0
lindexi

ماذا عن مجرد إعادة صياغة داخل الطريقة التي يجب أن تستدعي أساسا منشئ نسخة تلقائية

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

يبدو أن العمل بالنسبة لي

0
will_m

سيؤدي هذا إلى نسخ جميع الخصائص القابلة للقراءة والكتابة لكائن إلى آخر.

 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);
0
Ylli Prifti