كيف تفعل نسخة عميق كائن في .NET (C# على وجه التحديد)?[مكررة]

StackOverflow https://stackoverflow.com/questions/129389

  •  02-07-2019
  •  | 
  •  

سؤال

هذا السؤال سبق الجواب هنا:

أريد الحقيقي العميق نسخ.في جافا, هذا سهل, ولكن كيف يمكنك أن تفعل ذلك في C# ؟

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

المحلول

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

public static T DeepClone<T>(T obj)
{
 using (var ms = new MemoryStream())
 {
   var formatter = new BinaryFormatter();
   formatter.Serialize(ms, obj);
   ms.Position = 0;

   return (T) formatter.Deserialize(ms);
 }
}

ملاحظات:

  • الفئة يجب أن تكون علامة [Serializable] من أجل هذا العمل.
  • الملف المصدر يجب أن تتضمن التعليمات البرمجية التالية:

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

نصائح أخرى

أنا كتب عميق نسخة الكائن طريقة التمديد, استنادا إلى العودية "MemberwiseClone".فهو سريع (أسرع ثلاث مرات من BinaryFormatter), ويعمل مع أي كائن.أنت لا تحتاج إلى منشئ افتراضي أو تسلسل الصفات.

المصدر مدونة:

using System.Collections.Generic;
using System.Reflection;
using System.ArrayExtensions;

namespace System
{
    public static class ObjectExtensions
    {
        private static readonly MethodInfo CloneMethod = typeof(Object).GetMethod("MemberwiseClone", BindingFlags.NonPublic | BindingFlags.Instance);

        public static bool IsPrimitive(this Type type)
        {
            if (type == typeof(String)) return true;
            return (type.IsValueType & type.IsPrimitive);
        }

        public static Object Copy(this Object originalObject)
        {
            return InternalCopy(originalObject, new Dictionary<Object, Object>(new ReferenceEqualityComparer()));
        }
        private static Object InternalCopy(Object originalObject, IDictionary<Object, Object> visited)
        {
            if (originalObject == null) return null;
            var typeToReflect = originalObject.GetType();
            if (IsPrimitive(typeToReflect)) return originalObject;
            if (visited.ContainsKey(originalObject)) return visited[originalObject];
            if (typeof(Delegate).IsAssignableFrom(typeToReflect)) return null;
            var cloneObject = CloneMethod.Invoke(originalObject, null);
            if (typeToReflect.IsArray)
            {
                var arrayType = typeToReflect.GetElementType();
                if (IsPrimitive(arrayType) == false)
                {
                    Array clonedArray = (Array)cloneObject;
                    clonedArray.ForEach((array, indices) => array.SetValue(InternalCopy(clonedArray.GetValue(indices), visited), indices));
                }

            }
            visited.Add(originalObject, cloneObject);
            CopyFields(originalObject, visited, cloneObject, typeToReflect);
            RecursiveCopyBaseTypePrivateFields(originalObject, visited, cloneObject, typeToReflect);
            return cloneObject;
        }

        private static void RecursiveCopyBaseTypePrivateFields(object originalObject, IDictionary<object, object> visited, object cloneObject, Type typeToReflect)
        {
            if (typeToReflect.BaseType != null)
            {
                RecursiveCopyBaseTypePrivateFields(originalObject, visited, cloneObject, typeToReflect.BaseType);
                CopyFields(originalObject, visited, cloneObject, typeToReflect.BaseType, BindingFlags.Instance | BindingFlags.NonPublic, info => info.IsPrivate);
            }
        }

        private static void CopyFields(object originalObject, IDictionary<object, object> visited, object cloneObject, Type typeToReflect, BindingFlags bindingFlags = BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.FlattenHierarchy, Func<FieldInfo, bool> filter = null)
        {
            foreach (FieldInfo fieldInfo in typeToReflect.GetFields(bindingFlags))
            {
                if (filter != null && filter(fieldInfo) == false) continue;
                if (IsPrimitive(fieldInfo.FieldType)) continue;
                var originalFieldValue = fieldInfo.GetValue(originalObject);
                var clonedFieldValue = InternalCopy(originalFieldValue, visited);
                fieldInfo.SetValue(cloneObject, clonedFieldValue);
            }
        }
        public static T Copy<T>(this T original)
        {
            return (T)Copy((Object)original);
        }
    }

    public class ReferenceEqualityComparer : EqualityComparer<Object>
    {
        public override bool Equals(object x, object y)
        {
            return ReferenceEquals(x, y);
        }
        public override int GetHashCode(object obj)
        {
            if (obj == null) return 0;
            return obj.GetHashCode();
        }
    }

    namespace ArrayExtensions
    {
        public static class ArrayExtensions
        {
            public static void ForEach(this Array array, Action<Array, int[]> action)
            {
                if (array.LongLength == 0) return;
                ArrayTraverse walker = new ArrayTraverse(array);
                do action(array, walker.Position);
                while (walker.Step());
            }
        }

        internal class ArrayTraverse
        {
            public int[] Position;
            private int[] maxLengths;

            public ArrayTraverse(Array array)
            {
                maxLengths = new int[array.Rank];
                for (int i = 0; i < array.Rank; ++i)
                {
                    maxLengths[i] = array.GetLength(i) - 1;
                }
                Position = new int[array.Rank];
            }

            public bool Step()
            {
                for (int i = 0; i < Position.Length; ++i)
                {
                    if (Position[i] < maxLengths[i])
                    {
                        Position[i]++;
                        for (int j = 0; j < i; j++)
                        {
                            Position[j] = 0;
                        }
                        return true;
                    }
                }
                return false;
            }
        }
    }

}

بناء على Kilhoffer حل...

مع C# 3.0 يمكنك إنشاء طريقة التمديد كما يلي:

public static class ExtensionMethods
{
    // Deep clone
    public static T DeepClone<T>(this T a)
    {
        using (MemoryStream stream = new MemoryStream())
        {
            BinaryFormatter formatter = new BinaryFormatter();
            formatter.Serialize(stream, a);
            stream.Position = 0;
            return (T) formatter.Deserialize(stream);
        }
    }
}

الذي يمتد أي الفئة التي تم وضع علامة عليها كـ [Serializable] مع DeepClone طريقة

MyClass copy = obj.DeepClone();

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

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

هنا هو الإخراج من قانون عرض الأداء النسبي الفارق (4.77 ثانية عميقة متداخلة MemberwiseCopy مقابل39.93 ثانية على التسلسل).باستخدام متداخلة MemberwiseCopy تقريبا بأسرع ما نسخ البنية و نسخ البنية هو الرتق على مقربة من السرعة القصوى النظرية .صافي قادر على ، والتي ربما تكون قريبة جدا من سرعة نفس الشيء في C أو C++ (ولكن سيكون لديك لتشغيل بعض ما يعادلها معايير التحقق من هذا الادعاء).

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

لفهم كيف تفعل نسخة عميق باستخدام MemberwiseCopy, هنا هو مشروع تجريبي:

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

ثم اتصل التجريبي من الرئيسية:

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

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

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

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

التحديث

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

التحديث

اقتباس المستقلة اختبار سرعة (انظر التعليقات الواردة أدناه):

لقد تشغيل بلدي اختبار سرعة باستخدام نيل تسلسل/إلغاء تسلسل امتداد أسلوب التأجيل هو متداخلة MemberwiseClone أليكس Burtsev هو التفكير القائم على امتداد طريقة AutoMapper ، 1 مليون مرة كل.تسلسل-إلغاء تسلسل كانت أبطأ ، مع 15.7 ثانية.ثم جاء AutoMapper ، مع 10.1 ثانية.أسرع بكثير كان التفكير القائم على الأسلوب الذي أخذت 2.4 ثانية.حتى الآن أسرع كان متداخلة MemberwiseClone ، مع الأخذ 0.1 ثانية.يأتي الأداء مقابل المتاعب إضافة رمز إلى كل فئة إلى استنساخ ذلك.إذا كان الأداء ليست قضية الذهاب مع أليكس Burtsev طريقة.– سيمون Tewsi

وأعتقد أن BinaryFormatter نهج بطيء نسبيا (والذي جاء بمثابة مفاجأة بالنسبة لي!).كنت قد تكون قادرة على استخدام ProtoBuf .صافي لبعض الكائنات إذا كانت تلبية متطلبات ProtoBuf.من ProtoBuf بدأت صفحة (http://code.google.com/p/protobuf-net/wiki/GettingStarted):

ملاحظات على أنواع المدعومة:

مخصص فئات:

  • يتم وضع علامة بيانات العقد
  • يكون parameterless منشئ
  • ل Silverlight:العامة
  • العديد من الأوليات ، إلخ.
  • واحد البعد المصفوفات:T[]
  • قائمة<T> / IList<T>
  • القاموس<TKey, TValue=""> / يقع<TKey, TValue="">
  • أي نوع التي تنفذ IEnumerable<T> ومن لديه إضافة(T) طريقة

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

إذا كان لديك فئة يلبي هذه المتطلبات قد تتمكن من محاولة:

public static void deepCopy<T>(ref T object2Copy, ref T objectCopy)
{
    using (var stream = new MemoryStream())
    {
        Serializer.Serialize(stream, object2Copy);
        stream.Position = 0;
        objectCopy = Serializer.Deserialize<T>(stream);
    }
}

وهو سريع جدا في الواقع...

تحرير:

هنا هو رمز يعمل على تعديل هذا (اختبار على .صافي 4.6).ويستخدم النظام.Xml.التسلسل النظام.IO.لا تحتاج إلى علامة فئات وفقا للتسلسل.

public void DeepCopy<T>(ref T object2Copy, ref T objectCopy)
{
    using (var stream = new MemoryStream())
    {
        var serializer = new XS.XmlSerializer(typeof(T));

        serializer.Serialize(stream, object2Copy);
        stream.Position = 0;
        objectCopy = (T)serializer.Deserialize(stream);
    }
}

يمكنك أن تجرب هذا

    public static object DeepCopy(object obj)
    {
        if (obj == null)
            return null;
        Type type = obj.GetType();

        if (type.IsValueType || type == typeof(string))
        {
            return obj;
        }
        else if (type.IsArray)
        {
            Type elementType = Type.GetType(
                 type.FullName.Replace("[]", string.Empty));
            var array = obj as Array;
            Array copied = Array.CreateInstance(elementType, array.Length);
            for (int i = 0; i < array.Length; i++)
            {
                copied.SetValue(DeepCopy(array.GetValue(i)), i);
            }
            return Convert.ChangeType(copied, obj.GetType());
        }
        else if (type.IsClass)
        {

            object toret = Activator.CreateInstance(obj.GetType());
            FieldInfo[] fields = type.GetFields(BindingFlags.Public |
                        BindingFlags.NonPublic | BindingFlags.Instance);
            foreach (FieldInfo field in fields)
            {
                object fieldValue = field.GetValue(obj);
                if (fieldValue == null)
                    continue;
                field.SetValue(toret, DeepCopy(fieldValue));
            }
            return toret;
        }
        else
            throw new ArgumentException("Unknown type");
    }

بفضل DetoX83 المادة على رمز المشروع.

ربما تحتاج فقط نسخة الضحلة في هذه الحالة استخدام Object.MemberWiseClone().

هناك توصيات جيدة في وثائق MemberWiseClone() استراتيجيات نسخة العميق:-

http://msdn.microsoft.com/en-us/library/system.object.memberwiseclone.aspx

فإن أفضل طريقة هي:

    public interface IDeepClonable<T> where T : class
    {
        T DeepClone();
    }

    public class MyObj : IDeepClonable<MyObj>
    {
        public MyObj Clone()
        {
            var myObj = new MyObj();
            myObj._field1 = _field1;//value type
            myObj._field2 = _field2;//value type
            myObj._field3 = _field3;//value type

            if (_child != null)
            {
                myObj._child = _child.DeepClone(); //reference type .DeepClone() that does the same
            }

            int len = _array.Length;
            myObj._array = new MyObj[len]; // array / collection
            for (int i = 0; i < len; i++)
            {
                myObj._array[i] = _array[i];
            }

            return myObj;
        }

        private bool _field1;
        public bool Field1
        {
            get { return _field1; }
            set { _field1 = value; }
        }

        private int _field2;
        public int Property2
        {
            get { return _field2; }
            set { _field2 = value; }
        }

        private string _field3;
        public string Property3
        {
            get { return _field3; }
            set { _field3 = value; }
        }

        private MyObj _child;
        private MyObj Child
        {
            get { return _child; }
            set { _child = value; }
        }

        private MyObj[] _array = new MyObj[4];
    }

وثائق MSDN يبدو إشارة إلى أن استنساخ يجب إجراء نسخة عميق ، ولكنه لم ينص صراحة على:

على ICloneable يحتوي على واجهة أحد الأعضاء, استنساخ, الذي يهدف إلى دعم الاستنساخ أبعد من ذلك الموردة من قبل MemberWiseClone... على MemberwiseClone الأسلوب يخلق نسخة الضحلة...

يمكنك العثور على وظيفة مفيدة.

http://pragmaticcoding.com/index.php/cloning-objects-in-c/

    public static object CopyObject(object input)
    {
        if (input != null)
        {
            object result = Activator.CreateInstance(input.GetType());
            foreach (FieldInfo field in input.GetType().GetFields(Consts.AppConsts.FullBindingList))
            {
                if (field.FieldType.GetInterface("IList", false) == null)
                {
                    field.SetValue(result, field.GetValue(input));
                }
                else
                {
                    IList listObject = (IList)field.GetValue(result);
                    if (listObject != null)
                    {
                        foreach (object item in ((IList)field.GetValue(input)))
                        {
                            listObject.Add(CopyObject(item));
                        }
                    }
                }
            }
            return result;
        }
        else
        {
            return null;
        }
    }

هذه الطريقة عدة مرات أسرع من BinarySerialization و هذا لا يتطلب [Serializable] السمة.

لدي أبسط فكرة.استخدام LINQ مع مجموعة جديدة.

public class Fruit
{
  public string Name {get; set;}
  public int SeedCount {get; set;}
}

void SomeMethod()
{
  List<Fruit> originalFruits = new List<Fruit>();
  originalFruits.Add(new Fruit {Name="Apple", SeedCount=10});
  originalFruits.Add(new Fruit {Name="Banana", SeedCount=0});

  //Deep Copy
  List<Fruit> deepCopiedFruits = from f in originalFruits
              select new Fruit {Name=f.Name, SeedCount=f.SeedCount};
}
مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top