문제

이 질문은 이미 여기에 답이 있습니다.

나는 진정한 딥 카피를 원한다. Java에서 이것은 쉬웠지만 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". 빠릅니다.3 배 더 빠릅니다 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);
        }
    }
}

딥 클론 방법으로 [SERIALIZALE]로 표시된 클래스를 연장합니다.

MyClass copy = obj.DeepClone();

당신이 사용할 수있는 중첩 된 Memberwiseclone 딥 카피를 수행합니다. 값 구조물을 복사하는 것과 거의 같은 속도이며 (a) 반사 또는 (b) 직렬화 (이 페이지의 다른 답변에 설명 된대로)보다 빠른 순서입니다.

주목하십시오 만약에 너는 사용한다 딥 카피를위한 중첩 된 Memberwiseclone, 당신은 클래스의 각 중첩 레벨에 대해 얕은 계수를 수동으로 구현해야하며, 모든 얕은 코피 방법을 호출하여 완전한 클론을 만들어야합니다. 이것은 간단합니다. 총 몇 줄만, 아래 데모 코드를 참조하십시오.

다음은 상대 성능 차이를 보여주는 코드의 출력입니다 (깊은 중첩 된 멤버 위시 코피의 경우 4.77 초 대 직렬화의 경우 39.93 초). 중첩 된 Memberwisecopy를 사용하는 것은 구조물을 복사하는 것만 큼 빠르며, 구조물을 복사하는 것은 이론적 인 최대 속도에 가깝습니다 .NET. 이 주장을 확인하려면 동등한 벤치 마크를 실행해야합니다).

    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, 당신은 클래스의 각 중첩 레벨에 대해 얕은 계수를 수동으로 구현해야하며, 모든 얕은 코피 방법을 호출하여 완전한 클론을 만들어야합니다. 이것은 간단합니다. 총 몇 줄만, 위의 데모 코드를 참조하십시오.

물체를 복제 할 때 "구조물"과 "클래스"사이에는 큰 차이가 있습니다.

  • "struct"가있는 경우 값 유형이므로 복사하면 내용이 복제됩니다.
  • "클래스"가있는 경우 참조 유형이므로 복사하면 포인터를 복사하는 것입니다. 진정한 클론을 만들려면 더 창의적이어야하고 메모리에서 원래 객체의 다른 사본을 만드는 메소드를 사용해야합니다.
  • 객체를 잘못 복제하면 핀 다운 버그가 매우 어려울 수 있습니다. 생산 코드에서는 객체가 제대로 복제되었으며 다른 참조로 인해 손상되지 않았는지 확인하기 위해 체크섬을 구현하는 경향이 있습니다. 이 체크섬은 릴리스 모드에서 꺼질 수 있습니다.
  • 나는이 방법이 매우 유용하다고 생각한다. 종종, 당신은 모든 것이 아니라 물체의 일부만 복제하고 싶다. 객체를 수정 한 다음 수정 된 사본을 대기열에 공급하는 모든 유스 케이스에도 필수적입니다.

업데이트

반사를 사용하여 객체 그래프를 재귀 적으로 걸어 가서 깊은 사본을 수행 할 수 있습니다. WCF는이 기술을 사용하여 모든 어린이를 포함하여 물체를 직렬화합니다. 트릭은 모든 어린이 객체에 발견 할 수있는 속성으로 주석을 달아야합니다. 그러나 성능 이점을 잃을 수도 있습니다.

업데이트

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

Neil의 Serialize/Deserialize Extension Method, Contango의 Nested Memberwiseclone, Alex Burtsev의 반사 기반 확장 방법 및 Automapper를 각각 1 백만 번 사용하여 나만의 속도 테스트를 실행합니다. 직렬화 내용은 15.7 초가 걸리며 가장 느 렸습니다. 그런 다음 10.1 초가 걸리는 Automapper가 왔습니다. 훨씬 빠른 반사 기반 방법은 2.4 초가 걸렸습니다. 지금까지 가장 빠른 것은 중첩 된 Memberwiseclone으로 0.1 초가 걸렸습니다. 각 클래스에 코드를 추가하여 복제하기 위해 성능과 번거 로움에 이릅니다. 성능이 문제가되지 않으면 Alex Burtsev의 방법으로 가십시오. - 사이먼 테우시

나는 Binaryformatter 접근법이 상대적으로 느린다 고 생각합니다 (놀랍게도 나에게 왔습니다!). Protobuf의 요구 사항을 충족하는 경우 일부 객체에 Protobuf .NET을 사용할 수 있습니다. Protobuf 시작 페이지에서 (http://code.google.com/p/protobuf-net/wiki/gettingstarted):

지원되는 유형에 대한 메모 :

커스텀 클래스 :

  • 데이터 계약으로 표시됩니다
  • 매개 변수가없는 생성자가 있습니다
  • Silverlight : 공개적입니다
  • 많은 일반적인 프리미티브 등
  • 하나의 치수 배열 : t [
  • 목록u003CT> / ilistu003CT>
  • 사전u003CTKey, TValue> / idictionaryu003CTKey, TValue>
  • ienumerable을 구현하는 모든 유형u003CT> 추가 (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 문서는 클론이 딥 카피를 수행해야한다는 것을 암시하는 것으로 보이지만 명시 적으로 언급되지는 않습니다.

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};
}
라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top