질문자 :NakedBrunch
다음과 같이 하고 싶습니다.
MyObject myObj = GetMyObj(); // Create and fill a new object MyObject newObj = myObj.Clone();
그런 다음 원래 개체에 반영되지 않은 새 개체를 변경합니다.
이 기능이 자주 필요하지 않으므로 필요할 때 새 개체를 만든 다음 각 속성을 개별적으로 복사했지만 항상 더 낫거나 더 우아한 처리 방법이 있다는 느낌을 받습니다. 그 상황.
원본 개체에 변경 사항이 반영되지 않고 복제된 개체를 수정할 수 있도록 개체를 복제하거나 전체 복사하려면 어떻게 해야 합니까?
한 가지 접근 방식은 ICloneable
인터페이스 를 구현하는 것이지만(여기 에 설명되어 있으므로 다시 설명하지 않겠습니다.) 여기 에 얼마 전 코드 프로젝트 에서 찾은 멋진 딥 클론 개체 복사기가 있고 이를 코드에 통합했습니다. 다른 곳에서 언급했듯이 개체를 직렬화할 수 있어야 합니다.
using System; using System.IO; using System.Runtime.Serialization; using System.Runtime.Serialization.Formatters.Binary; /// <summary> /// Reference Article http://www.codeproject.com/KB/tips/SerializedObjectCloner.aspx /// Provides a method for performing a deep copy of an object. /// Binary Serialization is used to perform the copy. /// </summary> public static class ObjectCopier { /// <summary> /// Perform a deep copy of the object via serialization. /// </summary> /// <typeparam name="T">The type of object being copied.</typeparam> /// <param name="source">The object instance to copy.</param> /// <returns>A deep copy of the object.</returns> public static T Clone<T>(T source) { if (!typeof(T).IsSerializable) { throw new ArgumentException("The type must be serializable.", nameof(source)); } // Don't serialize a null object, simply return the default for that object if (ReferenceEquals(source, null)) return default; using var Stream stream = new MemoryStream(); IFormatter formatter = new BinaryFormatter(); formatter.Serialize(stream, source); stream.Seek(0, SeekOrigin.Begin); return (T)formatter.Deserialize(stream); } }
아이디어는 개체를 직렬화한 다음 새로운 개체로 역직렬화한다는 것입니다. 이점은 개체가 너무 복잡해지면 모든 것을 복제하는 것에 대해 걱정할 필요가 없다는 것입니다.
C# 3.0의 새로운 확장 방법 을 사용하려는 경우 다음 서명을 갖도록 방법을 변경하십시오.
public static T Clone<T>(this T source) { // ... }
이제 메서드 호출은 단순히 objectBeingCloned.Clone();
.
EDIT (2015년 1월 10일) 이것을 다시 방문할 것이라고 생각했습니다. 최근에 (Newtonsoft) Json을 사용하여 이를 수행하기 시작했습니다 . 더 가벼워야 하고 [Serializable] 태그의 오버헤드를 피할 수 있습니다. ( NB @atconway는 비공개 멤버가 JSON 방법을 사용하여 복제되지 않는다는 주석을 지적했습니다)
/// <summary> /// Perform a deep Copy of the object, using Json as a serialization method. NOTE: Private members are not cloned using this method. /// </summary> /// <typeparam name="T">The type of object being copied.</typeparam> /// <param name="source">The object instance to copy.</param> /// <returns>The copied object.</returns> public static T CloneJson<T>(this T source) { // Don't serialize a null object, simply return the default for that object if (ReferenceEquals(source, null)) return default; // initialize inner objects individually // for example in default constructor some list property initialized with some values, // but in 'source' these items are cleaned - // without ObjectCreationHandling.Replace default constructor values will be added to result var deserializeSettings = new JsonSerializerSettings {ObjectCreationHandling = ObjectCreationHandling.Replace}; return JsonConvert.DeserializeObject<T>(JsonConvert.SerializeObject(source), deserializeSettings); }
Community Wiki나는 대부분 원시적(primitive)과 목록(list)으로 이루어진 매우 단순한 객체를 위한 복제기를 원했습니다. 객체가 JSON 직렬화 가능하지 않은 경우 이 방법이 트릭을 수행합니다. 이를 위해서는 복제된 클래스에서 인터페이스를 수정하거나 구현할 필요가 없으며 JSON.NET과 같은 JSON 직렬 변환기만 있으면 됩니다.
public static T Clone<T>(T source) { var serialized = JsonConvert.SerializeObject(source); return JsonConvert.DeserializeObject<T>(serialized); }
또한 이 확장 방법을 사용할 수 있습니다.
public static class SystemExtension { public static T Clone<T>(this T source) { var serialized = JsonConvert.SerializeObject(source); return JsonConvert.DeserializeObject<T>(serialized); } }
craastadICloneable 을 사용 하지 않는 이유는 일반 인터페이스가 없기 때문이 아닙니다. 사용하지 않는 이유는 모호하기 때문 입니다. 얕은 사본인지 깊은 사본인지 명확하지 않습니다. 그것은 구현자에게 달려 있습니다.
예, MemberwiseClone
은 얕은 사본을 만들지만 MemberwiseClone
Clone
이 아닙니다. 아마도 존재하지 않는 DeepClone
일 것입니다. ICloneable 인터페이스를 통해 개체를 사용하는 경우 기본 개체가 수행하는 복제 유형을 알 수 없습니다. (그리고 XML 주석은 명확하지 않을 것입니다. 왜냐하면 개체의 Clone 메서드에 대한 주석이 아닌 인터페이스 주석을 얻게 되기 때문입니다.)
내가 보통 하는 일은 내가 원하는 것을 정확히 수행 Copy
Ryan Lundy여기에 링크된 많은 옵션과 이 문제에 대한 가능한 솔루션에 대해 많이 읽은 후 Ian P 의 링크 (다른 모든 옵션은 해당 옵션의 변형임)에 모든 옵션이 매우 잘 요약되어 있으며 최상의 솔루션은 다음에서 제공됩니다. 질문 댓글에 대한 Pedro77 의 링크.
그래서 나는 여기에서 그 2개의 참조 중 관련 부분을 복사할 것입니다. 그렇게 하면 다음과 같이 할 수 있습니다.
C 샤프에서 객체를 복제하는 가장 좋은 방법!
무엇보다도 이것이 우리의 모든 옵션입니다.
표현식 트리에 의한 Fast Deep Copy 기사 에는 직렬화, 리플렉션 및 표현식 트리에 의한 복제 성능 비교도 있습니다.
ICloneable을 선택하는 이유(예: 수동)
Mr Venkat Subramaniam(여기에 중복 링크)이 이유를 자세히 설명합니다 .
그의 모든 기사는 Person , Brain 및 City 의 3가지 객체를 사용하여 대부분의 경우에 적용할 수 있는 예제를 중심으로 돌아갑니다. 우리는 자신의 두뇌를 가지고 있지만 같은 도시를 가진 사람을 복제하고 싶습니다. 위의 다른 방법으로 기사를 가져오거나 읽을 수 있는 모든 문제를 그림으로 그릴 수 있습니다.
이것은 그의 결론을 약간 수정한 것입니다.
New
뒤에 클래스 이름을 지정하여 객체를 복사하면 확장할 수 없는 코드가 생성되는 경우가 많습니다. 프로토타입 패턴의 적용인 복제를 사용하는 것이 이를 달성하는 더 좋은 방법입니다. 그러나 C#(및 Java)에서 제공되는 대로 복제를 사용하는 것도 상당히 문제가 될 수 있습니다. 보호된(비공개) 복사 생성자를 제공하고 복제 메서드에서 호출하는 것이 좋습니다. 이를 통해 객체 생성 작업을 클래스 자체의 인스턴스에 위임할 수 있으므로 확장성을 제공하고 보호된 복사 생성자를 사용하여 객체를 안전하게 생성할 수 있습니다.
이 구현으로 상황이 명확해지기를 바랍니다.
public class Person : ICloneable { private final Brain brain; // brain is final since I do not want // any transplant on it once created! private int age; public Person(Brain aBrain, int theAge) { brain = aBrain; age = theAge; } protected Person(Person another) { Brain refBrain = null; try { refBrain = (Brain) another.brain.clone(); // You can set the brain in the constructor } catch(CloneNotSupportedException e) {} brain = refBrain; age = another.age; } public String toString() { return "This is person with " + brain; // Not meant to sound rude as it reads! } public Object clone() { return new Person(this); } … }
이제 Person에서 클래스를 파생시키는 것을 고려하십시오.
public class SkilledPerson extends Person { private String theSkills; public SkilledPerson(Brain aBrain, int theAge, String skills) { super(aBrain, theAge); theSkills = skills; } protected SkilledPerson(SkilledPerson another) { super(another); theSkills = another.theSkills; } public Object clone() { return new SkilledPerson(this); } public String toString() { return "SkilledPerson: " + super.toString(); } }
다음 코드를 실행해 볼 수 있습니다.
public class User { public static void play(Person p) { Person another = (Person) p.clone(); System.out.println(p); System.out.println(another); } public static void main(String[] args) { Person sam = new Person(new Brain(), 1); play(sam); SkilledPerson bob = new SkilledPerson(new SmarterBrain(), 1, "Writer"); play(bob); } }
생성된 출력은 다음과 같습니다.
This is person with Brain@1fcc69 This is person with Brain@253498 SkilledPerson: This is person with SmarterBrain@1fef6f SkilledPerson: This is person with SmarterBrain@209f4e
개체 수를 세는 경우 여기에 구현된 복제는 개체 수의 정확한 수를 유지합니다.
cregox나는 복제보다 복사 생성자를 선호합니다. 의도는 더 명확합니다.
Nick모든 공용 속성을 복사하는 간단한 확장 방법입니다. 모든 객체에 대해 작동하며 클래스가 [Serializable]
일 필요가 없습니다 . 다른 액세스 수준으로 확장할 수 있습니다.
public static void CopyTo( this object S, object T ) { foreach( var pS in S.GetType().GetProperties() ) { foreach( var pT in T.GetType().GetProperties() ) { if( pT.Name != pS.Name ) continue; ( pT.GetSetMethod() ).Invoke( T, new object[] { pS.GetGetMethod().Invoke( S, null ) } ); } }; }
Konstantin SalavatovCloneExtensions
라이브러리 프로젝트를 만들었습니다. Expression Tree 런타임 코드 컴파일에서 생성된 간단한 할당 작업을 사용하여 빠르고 깊은 복제를 수행합니다.
이것을 어떻게 사용 하는가?
Clone
또는 Copy
메서드를 작성하는 대신 Expression Tree를 사용하여 프로그램이 스스로 수행하도록 합니다. GetClone<T>()
메서드를 사용하면 인스턴스에서 간단히 호출할 수 있습니다.
var newInstance = source.GetClone();
CloningFlags
열거형을 사용하여 source
에서 newInstance
로 복사할 항목을 선택할 수 있습니다.
var newInstance = source.GetClone(CloningFlags.Properties | CloningFlags.CollectionItems);
무엇을 복제할 수 있습니까?
- 기본(int, uint, byte, double, char 등), 알려진 변경 불가능한 유형(DateTime, TimeSpan, String) 및 대리자(Action, Func 등 포함)
- 널 입력 가능
- T[] 배열
- 제네릭 클래스 및 구조체를 포함한 사용자 지정 클래스 및 구조체.
다음 클래스/구조체 멤버는 내부적으로 복제됩니다.
- 읽기 전용 필드가 아닌 public 값
- get 및 set 접근자가 모두 있는 공용 속성 값
- ICollection을 구현하는 형식의 컬렉션 항목
얼마나 빠릅니까?
T
GetClone<T>
가 처음 사용되기 전에 구성원 정보를 한 번만 수집해야 하기 때문에 솔루션은 리플렉션보다 빠릅니다.
T
인스턴스를 두 개 이상 복제할 때 직렬화 기반 솔루션보다 빠릅니다.
그리고 더...
문서에서 생성된 표현식에 대해 자세히 읽어보십시오.
List<int>
대한 샘플 표현식 디버그 목록:
.Lambda #Lambda1<System.Func`4[System.Collections.Generic.List`1[System.Int32],CloneExtensions.CloningFlags,System.Collections.Generic.IDictionary`2[System.Type,System.Func`2[System.Object,System.Object]],System.Collections.Generic.List`1[System.Int32]]>( System.Collections.Generic.List`1[System.Int32] $source, CloneExtensions.CloningFlags $flags, System.Collections.Generic.IDictionary`2[System.Type,System.Func`2[System.Object,System.Object]] $initializers) { .Block(System.Collections.Generic.List`1[System.Int32] $target) { .If ($source == null) { .Return #Label1 { null } } .Else { .Default(System.Void) }; .If ( .Call $initializers.ContainsKey(.Constant<System.Type>(System.Collections.Generic.List`1[System.Int32])) ) { $target = (System.Collections.Generic.List`1[System.Int32]).Call ($initializers.Item[.Constant<System.Type>(System.Collections.Generic.List`1[System.Int32])] ).Invoke((System.Object)$source) } .Else { $target = .New System.Collections.Generic.List`1[System.Int32]() }; .If ( ((System.Byte)$flags & (System.Byte).Constant<CloneExtensions.CloningFlags>(Fields)) == (System.Byte).Constant<CloneExtensions.CloningFlags>(Fields) ) { .Default(System.Void) } .Else { .Default(System.Void) }; .If ( ((System.Byte)$flags & (System.Byte).Constant<CloneExtensions.CloningFlags>(Properties)) == (System.Byte).Constant<CloneExtensions.CloningFlags>(Properties) ) { .Block() { $target.Capacity = .Call CloneExtensions.CloneFactory.GetClone( $source.Capacity, $flags, $initializers) } } .Else { .Default(System.Void) }; .If ( ((System.Byte)$flags & (System.Byte).Constant<CloneExtensions.CloningFlags>(CollectionItems)) == (System.Byte).Constant<CloneExtensions.CloningFlags>(CollectionItems) ) { .Block( System.Collections.Generic.IEnumerator`1[System.Int32] $var1, System.Collections.Generic.ICollection`1[System.Int32] $var2) { $var1 = (System.Collections.Generic.IEnumerator`1[System.Int32]).Call $source.GetEnumerator(); $var2 = (System.Collections.Generic.ICollection`1[System.Int32])$target; .Loop { .If (.Call $var1.MoveNext() != False) { .Call $var2.Add(.Call CloneExtensions.CloneFactory.GetClone( $var1.Current, $flags, $initializers)) } .Else { .Break #Label2 { } } } .LabelTarget #Label2: } } .Else { .Default(System.Void) }; .Label $target .LabelTarget #Label1: }
}
다음 C# 코드와 같은 의미는 무엇입니까?
(source, flags, initializers) => { if(source == null) return null; if(initializers.ContainsKey(typeof(List<int>)) target = (List<int>)initializers[typeof(List<int>)].Invoke((object)source); else target = new List<int>(); if((flags & CloningFlags.Properties) == CloningFlags.Properties) { target.Capacity = target.Capacity.GetClone(flags, initializers); } if((flags & CloningFlags.CollectionItems) == CloningFlags.CollectionItems) { var targetCollection = (ICollection<int>)target; foreach(var item in (ICollection<int>)source) { targetCollection.Add(item.Clone(flags, initializers)); } } return target; }
List<int>
Clone
메서드를 작성하는 방법과 비슷하지 않습니까?
MarcinJuraszek이미 ValueInjecter 또는 Automapper 와 같은 타사 애플리케이션을 사용하고 있다면 다음과 같이 할 수 있습니다.
MyObject oldObj; // The existing object to clone MyObject newObj = new MyObject(); newObj.InjectFrom(oldObj); // Using ValueInjecter syntax
이 방법을 사용하면 개체에 ISerializable
또는 ICloneable
이것은 MVC/MVVM 패턴에서 일반적이므로 이와 같은 간단한 도구가 만들어졌습니다.
GitHub의 ValueInjecter 심층 복제 샘플을 참조하세요.
Michael CoxSilverlight에서 ICloneable을 사용하는 데 문제가 있었지만 직렬화라는 아이디어가 마음에 들었고 XML을 직렬화할 수 있었기 때문에 다음과 같이 했습니다.
static public class SerializeHelper { //Michael White, Holly Springs Consulting, 2009 //michael@hollyspringsconsulting.com public static T DeserializeXML<T>(string xmlData) where T:new() { if (string.IsNullOrEmpty(xmlData)) return default(T); TextReader tr = new StringReader(xmlData); T DocItms = new T(); XmlSerializer xms = new XmlSerializer(DocItms.GetType()); DocItms = (T)xms.Deserialize(tr); return DocItms == null ? default(T) : DocItms; } public static string SeralizeObjectToXML<T>(T xmlObject) { StringBuilder sbTR = new StringBuilder(); XmlSerializer xmsTR = new XmlSerializer(xmlObject.GetType()); XmlWriterSettings xwsTR = new XmlWriterSettings(); XmlWriter xmwTR = XmlWriter.Create(sbTR, xwsTR); xmsTR.Serialize(xmwTR,xmlObject); return sbTR.ToString(); } public static T CloneObject<T>(T objClone) where T:new() { string GetString = SerializeHelper.SeralizeObjectToXML<T>(objClone); return SerializeHelper.DeserializeXML<T>(GetString); } }
Michael White가장 좋은 것은 다음 과 같은 확장 방법 을 구현하는 것입니다.
public static T DeepClone<T>(this T originalObject) { /* the cloning code */ }
그런 다음 솔루션의 어느 곳에서나 사용하십시오.
var copy = anyObject.DeepClone();
다음 세 가지 구현이 가능합니다.
- 직렬화 기준 (가장 짧은 코드)
- 반사에 의해 - 5배 더 빠름
- 표현식 트리 사용 - 20배 더 빠름
연결된 모든 방법은 잘 작동하며 깊이 테스트되었습니다.
frakon짧은 대답은 ICloneable 인터페이스에서 상속한 다음 .clone 기능을 구현하는 것입니다. 클론은 구성원 단위 복사를 수행하고 필요한 구성원에 대해 전체 복사를 수행한 다음 결과 개체를 반환해야 합니다. 이것은 재귀 작업입니다(이를 수행하려면 복제하려는 클래스의 모든 멤버가 값 유형이거나 ICloneable을 구현해야 하며 해당 멤버가 값 유형이거나 ICloneable을 구현해야 하는 식이어야 합니다.)
ICloneable을 사용한 복제에 대한 자세한 설명은 이 문서를 참조하십시오 .
긴 대답은 "그것은 의존한다"이다. 다른 사람들이 언급했듯이 ICloneable은 제네릭에서 지원되지 않으며 순환 클래스 참조에 대한 특별한 고려 사항이 필요하며 실제로 일부에서는 .NET Framework 의 "실수"로 간주합니다. 직렬화 방법은 직렬화할 수 있는 객체에 따라 다르며, 직렬화할 수 없고 제어할 수 없습니다. 커뮤니티에서 "가장 좋은" 관행이 무엇인지에 대해 여전히 많은 논쟁이 있습니다. 실제로 ICloneable이 원래 그렇게 해석된 것처럼 모든 상황에 대한 모든 모범 사례에 맞는 단일 솔루션은 없습니다.
몇 가지 더 많은 옵션에 대해서는 이 개발자 코너 기사 를 참조하십시오(Ian에게 제공).
Zach Burlingame- 기본적으로 ICloneable 인터페이스를 구현한 다음 객체 구조 복사를 구현해야 합니다.
- 모든 구성원의 전체 복사본인 경우 모든 하위 항목도 복제할 수 있는지 확인해야 합니다(선택한 솔루션과 관련이 없음).
- 예를 들어 ORM 개체를 복사하는 경우 대부분의 프레임워크가 세션에 연결된 개체를 하나만 허용하고 이 개체의 복제본을 만들면 안 됩니다(MUST NOT). 이러한 개체의 세션 연결에 대해.
건배.
dimarzionist편집: 프로젝트가 중단되었습니다
알 수 없는 유형에 대한 진정한 복제를 원한다면 fastclone을 살펴볼 수 있습니다.
이는 바이너리 직렬화보다 약 10배 더 빠르게 작동하고 완전한 개체 그래프 무결성을 유지하는 표현식 기반 복제입니다.
즉, 계층에서 동일한 개체를 여러 번 참조하는 경우 복제본에도 단일 인스턴스가 참조됩니다.
복제되는 개체에 대한 인터페이스, 속성 또는 기타 수정이 필요하지 않습니다.
Michael SanderDeepCloner: 복제를 해결하는 빠르고 쉽고 효과적인 NuGet 패키지
모든 답변을 읽은 후 아무도 이 훌륭한 패키지에 대해 언급하지 않았다는 사실에 놀랐습니다.
DeepCloner GitHub 프로젝트
DeepCloner NuGet 패키지
README에 대해 조금 더 자세히 설명하면 다음은 직장에서 선택한 이유입니다.
- 깊은 복사 또는 얕은 복사 가능
- 딥 클로닝에서는 모든 개체 그래프가 유지됩니다.
- 결과 복제가 엄청나게 빠르기 때문에 런타임에 코드 생성을 사용합니다.
- 내부 구조에 의해 복사된 객체, 호출된 메서드 또는 ctor 없음
- 어떻게든 클래스를 표시할 필요가 없습니다(예: Serializable-attribute 또는 구현 인터페이스).
- 복제를 위한 개체 유형을 지정할 필요가 없습니다. 객체는 인터페이스 또는 추상 객체로 캐스팅될 수 있습니다(예: int 배열을 추상 Array 또는 IEnumerable로 복제할 수 있습니다. null도 오류 없이 복제할 수 있음)
- 복제된 개체는 자신이 복제인지 확인할 수 있는 기능이 없습니다(매우 구체적인 방법 제외).
용법:
var deepClone = new { Id = 1, Name = "222" }.DeepClone(); var shallowClone = new { Id = 1, Name = "222" }.ShallowClone();
성능:
README에는 다양한 복제 라이브러리 및 방법의 성능 비교가 포함되어 있습니다. DeepCloner Performance .
요구 사항:
- .NET 4.0 이상 또는 .NET Standard 1.3(.NET Core)
- 완전 신뢰 권한 집합 또는 반영 권한(MemberAccess)이 필요합니다.
alexlomba87일을 단순하게 유지 하고 다른 사람들이 언급한 대로 AutoMapper 를 사용합니다. 한 개체를 다른 개체에 매핑하는 간단한 작은 라이브러리입니다... 개체를 동일한 유형의 다른 개체에 복사하려면 세 줄의 코드만 있으면 됩니다.
MyType source = new MyType(); Mapper.CreateMap<MyType, MyType>(); MyType target = Mapper.Map<MyType, MyType>(source);
대상 개체는 이제 원본 개체의 복사본입니다. 충분히 간단하지 않습니까? 솔루션의 모든 곳에서 사용할 확장 메서드를 만듭니다.
public static T Copy<T>(this T source) { T copy = default(T); Mapper.CreateMap<T, T>(); copy = Mapper.Map<T, T>(source); return copy; }
확장 방법은 다음과 같이 사용할 수 있습니다.
MyType copy = source.Copy();
Stacked일반적으로 ICloneable 인터페이스를 구현하고 Clone을 직접 구현합니다. C# 개체에는 모든 기본 요소에 대해 도움이 될 수 있는 얕은 복사를 수행하는 내장 MemberwiseClone 메서드가 있습니다.
깊은 복사의 경우 자동으로 수행하는 방법을 알 수 있는 방법이 없습니다.
HappyDudeList<T>를 수동으로 딥 복사 해야 하는 .NET의 단점을 극복하기 위해 이것을 생각해 냈습니다.
나는 이것을 사용한다:
static public IEnumerable<SpotPlacement> CloneList(List<SpotPlacement> spotPlacements) { foreach (SpotPlacement sp in spotPlacements) { yield return (SpotPlacement)sp.Clone(); } }
그리고 다른 곳에서:
public object Clone() { OrderItem newOrderItem = new OrderItem(); ... newOrderItem._exactPlacements.AddRange(SpotPlacement.CloneList(_exactPlacements)); ... return newOrderItem; }
이 작업을 수행하는 oneliner를 생각해 냈지만 익명 메서드 블록 내에서 수율이 작동하지 않기 때문에 불가능합니다.
더 나은 방법은 일반 List<T> 복제기를 사용하는 것입니다.
class Utility<T> where T : ICloneable { static public IEnumerable<T> CloneList(List<T> tl) { foreach (T t in tl) { yield return (T)t.Clone(); } } }
Daniel MošmondorQ. 이 답변을 선택하는 이유는 무엇입니까?
- .NET이 가능한 가장 빠른 속도를 원하면 이 답변을 선택하십시오.
- 정말, 정말 쉬운 복제 방법을 원한다면 이 답변을 무시하십시오.
즉, 수정이 필요한 성능 병목 현상이 없는 한 다른 답변을 사용하고 프로파일러로 이를 증명할 수 있습니다 .
다른 방법보다 10배 빠름
딥 클론을 수행하는 다음 방법은 다음과 같습니다.
- 직렬화/역직렬화와 관련된 모든 것보다 10배 빠릅니다.
- .NET이 할 수 있는 이론적인 최대 속도에 거의 가깝습니다.
그리고 그 방법은 ...
최고의 속도를 위해 Nested MemberwiseClone을 사용하여 전체 복사를 수행 할 수 있습니다. 값 구조체를 복사하는 것과 거의 동일한 속도이며 (a) 리플렉션 또는 (b) 직렬화(이 페이지의 다른 답변에 설명됨)보다 훨씬 빠릅니다.
Deep Copy에 Nested MemberwiseClone을 사용하는 경우 클래스의 각 중첩 수준에 대해 ShallowCopy를 수동으로 구현해야 하며, 완전한 복제본을 생성하기 위해 해당 ShallowCopy 메서드를 모두 호출하는 DeepCopy를 수동으로 구현해야 합니다. 이것은 간단합니다. 총 몇 줄이면 아래 데모 코드를 참조하십시오.
다음은 100,000개 클론에 대한 상대적인 성능 차이를 보여주는 코드의 출력입니다.
- 중첩 구조체의 중첩 MemberwiseClone의 경우 1.08초
- 중첩 클래스의 Nested MemberwiseClone의 경우 4.77초
- 직렬화/역직렬화의 경우 39.93초
클래스에서 Nested MemberwiseClone을 사용하면 구조체를 복사하는 것만큼 빠르며 구조체를 복사하는 것은 .NET이 할 수 있는 이론적인 최대 속도에 매우 가깝습니다.
Demo 1 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 2 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 3 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 1 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\n", sw.Elapsed, total); } { Console.Write("Demo 2 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\n", sw.Elapsed, total); } { Console.Write("Demo 3 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 " 사이에는 큰 차이가 있습니다.
- " struct "가 있는 경우 값 유형 이므로 복사하기만 하면 내용이 복제됩니다(그러나 이 게시물의 기술을 사용하지 않는 한 얕은 복제만 생성됨).
- " class "가 있으면 참조 유형 이므로 복사하면 포인터를 복사하는 것뿐입니다. 진정한 복제본을 만들려면 더 창의적이어야 하며 메모리에 원본 개체의 다른 복사본을 만드는 값 유형과 참조 유형 간의 차이점을 사용해야 합니다.
값 유형과 참조 유형의 차이점을 참조하십시오.
디버깅에 도움이 되는 체크섬
- 개체를 잘못 복제하면 핀다운하기 매우 어려운 버그가 발생할 수 있습니다. 프로덕션 코드에서는 개체가 제대로 복제되었는지, 다른 참조에 의해 손상되지 않았는지 다시 확인하기 위해 체크섬을 구현하는 경향이 있습니다. 이 체크섬은 릴리스 모드에서 끌 수 있습니다.
- 나는 이 방법이 매우 유용하다고 생각합니다. 종종 전체가 아닌 개체의 일부만 복제하고 싶을 때가 있습니다.
많은 다른 스레드에서 많은 스레드를 분리하는 데 정말 유용합니다.
이 코드의 훌륭한 사용 사례 중 하나는 중첩 클래스 또는 구조체의 복제본을 대기열에 공급하여 생산자/소비자 패턴을 구현하는 것입니다.
- 우리는 자신이 소유한 클래스를 수정하는 하나 이상의 스레드를 가질 수 있으며 이 클래스의 전체 복사본을
ConcurrentQueue
푸시할 수 있습니다. - 그런 다음 이러한 클래스의 복사본을 가져와 처리하는 하나 이상의 스레드가 있습니다.
이것은 실제로 매우 잘 작동하며 하나 이상의 스레드(소비자)에서 많은 스레드(생산자)를 분리할 수 있습니다.
그리고 이 방법도 눈에 띄게 빠릅니다. 중첩 구조체를 사용하면 중첩 클래스를 직렬화/역직렬화하는 것보다 35배 빠르며 시스템에서 사용 가능한 모든 스레드를 활용할 수 있습니다.
업데이트
분명히 ExpressMapper는 위와 같은 수동 코딩보다 빠르지는 않더라도 빠릅니다. 프로파일러와 비교하는 방법을 확인해야 할 수도 있습니다.
Contango면책 조항: 저는 언급된 패키지의 작성자입니다.
2019년 이 질문에 대한 상위 답변이 여전히 직렬화 또는 리플렉션을 사용하는 방법에 놀랐습니다.
직렬화가 제한적이며(속성, 특정 생성자 등 필요) 매우 느립니다.
BinaryFormatter
에는 Serializable
속성이 JsonConverter
에는 매개 변수가 없는 생성자 또는 속성이 필요하며 읽기 전용 필드나 인터페이스를 잘 처리하지 않으며 둘 다 필요한 것보다 10-30배 느립니다.
표현식 트리
대신 Expression Trees 또는 Reflection.Emit 을 사용하여 복제 코드를 한 번만 생성한 다음 느린 반사 또는 직렬화 대신 컴파일된 코드를 사용할 수 있습니다.
스스로 문제를 발견하고 만족스러운 해결책을 찾지 못하자, 나는 바로 그 일을 하고 모든 유형에서 작동하며 사용자 정의 작성 코드만큼 빠른 패키지를 만들기로 결정했습니다.
GitHub에서 프로젝트를 찾을 수 있습니다: https://github.com/marcelltoth/ObjectCloner
용법
NuGet에서 설치할 수 있습니다. ObjectCloner
패키지를 가져와 다음과 같이 사용하십시오.
var clone = ObjectCloner.DeepClone(original);
또는 확장으로 개체 유형을 오염시키는 데 신경 쓰지 않으면 ObjectCloner.Extensions
도 가져 와서 다음을 작성하십시오.
var clone = original.DeepClone();
성능
클래스 계층을 복제하는 간단한 벤치마크는 Reflection을 사용하는 것보다 ~3배, Newtonsoft.Json 직렬화보다 ~12배, 강력하게 제안된 BinaryFormatter
보다 ~36배 빠른 성능을 보여주었습니다.
Marcell Toth딥 카피 구현은 다음과 같습니다.
public static object CloneObject(object opSource) { //grab the type and create a new instance of that type Type opSourceType = opSource.GetType(); object opTarget = CreateInstanceOfType(opSourceType); //grab the properties PropertyInfo[] opPropertyInfo = opSourceType.GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); //iterate over the properties and if it has a 'set' method assign it from the source TO the target foreach (PropertyInfo item in opPropertyInfo) { if (item.CanWrite) { //value types can simply be 'set' if (item.PropertyType.IsValueType || item.PropertyType.IsEnum || item.PropertyType.Equals(typeof(System.String))) { item.SetValue(opTarget, item.GetValue(opSource, null), null); } //object/complex types need to recursively call this method until the end of the tree is reached else { object opPropertyValue = item.GetValue(opSource, null); if (opPropertyValue == null) { item.SetValue(opTarget, null, null); } else { item.SetValue(opTarget, CloneObject(opPropertyValue), null); } } } } //return the new item return opTarget; }
dougajmcdonald다른 프로젝트에서 내 모든 요구 사항을 충족하는 복제기를 찾을 수 없었기 때문에 복제기 요구 사항을 충족하도록 코드를 조정하는 대신 다른 코드 구조에 맞게 구성하고 조정할 수 있는 딥 복제기를 만들었습니다. 복제할 코드에 주석을 추가하거나 기본 동작을 갖도록 코드를 그대로 두면 됩니다. 리플렉션, 유형 캐시를 사용하며 fastflect를 기반으로 합니다. 복제 프로세스는 엄청난 양의 데이터와 높은 개체 계층 구조에 대해 매우 빠릅니다(다른 반사/직렬화 기반 알고리즘에 비해).
https://github.com/kalisohn/CloneBehave
너겟 패키지로도 사용 가능: https://www.nuget.org/packages/Clone.Behave/1.0.0
예: 다음 코드는 주소를 deepClone하지만 _currentJob 필드의 얕은 복사만 수행합니다.
public class Person { [DeepClone(DeepCloneBehavior.Shallow)] private Job _currentJob; public string Name { get; set; } public Job CurrentJob { get{ return _currentJob; } set{ _currentJob = value; } } public Person Manager { get; set; } } public class Address { public Person PersonLivingHere { get; set; } } Address adr = new Address(); adr.PersonLivingHere = new Person("John"); adr.PersonLivingHere.BestFriend = new Person("James"); adr.PersonLivingHere.CurrentJob = new Job("Programmer"); Address adrClone = adr.Clone(); //RESULT adr.PersonLivingHere == adrClone.PersonLivingHere //false adr.PersonLivingHere.Manager == adrClone.PersonLivingHere.Manager //false adr.PersonLivingHere.CurrentJob == adrClone.PersonLivingHere.CurrentJob //true adr.PersonLivingHere.CurrentJob.AnyProperty == adrClone.PersonLivingHere.CurrentJob.AnyProperty //true
kalisohn리플렉션을 통해 구현되는 것도 보았습니다. 기본적으로 개체의 멤버를 반복하고 새 개체에 적절하게 복사하는 메서드가 있었습니다. 참조 유형이나 컬렉션에 도달했을 때 자체적으로 재귀 호출을 수행했다고 생각합니다. 리플렉션은 비용이 많이 들지만 꽤 잘 작동했습니다.
xr280xr코드 생성기
우리는 수동 구현을 통한 직렬화에서 반영에 이르기까지 많은 아이디어를 보았고 CGbR 코드 생성기를 사용하여 완전히 다른 접근 방식을 제안하고 싶습니다. 클론 생성 방법은 메모리와 CPU 효율성이 높기 때문에 표준 DataContractSerializer보다 300배 더 빠릅니다.
ICloneable
하여 부분 클래스 정의만 하면 나머지는 생성기가 수행합니다.
public partial class Root : ICloneable { public Root(int number) { _number = number; } private int _number; public Partial[] Partials { get; set; } public IList<ulong> Numbers { get; set; } public object Clone() { return Clone(true); } private Root() { } } public partial class Root { public Root Clone(bool deep) { var copy = new Root(); // All value types can be simply copied copy._number = _number; if (deep) { // In a deep clone the references are cloned var tempPartials = new Partial[Partials.Length]; for (var i = 0; i < Partials.Length; i++) { var value = Partials[i]; value = value.Clone(true); tempPartials[i] = value; } copy.Partials = tempPartials; var tempNumbers = new List<ulong>(Numbers.Count); for (var i = 0; i < Numbers.Count; i++) { var value = Numbers[i]; tempNumbers.Add(value); } copy.Numbers = tempNumbers; } else { // In a shallow clone only references are copied copy.Partials = Partials; copy.Numbers = Numbers; } return copy; } }
참고: 최신 버전에는 더 많은 null 검사가 있지만 더 나은 이해를 위해 생략했습니다.
Toxantron확장 만들기:
public static T Clone<T>(this T theObject) { string jsonData = JsonConvert.SerializeObject(theObject); return JsonConvert.DeserializeObject<T>(jsonData); }
그리고 다음과 같이 호출합니다.
NewObject = OldObject.Clone();
Sean McAvoy나는 다음과 같은 Copyconstructors를 좋아합니다.
public AnyObject(AnyObject anyObject) { foreach (var property in typeof(AnyObject).GetProperties()) { property.SetValue(this, property.GetValue(anyObject)); } foreach (var field in typeof(AnyObject).GetFields()) { field.SetValue(this, field.GetValue(anyObject)); } }
복사할 내용이 더 있으면 추가하세요.
LuckyLikey이 방법은 나를 위해 문제를 해결했습니다.
private static MyObj DeepCopy(MyObj source) { var DeserializeSettings = new JsonSerializerSettings { ObjectCreationHandling = ObjectCreationHandling.Replace }; return JsonConvert.DeserializeObject<MyObj >(JsonConvert.SerializeObject(source), DeserializeSettings); }
다음과 같이 사용하십시오. MyObj a = DeepCopy(b);
GorvGoyl직렬화/역직렬화를 중계하지 않고 저에게 효과적이었던 빠르고 쉬운 솔루션이 있습니다.
public class MyClass { public virtual MyClass DeepClone() { var returnObj = (MyClass)MemberwiseClone(); var type = returnObj.GetType(); var fieldInfoArray = type.GetRuntimeFields().ToArray(); foreach (var fieldInfo in fieldInfoArray) { object sourceFieldValue = fieldInfo.GetValue(this); if (!(sourceFieldValue is MyClass)) { continue; } var sourceObj = (MyClass)sourceFieldValue; var clonedObj = sourceObj.DeepClone(); fieldInfo.SetValue(returnObj, clonedObj); } return returnObj; } }
편집 : 필요
using System.Linq; using System.Reflection;
그렇게 사용했어요
public MyClass Clone(MyClass theObjectIneededToClone) { MyClass clonedObj = theObjectIneededToClone.DeepClone(); }
Daniele D.이 차례를 따라라:
- 정의
ISelf<T>
읽기 전용으로 Self
복귀하는 것이 속성 T
및 ICloneable<out T>
에서 도출 ISelf<T>
방법을 포함하고, T Clone()
. - 그런 다음
MemberwiseClone
을 전달된 유형으로 캐스팅 protected virtual generic VirtualClone
을 구현 CloneBase
- 각 파생 형식은 기본 복제 메서드를 호출한 다음 부모 VirtualClone 메서드가 아직 처리하지 않은 파생 형식의 측면을 적절하게 복제하기 위해 필요한 모든 작업을 수행하여
VirtualClone
상속의 다양성을 최대화하기 위해 공개 복제 기능을 노출하는 클래스는 sealed
되어야 하지만 복제가 없다는 점을 제외하고는 동일한 기본 클래스에서 파생되어야 합니다. 명시적 복제 가능 유형의 변수를 전달하는 대신 ICloneable<theNonCloneableType>
유형의 매개변수를 사용하십시오. Foo
DerivedFoo
의 복제 가능한 파생물과 함께 작동할 것으로 예상하는 루틴을 Foo
복제 불가능한 파생물 생성도 허용합니다.
supercat나는 당신이 이것을 시도 할 수 있다고 생각합니다.
MyObject myObj = GetMyObj(); // Create and fill a new object MyObject newObj = new MyObject(myObj); //DeepClone it
Sudhanva Kotabagi이 질문에 대한 거의 모든 답변이 불만족 스럽거나 제 상황에서 제대로 작동하지 않기 때문에 저는 완전히 리플렉션으로 구현되고 여기에서 모든 요구를 해결한 AnyClone을 작성했습니다. 복잡한 구조의 복잡한 시나리오에서 직렬화가 작동하도록 할 수 없었고 IClonable
은 이상적이지 않습니다. 실제로는 필요하지도 않습니다.
표준 무시 속성은 [IgnoreDataMember]
, [NonSerialized]
사용하여 지원됩니다. 복잡한 컬렉션, 설정자가 없는 속성, 읽기 전용 필드 등을 지원합니다.
나와 같은 문제에 직면한 다른 누군가에게 도움이 되길 바랍니다.
Michael Brown출처 : http:www.stackoverflow.com/questions/78536/deep-cloning-objects