Programing

.NET (특히 C #)에서 개체의 전체 복사를 수행하는 방법은 무엇입니까?

lottogame 2020. 10. 4. 10:16
반응형

.NET (특히 C #)에서 개체의 전체 복사를 수행하는 방법은 무엇입니까? [복제]


이 질문에 이미 답변이 있습니다.

진정한 딥 카피를 원합니다. 자바에서는 쉬웠지만 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보다 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;
            }
        }
    }

}

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

DeepClone 메서드를 사용하여 [Serializable]로 표시된 모든 클래스를 확장합니다.

MyClass copy = obj.DeepClone();

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

전체 복제에 Nested MemberwiseClone을 사용 하는 경우 클래스의 각 중첩 수준에 대해 ShallowCopy를 수동으로 구현해야하며 전체 복제를 만들기 위해 모든 ShallowCopy 메서드를 호출하는 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;
    }
}

그런 다음 main에서 데모를 호출합니다.

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

다시 말하지만, 딥 카피에 Nested MemberwiseClone을 사용 하는 경우 , 클래스의 각 중첩 레벨에 대해 ShallowCopy를 수동으로 구현해야하며, 모든 ShallowCopy 메서드를 호출하여 완전한 복제본을 생성하는 DeepCopy를 수동으로 구현해야합니다. 이것은 간단합니다. 총 몇 줄만 있으면 위의 데모 코드를 참조하십시오.

객체를 복제 할 때 "struct"와 "class"사이에는 큰 차이가 있습니다.

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

최신 정보

리플렉션을 사용하여 개체 그래프를 재귀 적으로 탐색하여 깊은 복사를 수행 할 수 있습니다. WCF는이 기술을 사용하여 모든 자식을 포함하여 개체를 직렬화합니다. 트릭은 모든 하위 개체에 검색 가능하게 만드는 속성으로 주석을 추가하는 것입니다. 그러나 일부 성능 이점을 잃을 수 있습니다.

최신 정보

독립적 인 속도 테스트에 대한 인용문 (아래 설명 참조) :

Neil의 직렬화 / 역 직렬화 확장 방법, Contango의 Nested MemberwiseClone, Alex Burtsev의 반사 기반 확장 방법 및 AutoMapper를 각각 100 만 번 사용하여 자체 속도 테스트를 실행했습니다. Serialize-deserialize가 가장 느려 15.7 초가 소요되었습니다. 그런 다음 10.1 초가 걸리는 AutoMapper가 나왔습니다. 2.4 초가 걸린 반사 기반 방법이 훨씬 빨랐습니다. 가장 빠른 것은 Nested MemberwiseClone으로 0.1 초가 걸렸습니다. 성능과 복제를 위해 각 클래스에 코드를 추가하는 번거 로움이 있습니다. 성능이 문제가되지 않는다면 Alex Burtsev의 방법을 사용하십시오. – Simon Tewsi


BinaryFormatter 접근 방식이 상대적으로 느리다고 생각합니다 (놀랍게도!). ProtoBuf의 요구 사항을 충족하는 경우 일부 개체에 대해 ProtoBuf .NET을 사용할 수 있습니다. ProtoBuf 시작하기 페이지 ( http://code.google.com/p/protobuf-net/wiki/GettingStarted )에서 :

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

다음과 같은 사용자 정의 클래스 :

  • 데이터 계약으로 표시됨
  • 매개 변수없는 생성자
  • Silverlight : 공개
  • 많은 공통 프리미티브 등
  • 단일 차원 배열 : T []
  • 목록 <T> / IList <T>
  • 사전 <TKey, TValue> / IDictionary <TKey, TValue>
  • IEnumerable <T>를 구현하고 Add (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에서 테스트 됨). System.Xml.Serialization 및 System.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 문서는 Clone이 전체 복사를 수행해야한다고 암시하는 것처럼 보이지만 명시 적으로 언급되지는 않습니다.

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].


더 간단한 아이디어가 있습니다. 새 선택에 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};
}

참고 URL : https://stackoverflow.com/questions/129389/how-do-you-do-a-deep-copy-of-an-object-in-net-c-specifically

반응형