.NET (C#)에서 객체의 깊은 사본을 어떻게 수행합니까? [복제하다
-
02-07-2019 - |
문제
이 질문은 이미 여기에 답이 있습니다.
- 깊은 복제 물체 43 답변
나는 진정한 딥 카피를 원한다. 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 방법은 얕은 사본을 만듭니다.
내 게시물이 도움이 될 수 있습니다.
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};
}