programing

에서 개체의 전체 복사본을 수행하는 방법은 무엇입니까?NET?

javajsp 2023. 5. 4. 18:41

에서 개체의 전체 복사본을 수행하는 방법은 무엇입니까?NET?

진정한 딥 카피를 원합니다.자바에서는 이것이 쉬웠지만, C#에서는 어떻게 합니까?

중요 참고 사항

이진 포맷터는 더 이상 사용되지 않으며 에서 더 이상 사용할 수 없습니다.2023년 11월 이후 NET.이진 포맷터 제거 전략 참조


이에 대한 몇 가지 다른 접근 방식을 살펴보았지만 일반적인 유틸리티 방법은 다음과 같습니다.

public static T DeepClone<T>(this 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보다 3배 더 빠르며 모든 객체에서 작동합니다.기본 생성자 또는 직렬화 가능한 특성은 필요하지 않습니다.

소스 코드:

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

}

킬호퍼의 해결책을 바탕으로...

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

DeepClone 방법으로 [Serializable](시리얼 가능)으로 표시된 모든 클래스를 확장합니다.

MyClass copy = obj.DeepClone();

Nested MemberwiseClone을 사용하여 전체 복사를 수행할 수 있습니다.값 구조를 복사하는 것과 거의 동일한 속도이며 (a) 반사 또는 (b) 직렬화(이 페이지의 다른 답변에서 설명한 바와 같이)보다 훨씬 빠릅니다.

전체 복제본에 대해 중첩된 구성원별 복제본을 사용하는 경우 클래스의 각 중첩 수준에 대해 HelowCopy를 수동으로 구현해야 하며, 모든 HelowCopy 메서드를 호출하는 DeepCopy를 사용하여 DeepCopy를 구현해야 합니다.이 방법은 간단합니다. 총 몇 줄만 사용할 수 있습니다. 아래 데모 코드를 참조하십시오.

다음은 상대적인 성능 차이를 보여주는 코드의 출력입니다(깊이 중첩된 MemberwiseCopy의 경우 4.77초, 직렬화의 경우 39.93초).중첩된 MemberwiseCopy를 사용하는 것은 구조체를 복사하는 것만큼 빠르며, 구조체를 복사하는 것은 이론적인 최대 속도에 매우 가깝습니다.NET은 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();
    }

다시 말하지만, 전체 복제본을 만들기 위해 중첩된 구성원별 복제본을 사용하는 경우 클래스의 각 중첩 수준에 대해 HelowCopy를 수동으로 구현하고, 모든 HelowCopy 메서드를 호출하는 DeepCopy를 구현해야 합니다.이 방법은 간단합니다. 총 몇 줄만 사용할 수 있습니다. 위의 데모 코드를 참조하십시오.

개체 복제와 관련하여 "구조"와 "클래스" 사이에는 큰 차이가 있습니다.

  • 만약 당신이 "구조"를 가지고 있다면, 그것은 가치 유형이기 때문에 당신은 그것을 복사할 수 있고, 그 내용은 복제될 것입니다.
  • 클래스가 있으면 참조 유형이므로, 클래스를 복사하면 포인터를 해당 클래스에 복사하기만 하면 됩니다.진정한 복제품을 만들려면 더 창의적이어야 하고 메모리에 원래 개체의 다른 복사본을 만드는 방법을 사용해야 합니다.
  • 개체를 잘못 복제하면 핀 다운이 매우 어려운 버그가 발생할 수 있습니다.프로덕션 코드에서는 개체가 제대로 복제되었는지, 그리고 개체에 대한 다른 참조로 인해 손상되지 않았는지 두 번 확인하기 위해 체크섬을 구현하는 경향이 있습니다.이 체크섬은 릴리스 모드에서 끌 수 있습니다.
  • 저는 이 방법이 매우 유용하다고 생각합니다. 종종 전체가 아니라 개체의 일부만 복제하려고 합니다.또한 개체를 수정한 다음 수정된 복사본을 대기열에 공급하는 모든 사용 사례에 필수적입니다.

갱신하다

반사를 사용하여 개체 그래프를 재귀적으로 이동하여 딥 복사를 수행할 수 있습니다.WCF는 이 기술을 사용하여 모든 하위 개체를 포함하여 개체를 직렬화합니다.이 방법은 모든 하위 개체에 검색 가능한 속성을 사용하여 주석을 다는 것입니다.그러나 일부 성능 이점이 손실될 수 있습니다.

갱신하다

독립 속도 테스트에 대한 견적(아래 주석 참조):

Neil의 serialize/deserialize 확장 방법, Contango의 Nested MemberwiseClone, Alex Burtsev의 반사 기반 확장 방법 및 AutoMapper를 각각 100만 번 사용하여 자체 속도 테스트를 실행했습니다.직렬화-직렬화 속도가 15.7초로 가장 느렸습니다.그리고 10.1초가 걸린 AutoMapper가 등장했습니다.훨씬 더 빠른 것은 2.4초가 걸린 반사 기반 방식이었습니다.가장 빠른 것은 Nested MemberwiseClone으로 0.1초가 걸렸습니다.결국 성능과 각 클래스에 코드를 추가하여 복제해야 하는 번거로움이 있습니다.성능이 문제가 되지 않는다면 Alex Burtsev의 방법을 따르세요.사이먼 튜시

저는 바이너리 포맷터 접근법이 상대적으로 느리다고 생각합니다. (이것은 저에게 놀라운 일이었습니다.ProtoBuf를 사용할 수 있습니다.ProtoBuf의 요구 사항을 충족하는 경우 일부 개체에 대한 NET.ProtoBuf 시작하기 페이지(http://code.google.com/p/protobuf-net/wiki/GettingStarted) :

지원되는 유형에 대한 참고:

사용자 지정 클래스:

  • 데이터 계약으로 표시됨
  • 매개 변수 없는 생성자가 있음
  • Silverlight의 경우: 공용입니다.
  • 많은 일반적인 원시 요소 등.
  • 단일 차원 배열:T[]
  • 목록<T> / IList<T>
  • 사전<TKey, TValue> / ID사전<TKey, TValue >
  • IEnumberable을 구현하는 모든 유형 <추가(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);
    }
}

정말로 매우 빠른 것은...

편집:

다음은 이 수정에 대한 작업 코드입니다(에서 테스트됨).NET 4.6).시스템을 사용합니다.Xml.Serialization 및 시스템.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 기사 덕분입니다.

가장 좋은 방법은 다음과 같습니다.

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

얕은 복사본만 필요할 수도 있습니다. 그런 경우에는Object.MemberWiseClone().

설명서에는 다음에 대한 권장 사항이 나와 있습니다.MemberWiseClone()딥 카피 전략: -

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

MSDN 문서는 클론이 딥 카피를 수행해야 한다는 것을 암시하는 것처럼 보이지만, 명시적으로 언급된 적은 없습니다.

ICloneable 인터페이스에는 MemberWiseClone에서 제공하는 것 이상의 복제를 지원하기 위한 Clone이라는 멤버가 하나 포함되어 있습니다.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]기여하다.

언급URL : https://stackoverflow.com/questions/129389/how-do-you-do-a-deep-copy-of-an-object-in-net