.NET에서 객체의 딥 카피를 어떻게 수행합니까 (구체적으로 C #)? [복제]
- 딥 클로닝 객체 45 답변
나는 진정한 깊은 사본을 원합니다. 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을"
. 그것은 (빠른
세 배 빠른
경우 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); } } }
DeepClone 메서드를 사용하여 [Serializable]으로 표시된 모든 클래스를 확장합니다.
MyClass copy = obj.DeepClone();
Nested MemberwiseClone을
사용 하여 깊은 복사를 수행 할 수 있습니다 . 값 구조체를 복사하는 것과 거의 같은 속도이며 (a) 리플렉션 또는 (b) 직렬화 (이 페이지의 다른 답변에서 설명)보다 훨씬 빠릅니다.딥 카피에 Nested MemberwiseClone을 사용 하는
경우
클래스의 각 중첩 레벨에 대해 ShallowCopy를 수동으로 구현해야하며 DeepCopy는 모든 ShallowCopy 메소드를 호출하여 완전한 클론을 작성해야합니다. 이것은 간단합니다. 총 몇 줄만 아래 데모 코드를 참조하십시오.다음은 상대적 성능 차이를 보여주는 코드 출력입니다 (깊게 중첩 된 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=, Bob.Purchase.Description=\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=, BobsSon.Purchase.Description=\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=, Bob.Purchase.Description=\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: ,\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=, Bob.Purchase.Description=\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=, BobsSon.Purchase.Description=\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=, Bob.Purchase.Description=\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: ,\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: ,\n", sw.Elapsed, total); } Console.ReadKey(); }
다시 깊은 복사에 Nested MemberwiseClone을 사용 하는
경우
클래스의 각 중첩 수준에 대해 ShallowCopy를 수동으로 구현해야하며 DeepCopy는 모든 ShallowCopy 메소드를 호출하여 완전한 복제본을 작성해야합니다. 이것은 간단합니다. 총 몇 줄 밖에되지 않습니다. 위의 데모 코드를 참조하십시오.객체 복제와 관련하여 "struct"와 "class"사이에는 큰 차이가 있습니다.
- "struct"가있는 경우 값 유형이므로 복사 만하면 내용이 복제됩니다.
- "클래스"가있는 경우 참조 유형이므로 복사하면 포인터를 복사하기 만하면됩니다. 진정한 클론을 만들려면 창의력을 발휘해야하며 메모리에 원본 객체의 다른 사본을 만드는 방법을 사용해야합니다.
- 객체를 잘못 복제하면 매우 찾기 어려운 버그가 발생할 수 있습니다. 프로덕션 코드에서는 체크섬을 구현하여 객체가 올바르게 복제되었으며 다른 참조로 인해 손상되지 않았는지 다시 확인하는 경향이 있습니다. 이 체크섬은 해제 모드에서 끌 수 있습니다.
- 이 방법이 매우 유용하다는 것을 알았습니다. 종종 개체 전체가 아닌 개체의 일부만 복제하려고합니다. 또한 객체를 수정 한 다음 수정 된 사본을 대기열에 공급하는 사용 사례에도 필수적입니다.
최신 정보
리플렉션을 사용하여 개체 그래프를 재귀 적으로 탐색하여 딥 카피를 수행하는 것이 가능합니다. WCF는이 기술을 사용하여 모든 자식을 포함하여 개체를 직렬화합니다. 속임수는 모든 자식 개체에 검색 가능하게하는 특성을 추가하는 것입니다. 그러나 일부 성능 이점이 손실 될 수 있습니다.
최신 정보
독립 속도 테스트에 대한 인용문 (아래 주석 참조) :
Neil의 serialize / deserialize 확장 방법, Contango 's Nested MemberwiseClone, Alex Burtsev의 리플렉션 기반 확장 방법 및 AutoMapper를 각각 100 만 번 사용하여 속도 테스트를 실행했습니다. 직렬화 직렬화 해제는 15.7 초가 가장 느 렸습니다. 그런 다음 AutoMapper가 왔으며 10.1 초가 걸렸습니다. 2.4 초가 걸리는 반사 기반 방법이 훨씬 빠릅니다. 0.1 초가 걸리는 Nested MemberwiseClone이 가장 빠릅니다. 각 클래스에 코드를 추가하여 복제하는 성능과 번거 로움으로 귀결됩니다. 성능에 문제가 없다면 Alex Burtsev의 방법으로 진행하십시오. – 사이먼 테시
BinaryFormatter 접근 방식이 상대적으로 느리다고 생각합니다 (놀랍습니다!). ProtoBuf의 요구 사항을 충족하는 경우 일부 개체에 ProtoBuf .NET을 사용할 수 있습니다. ProtoBuf 시작하기 페이지 (
http://code.google.com/p/protobuf-net/wiki/GettingStarted
)에서 :
지원되는 유형에 대한 참고 사항 :
다음과 같은 커스텀 클래스 :
- 데이터 계약으로 표시
- 매개 변수가없는 생성자
- Silverlight의 경우 : 공개
- 많은 공통 프리미티브 등
- 단일 차원 배열 : T []
- List <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
AND 보다 몇 배 빠르며
[Serializable]
속성 이 필요하지 않습니다 .
더 간단한 아이디어가 있습니다. 새로운 선택과 함께 LINQ를 사용하십시오.
public class Fruit { public string Name public int SeedCount } void SomeMethod() { List<Fruit> originalFruits = new List<Fruit>(); originalFruits.Add(new Fruit ); originalFruits.Add(new Fruit ); //Deep Copy List<Fruit> deepCopiedFruits = from f in originalFruits select new Fruit ; }
참고 URL :
도움이 되겠다면 ↓↓↓ 배너 한번만 클릭 해주시면 감사합니다 ^^
'programing' 카테고리의 다른 글
OR은 SQL Server의 CASE 문에서 지원되지 않습니다 (0) | 2020.02.09 |
---|---|
Windows 용 Git에서 파일 이름이 너무 깁니다 (0) | 2020.02.09 |
문자열에서 구두점을 제거하는 가장 좋은 방법 (0) | 2020.02.09 |
JQuery에서 라디오 버튼이 선택되어 있는지 확인하십시오. (0) | 2020.02.09 |
PHP에서 @ 기호를 사용하는 것은 무엇입니까? (0) | 2020.02.09 |