C# 의 데이터에는 Value Type 와 Reference Type 이 존재한다.
Value Type 은 그 자체가 값인 경우이다.
Reference Type 은 별도의 메모리 공간에 인스탄스가 생성이 되고,
메모리의 위치만을 값으로 가지는 경우다.
배열이나 클래스의 객체는 Reference Type 으로, 실제값은 별도로 저장이된다.
Class 안에 멤버가 모두 Value Type 이라면, 단순히 Clone 을 통해서, 멤버 값이 전부 새로운 값으로 복사가 된다.
그러나, 한 Class 안에 Value Type, 배열, 클래스 객체가 공존한다면,
이 객체를 Clone 을 하게 되면, Value Type 은 정상적으로 복사가 되지만, 나머지 값은 원래 객체의 주소값이 복사가 된다.
원래 객체 / 클론 객체가 모두 동일한 메모리 위치를 가리키기 때문에, 한쪽에서 멤버객체의 값을 변경한다면,
다른곳에서는 의도치 않게 값이 변경되는 결과를 보게 된다.
Deep Clone 이라는 것은 Value / Reference Type 모두 새로운 값으로 복사를 하는 것이다.
2가지 방법이 있는데, 복사하려는 객체가 Serializable 객체라면,
// 2. Serializable 객체에 대한 Deep Clone 을 이용하여,
MemoryStream 을 통해 Serialize 하여 복사가 된다.
그렇지 않은 Class 는,
// 1. Deep Clone 구현 을 이용하여, 복사를 하면 된다.
Class 안의 멤버는 Value Type 과 Reference Type 이 있고,
Reference Type 객체 안에는 또다시, Value Type 과 Reference Type 의 멤버가 있고,
그 안에 있는 Reference 에는 또다시 Value Type 과 Reference Type 있다.
이런식으로 계층간에 여러 단계의 객체가 존재할 수가 있다.
이를 모두 복사해야 하기 때문에, Circular 또는 Recursive 를 통해서 값을 복사를 해야 한다.
using System; using System.Reflection; using System.Collections.Generic; using System.Runtime.Serialization.bformatters.Binary; using System.IO; namespace Hungry.Developer { // Copyright 헝그리개발자(https://bemeal2.tistory.com) // 소스는 자유롭게 사용가능합니다. Copyright 는 삭제하지 마세요. // C# 객체를 복사해주는 클래스 public class ObjectCopy { // 1. Deep Clone 구현 public static T DeepClone<T>(T obj) { if (obj == null) throw new ArgumentNullException("Object cannot be null."); return (T)Process(obj, new Dictionary<object, object>() { }); } private static object Process(object obj, Dictionary<object, object> circular) { if (obj == null) return null; Type type = obj.GetType(); if (type.IsValueType || type == typeof(string)) { return obj; } if (type.IsArray) { if (circular.ContainsKey(obj)) return circular[obj]; string typeNoArray = type.FullName.Replace("[]", string.Empty); Type elementType = Type.GetType(typeNoArray + ", " + type.Assembly.FullName); var array = obj as Array; Array arrCopied = Array.CreateInstance(elementType, array.Length); circular[obj] = arrCopied; for (int i = 0; i < array.Length; i++) { object element = array.GetValue(i); object objCopy = null; if (element != null && circular.ContainsKey(element)) objCopy = circular[element]; else objCopy = Process(element, circular); arrCopied.SetValue(objCopy, i); } return Convert.ChangeType(arrCopied, obj.GetType()); } if (type.IsClass) { if (circular.ContainsKey(obj)) return circular[obj]; object objValue = Activator.CreateInstance(obj.GetType()); circular[obj] = objValue; FieldInfo[] fields = type.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); foreach (FieldInfo field in fields) { object fieldValue = field.GetValue(obj); if (fieldValue == null) continue; object objCopy = circular.ContainsKey(fieldValue) ? circular[fieldValue] : Process(fieldValue, circular); field.SetValue(objValue, objCopy); } return objValue; } else throw new ArgumentException("Unknown type"); } // 2. Serializable 객체에 대한 Deep Clone public static T SerializableDeepClone<T>(T obj) { using (var ms = new MemoryStream()) { var bformatter = new Binarybformatter(); bformatter.Serialize(ms, obj); ms.Position = 0; return (T) bformatter.Deserialize(ms); } } } } |
'개발팁' 카테고리의 다른 글
C# string to int 문자열을 정수형으로 변경하는 여러가지 방법 (2) | 2016.08.10 |
---|---|
erwin r7 에서 Physical / Logical Model 컬럼순서 동기화 (0) | 2016.08.09 |
C# Singleton - Private Constructor 사용방법 (0) | 2016.08.02 |
C# ASP.NET 자동으로 퍼포먼스 로그 남기기 (0) | 2016.07.29 |
C# 어플리케이션 캐쉬 만들기 (0) | 2016.07.26 |