심층 복제 개체
다음과 같은 작업을 수행하고 싶습니다.
MyObject myObj = GetMyObj(); // Create and fill a new object
MyObject newObj = myObj.Clone();
그런 다음 원래 개체에 반영되지 않은 새 개체를 변경합니다.
이 기능이 필요한 경우가 많지 않기 때문에 필요할 때는 새로운 개체를 생성한 다음 각 속성을 개별적으로 복사하는 방법에 의존해 왔지만, 항상 상황을 더 낫거나 더 우아한 방식으로 처리할 수 있다는 느낌이 듭니다.
원래 개체에 변경 사항이 반영되지 않고 복제된 개체를 수정할 수 있도록 개체를 복제하거나 딥 복사하려면 어떻게 해야 합니까?
한 가지 접근 방식은 인터페이스를 구현하는 것이지만(여기서 설명하므로 반복하지 않겠습니다), 여기 얼마 전에 코드 프로젝트에서 찾은 멋진 딥 클론 개체 복사기가 있습니다.다른 곳에서 언급한 것처럼 개체를 직렬화해야 합니다.
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);
}
저는 원시적인 것들과 목록들로 이루어진 매우 단순한 물체들을 위한 복제기를 원했습니다.객체가 JSON 직렬화 가능한 상태라면 이 방법이 유용합니다.이를 위해서는 복제된 클래스의 인터페이스를 수정하거나 구현할 필요가 없으며 JSON과 같은 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);
}
}
ICloneable을 사용하지 않는 이유는 일반 인터페이스가 없기 때문이 아닙니다.그것을 사용하지 않는 이유는 모호하기 때문입니다.얕은 복사본을 얻었는지 깊은 복사본을 얻었는지는 명확하지 않습니다. 이는 구현자에게 달려 있습니다.
네.MemberwiseClone
는 반대입니다.MemberwiseClone
그렇지 않은Clone
아마, 아도마일 것입니다.DeepClone
존재하지 않는.IC 복제 가능한 인터페이스를 통해 개체를 사용하는 경우 기본 개체가 어떤 복제를 수행하는지 알 수 없습니다. (그리고 XML 주석은 개체의 복제 방법에 대한 주석이 아닌 인터페이스 주석을 얻을 수 있기 때문에 명확하지 않습니다.)
제가 주로 하는 것은 단순히 만드는 것입니다.Copy
내가 원하는 대로 하는 방법.
여기에 연결된 많은 옵션과 이 문제에 대한 가능한 해결책에 대해 많이 읽은 후, 저는 모든 옵션이 Ian P의 링크에서 꽤 잘 요약되었다고 생각합니다(다른 모든 옵션은 그러한 것의 변형입니다). 그리고 최고의 솔루션은 질문 코멘트에 대한 Pedro77의 링크에서 제공됩니다.
그래서 저는 여기에 그 두 개의 참고문헌의 관련 부분을 복사할 것입니다.우리가 가질 수 있는 방법:
C 샤프에서 물체를 복제하기 위해 해야 할 가장 좋은 일!
무엇보다도, 이것들이 우리의 모든 선택사항입니다.
- ICloneable을 사용한 수동 작업(낮고 유형이 안전하지 않음)
- ICloneable을 사용하는 MemberwiseClone
- 활성화기를 사용하여 반영합니다.인스턴스 생성 및 재귀 MemberwiseClone 복제
- johnc의 선호하는 답변에 의해 지적된 직렬화
- 중간 언어, 어떻게 작동하는지 전혀 모릅니다.
- Havard Straden의 이 사용자 정의 복제 프레임워크와 같은 확장 방법
- 표현식 트리
표현 트리의 빠른 딥 복사 기사에는 직렬화, 반사 및 표현 트리별 클로닝 성능 비교도 나와 있습니다.
IC 복제 가능을 선택하는 이유(예: 수동)
Venkat Subramaniam 씨(여기 중복 링크)는 그 이유를 매우 자세히 설명합니다.
그의 모든 기사는 다음과 같은 세 가지 객체를 사용하여 대부분의 경우에 적용할 수 있는 예제를 중심으로 진행됩니다.사람, 두뇌, 도시.우리는 사람을 복제하기를 원합니다. 그것은 그들만의 뇌를 가질 것이지만 같은 도시를 가질 것입니다.위의 다른 방법으로 기사를 가져오거나 읽을 수 있는 모든 문제를 상상할 수 있습니다.
이것은 그의 결론에 대한 나의 약간 수정된 버전입니다.
하여 복사
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);
}
…
}
이제 사용자로부터 파생된 클래스를 갖는 것을 고려해 보십시오.
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
개체 수를 계속 계산하면 여기서 구현된 클론이 개체 수를 정확하게 계산합니다.
복제보다 복사 생성자를 선호합니다.의도가 더 명확합니다.
모든 공용 속성을 복사할 수 있는 간단한 확장 방법입니다.모든 개체에 대해 작동하며 클래스가 필요하지 않습니다.[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 ) } );
}
};
}
방금 도서관 프로젝트를 만들었습니다.Expression Tree 런타임 코드 컴파일에 의해 생성된 단순 할당 작업을 사용하여 빠르고 심층적인 복제를 수행합니다.
어떻게 사용하나요?
에 자신것의을쓰대신에는대에▁instead신▁your.Clone
또는Copy
필드와 속성 사이에 할당 톤이 있는 메서드는 프로그램이 표현식 트리를 사용하여 직접 수행하도록 합니다. GetClone<T>()
확장 메서드로 표시된 메서드를 사용하면 인스턴스에서 간단히 호출할 수 있습니다.
var newInstance = source.GetClone();
여러분은 대할상선수있다니에서 할 수 .source
newInstance
용사를 CloningFlags
열거형:
var newInstance
= source.GetClone(CloningFlags.Properties | CloningFlags.CollectionItems);
무엇을 복제할 수 있습니까?
- 원시(int, int, 바이트, 이중, 문자 등), 알려진 불변 유형(날짜 시간, 시간 범위, 문자열) 및 대리인(액션, Func 등)
- 무효
- T[] 배열
- 일반 클래스 및 구조체를 포함한 사용자 지정 클래스 및 구조체입니다.
다음 클래스/구조체 멤버가 내부적으로 복제됩니다.
- 읽기 전용이 아닌 공용 필드의 값
- get 및 set 접근자가 모두 있는 공용 속성 값
- ICollection을 구현하는 유형에 대한 컬렉션 항목
얼마나 빠른가요?
는 한보다 빠릅니다.GetClone<T>
는 특정 의 지정된유형대에처사해용니다됩음로으▁▁for▁▁time▁is▁theT
.
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;
}
당신이 자신의 작품을 쓰는 것과 같지 않아요?Clone
방법한의 .List<int>
?
이미 Value와 같은 타사 애플리케이션을 사용 중인 경우Injector(인젝터) 또는 Automapper(오토매퍼)는 다음과 같은 작업을 수행할 수 있습니다.
MyObject oldObj; // The existing object to clone
MyObject newObj = new MyObject();
newObj.InjectFrom(oldObj); // Using ValueInjecter syntax
이방을사구필없요습다니가할현면을 구현할 필요가 .ISerializable
또는ICloneable
당신의 물건에.이는 MVC/MVVM 패턴과 공통적으로 발생하기 때문에 이와 같은 간단한 도구가 만들어졌습니다.
가치 참조GitHub에 주입기 딥 클로닝 샘플.
가장 좋은 방법은 다음과 같은 확장 방법을 구현하는 것입니다.
public static T DeepClone<T>(this T originalObject)
{ /* the cloning code */ }
그런 다음 솔루션의 아무 곳에서나 사용합니다.
var copy = anyObject.DeepClone();
다음과 같은 세 가지 구현이 가능합니다.
연결된 모든 방법은 잘 작동하며 깊이 테스트되었습니다.
Silverlight에서 ICloneable을 사용하는 데 문제가 있었지만, 저는 serialization의 아이디어가 마음에 들어 XML을 serialize할 수 있어서 이렇게 했습니다.
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);
}
}
DeepCloner:복제를 해결하기 위한 빠르고 쉽고 효과적인 NuGet 패키지
모든 답변을 읽은 후 아무도 이 훌륭한 패키지에 대해 언급하지 않아 놀랐습니다.
README에 대해 좀 더 자세히 설명하면, 우리가 직장에서 이 제품을 선택한 이유는 다음과 같습니다.
- 깊이 또는 얕게 복사할 수 있습니다.
- 딥 클로닝에서는 모든 개체 그래프가 유지됩니다.
- 결과 클로닝이 엄청나게 빠르기 때문에 런타임에 코드 생성 사용
- 내부 구조에 의해 복사된 개체, 호출된 메서드 또는 cctor 없음
- 클래스를 어떻게든 표시할 필요가 없습니다(예: 직렬화 가능 속성 또는 인터페이스 구현).
- 복제할 개체 유형을 지정할 필요가 없습니다.개체는 인터페이스에 캐스트하거나 추상 개체로 만들 수 있습니다(예: int의 배열을 추상 어레이 또는 IEnumberable로 복제할 수 있으며 null이라도 오류 없이 복제할 수 있습니다).
- 복제된 개체에 복제 여부를 확인할 수 있는 기능이 없음(단, 매우 구체적인 방법은 다음과 같습니다.
용도:
var deepClone = new { Id = 1, Name = "222" }.DeepClone();
var shallowClone = new { Id = 1, Name = "222" }.ShallowClone();
성능:
README에는 다양한 복제 라이브러리 및 방법의 성능 비교가 포함되어 있습니다.심층 클로너 성능.
요구 사항:
- .NET 4.0 이상 또는 .NET 표준 1.3(.NET 코어)
- 전체 신뢰 권한 세트 또는 반영 권한(MemberAccess)이 필요합니다.
간단히 말해 ICloneable 인터페이스에서 상속한 다음 .clone 기능을 구현하는 것입니다.복제는 구성원별 복사를 수행하고 필요한 구성원에 대해 전체 복사를 수행한 다음 결과 개체를 반환해야 합니다.이것은 반복 작업입니다. 복제할 클래스의 모든 멤버가 값 유형이거나 ICloneable을 구현하고 해당 멤버가 값 유형이거나 ICloneable을 구현해야 합니다.
ICloneable을 사용한 복제에 대한 자세한 설명은 이 문서를 참조하십시오.
긴 대답은 "그것은 의존한다"입니다.다른 사람들이 언급했듯이 ICloneable은 일반적으로 지원되지 않으며 순환 클래스 참조에 대한 특별한 고려 사항이 필요하며 실제로 일부에서는 에서 "실수"로 간주됩니다.NET Framework.직렬화 방법은 직렬화할 수 있는 개체에 따라 달라지며, 이러한 개체는 직렬화할 수 없고 사용자가 제어할 수 없습니다.지역 사회에서는 아직도 어느 것이 "최고의" 관행인지에 대한 많은 논쟁이 있습니다.실제로 ICloneable과 같은 모든 상황에 적합한 단일 크기 솔루션은 없습니다.
몇 가지 추가 옵션(Ian에 대한 크레딧)은 이 개발자 코너 기사를 참조하십시오.
- 기본적으로 IC 복제 가능 인터페이스를 구현한 다음 객체 구조 복사를 실현해야 합니다.
- 모든 구성원의 전체 복사본인 경우 모든 하위 항목도 복제할 수 있는지 확인해야 합니다(선택한 솔루션과 관련이 없음).
- 이 프로세스 중에 ORM 개체를 복사할 때 대부분의 프레임워크에서 세션에 하나의 개체만 연결할 수 있고 이 개체의 복제본을 만들지 않아야 하거나 가능하면 이러한 개체의 세션 연결에 신경을 써야 하는 등 몇 가지 제한 사항을 알아야 합니다.
건배.
편집: 프로젝트가 중단되었습니다.
알 수 없는 유형으로 진정한 복제를 원할 경우 빠른 복제를 검토할 수 있습니다.
이는 표현 기반 복제가 이진 직렬화보다 약 10배 빠르게 작동하고 완전한 개체 그래프 무결성을 유지하는 것입니다.
즉, 계층 구조에서 동일한 개체를 여러 번 참조하는 경우 복제본에도 단일 인스턴스가 참조됩니다.
복제할 개체에 대한 인터페이스, 특성 또는 기타 수정 사항이 필요하지 않습니다.
단순성을 유지하고 다른 사람들이 언급한 것처럼 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();
일반적으로 ICloneable 인터페이스를 구현하고 직접 복제를 구현합니다.C# 객체에는 모든 기본 요소에 도움이 되는 얕은 복사를 수행하는 기본 MemberwiseClone 메서드가 있습니다.
딥 복사의 경우 자동으로 수행하는 방법을 알 수 없습니다.
고지 사항:저는 언급된 패키지의 작성자입니다.
2019년 이 질문에 대한 상위 답변이 아직도 연작화나 성찰을 사용하고 있다는 사실에 놀랐습니다.
직렬화는 제한적이며(속성, 특정 생성자 등 필요) 매우 느림
BinaryFormatter
다음이 필요합니다.Serializable
기하다여,JsonConverter
매개 변수가 없는 생성자 또는 속성이 필요하며 읽기 전용 필드나 인터페이스를 잘 처리하지 않으며 둘 다 필요한 것보다 10-30배 느립니다.
표현식 트리
대신 표현식 트리 또는 리플렉션을 사용할 수 있습니다.내보내기 - 복제 코드를 한 번만 생성한 다음 느린 반사 또는 직렬화 대신 컴파일된 코드를 사용합니다.
직접 문제를 발견했지만 만족할 만한 해결책이 보이지 않자, 모든 유형에서 작동하고 사용자 지정 작성 코드만큼 빠른 패키지를 만들기로 결정했습니다.
GitHub: https://github.com/marcelltoth/ObjectCloner 에서 프로젝트를 찾을 수 있습니다.
사용.
NuGet에서 설치할 수 있습니다.그 둘 중 하나는ObjectCloner
패키지화하여 다음과 같이 사용:
var clone = ObjectCloner.DeepClone(original);
또는 확장자로 개체 유형을 오염시키는 것이 괜찮다면 다음과 같이 하십시오.ObjectCloner.Extensions
또한 다음과 같이 기록합니다.
var clone = original.DeepClone();
성능
클래스 계층을 복제하는 간단한 벤치마크는 Reflection을 사용하는 것보다 3배, Newtonsoft보다 12배 더 빠른 성능을 보여주었습니다.및 보다 ~BinaryFormatter
.
저는 이것을 극복하기 위해 생각해냈습니다.목록을 수동으로 딥 복사해야 하는 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;
}
저는 이것을 할 수 있는 하나의 라인을 생각해 내려고 했지만, 그것은 가능하지 않습니다. 왜냐하면 익명의 메소드 블록 안에서 수율이 작동하지 않기 때문입니다.
일반 목록을 사용하는 것이 좋습니다.복제자:
class Utility<T> where T : ICloneable
{
static public IEnumerable<T> CloneList(List<T> tl)
{
foreach (T t in tl)
{
yield return (T)t.Clone();
}
}
}
Q. 제가 왜 이 답을 선택해야 합니까?
- 가장 빠른 속도를 원하는 경우 이 대답을 선택합니다.NET은 할 수 있습니다.
- 정말 정말 쉬운 복제 방법을 원하는 경우 이 대답을 무시하십시오.
즉, 수정이 필요한 성능 병목 현상이 발생하지 않은 경우 프로파일러를 사용하여 이를 입증할 수 있는 경우가 아니라면 다른 답변을 사용하십시오.
다른 방법보다 10배 빠른 속도
딥 클론을 수행하는 방법은 다음과 같습니다.
- 직렬화/직렬화와 관련된 모든 것보다 10배 더 빠릅니다.
- 이론적인 최고 속도에 꽤 가깝습니다.NET은 할 수 있습니다.
그리고 그 방법은...
Nested MemberwiseClone을 사용하여 전체 복사를 수행할 수 있습니다.값 구조를 복사하는 것과 거의 동일한 속도이며 (a) 반사 또는 (b) 직렬화보다 훨씬 빠릅니다(이 페이지의 다른 답변에서 설명).
전체 복제본에 대해 중첩된 구성원별 복제본을 사용하는 경우 클래스의 각 중첩 수준에 대해 HelowCopy를 수동으로 구현해야 하며, 모든 HelowCopy 메서드를 호출하는 DeepCopy를 사용하여 DeepCopy를 구현해야 합니다.이 방법은 간단합니다. 총 몇 줄만 사용할 수 있습니다. 아래 데모 코드를 참조하십시오.
다음은 100,000개 클론에 대한 상대적인 성능 차이를 보여주는 코드의 출력입니다.
- 중첩된 구조에서 중첩된 MemberswiseClone의 경우 1.08초
- 중첩 클래스에서 중첩된 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;
}
}
그런 다음 메인에서 데모를 호출합니다.
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();
}
다시 말하지만, 전체 복제본을 만들기 위해 중첩된 구성원별 복제본을 사용하는 경우 클래스의 각 중첩 수준에 대해 HelowCopy를 수동으로 구현하고, 모든 HelowCopy 메서드를 호출하는 DeepCopy를 구현해야 합니다.이 방법은 간단합니다. 총 몇 줄만 사용할 수 있습니다. 위의 데모 코드를 참조하십시오.
값 유형 대참조 유형
개체 복제와 관련하여 "구조"와 "클래스" 사이에는 큰 차이가 있습니다.
- "struct"가 있는 경우에는 값 유형이므로 복사만 하면 내용이 복제됩니다(그러나 이 게시물의 기술을 사용하지 않는 한 얕은 복제만 가능합니다).
- 클래스가 있으면 참조 유형이므로, 클래스를 복사하면 포인터를 해당 클래스에 복사하기만 하면 됩니다.진정한 복제본을 만들려면 보다 창의적이어야 하며, 값 유형과 참조 유형 간의 차이를 사용하여 메모리에 원래 개체의 다른 복사본을 생성해야 합니다.
디버깅에 도움이 되는 체크섬
- 개체를 잘못 복제하면 핀 다운이 매우 어려운 버그가 발생할 수 있습니다.프로덕션 코드에서는 개체가 제대로 복제되었는지, 그리고 개체에 대한 다른 참조로 인해 손상되지 않았는지 두 번 확인하기 위해 체크섬을 구현하는 경향이 있습니다.이 체크섬은 릴리스 모드에서 끌 수 있습니다.
- 저는 이 방법이 매우 유용하다고 생각합니다. 종종 전체가 아니라 개체의 일부만 복제하려고 합니다.
많은 스레드를 다른 스레드에서 분리하는 데 매우 유용합니다.
이 코드의 한 가지 우수한 사용 사례는 생산자/소비자 패턴을 구현하기 위해 중첩된 클래스 또는 구조의 복제본을 대기열에 공급하는 것입니다.
- 하나 이상의 스레드가 소유한 클래스를 수정한 다음 이 클래스의 전체 복사본을 다음으로 밀어넣을 수 있습니다.
ConcurrentQueue
. - 그런 다음 하나 이상의 스레드가 이러한 클래스의 복사본을 꺼내어 처리합니다.
이것은 실제로 매우 잘 작동하며, 하나 이상의 스레드(소비자)에서 많은 스레드(생산자)를 분리할 수 있습니다.
또한 이 방법은 매우 빠릅니다. 중첩된 구조를 사용하면 중첩된 클래스를 직렬화/직렬화하는 것보다 35배 더 빠르며 기계에서 사용할 수 있는 모든 스레드를 활용할 수 있습니다.
갱신하다
분명히 ExpressMapper는 위와 같은 수동 코딩보다 빠르지는 않지만 빠릅니다.프로파일러와 비교해 봐야 할 것 같아요
확장자 만들기:
public static T Clone<T>(this T theObject)
{
string jsonData = JsonConvert.SerializeObject(theObject);
return JsonConvert.DeserializeObject<T>(jsonData);
}
그리고 이렇게 부릅니다.
NewObject = OldObject.Clone();
저는 반성을 통해서도 그것이 실행되는 것을 보았습니다.기본적으로 개체의 구성원을 통해 반복하고 새 개체에 적절하게 복사하는 방법이 있었습니다.참조 유형이나 컬렉션에 도달했을 때, 저는 그것이 스스로 재귀적인 호출을 했다고 생각합니다.반사는 비용이 많이 들지만, 꽤 효과가 있었습니다.
딥 카피 구현은 다음과 같습니다.
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;
}
여러 프로젝트에서 모든 요구 사항을 충족하는 클론을 찾을 수 없었기 때문에, 코드를 클론 요구 사항에 맞게 조정하는 대신 다른 코드 구조에 맞게 구성하고 조정할 수 있는 딥 클론을 만들었습니다.복제할 코드에 주석을 추가하거나 기본 동작을 그대로 유지하면 됩니다.이것은 반사, 타입 캐시를 사용하며 더 빠른 플렛트를 기반으로 합니다.복제 프로세스는 다른 반사/직렬화 기반 알고리즘에 비해 많은 양의 데이터와 높은 개체 계층 구조에 대해 매우 빠릅니다.
https://github.com/kalisohn/CloneBehave
너겟 패키지로도 이용 가능: https://www.nuget.org/packages/Clone.Behave/1.0.0
예:다음 코드는 Clone Address를 딥하게 만들지만 _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
코드 생성기
우리는 수동 구현에 대한 직렬화에서 성찰에 이르기까지 많은 아이디어를 보아 왔으며 CGbR Code Generator를 사용하여 완전히 다른 접근 방식을 제안하고자 합니다.클론 생성 방법은 메모리 및 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 검사가 더 많지만, 더 잘 이해하기 위해 생략했습니다.
저는 다음과 같은 Copy 생성자를 좋아합니다.
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));
}
}
복사할 내용이 더 있으면 추가합니다.
이 방법으로 문제가 해결되었습니다.
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);
여기 직렬화/직렬화에 대한 릴레이 없이도 작동하는 빠르고 쉬운 솔루션이 있습니다.
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();
}
다음 단계를 수행합니다.
- 를 합니다.
ISelf<T>
전용으로Self
T
,그리고.ICloneable<out T>
은 서에유래에서 한 것입니다.ISelf<T>
는 방법을 합니다.T Clone()
. - 그런 다음 정의합니다.
CloneBase
를하는유형을protected virtual generic VirtualClone
MemberwiseClone
전달된 형식으로. - 된 각 은 파된각유구합야니다해현은형생을 .
VirtualClone
기본 복제 방법을 호출한 다음 상위 VirtualClone 방법이 아직 처리하지 않은 파생 유형의 측면을 올바르게 복제하기 위해 수행해야 할 작업을 수행합니다.
가 " " " "이어야 .sealed
그러나 복제가 없다는 점을 제외하고는 동일하지 않은 기본 클래스에서 파생됩니다.으로 복제를 전달하는 것이 유형의 변수를 합니다.ICloneable<theNonCloneableType>
이를 통해 복제 가능한 파생 모델을 기대하는 루틴이 가능합니다.Foo
복제 가능한 파생물로 작업하다DerivedFoo
복제 .Foo
.
이 질문에 대한 거의 모든 답변이 만족스럽지 못하거나 제 상황에서 제대로 작동하지 않기 때문에 전적으로 반성하여 구현하고 여기서 필요한 모든 것을 해결한 AnyClone을 작성했습니다.복잡한 구조의 복잡한 시나리오에서 직렬화 작업을 수행할 수 없었고,IClonable
그것은 이상적이지 않습니다 - 사실 그것은 필요하지 않습니다.
은 표준 특은다사지다원니됩을 사용하여 됩니다.[IgnoreDataMember]
,[NonSerialized]
복잡한 컬렉션, 설정자가 없는 속성, 읽기 전용 필드 등을 지원합니다.
저와 같은 문제에 부딪힌 다른 누군가에게 도움이 되길 바랍니다.
가장 짧은 방법이지만 의존성이 필요합니다.
using Newtonsoft.Json;
public static T Clone<T>(T source) =>
JsonConvert.DeserializeObject<T>(JsonConvert.SerializeObject(source));
언급URL : https://stackoverflow.com/questions/78536/deep-cloning-objects
'programing' 카테고리의 다른 글
Postgres에서 한 데이터베이스에서 다른 데이터베이스로 표 복사 (0) | 2023.05.15 |
---|---|
SQL Server에서 MongoDB로 이동해야 하는 이유와 반대하는 이유 (0) | 2023.05.15 |
Postgresql에 제목이 있는 CSV로 테이블을 내보내는 방법은 무엇입니까? (0) | 2023.05.15 |
WPF 컨트롤의 여백 속성의 일부만 바인딩 (0) | 2023.05.15 |
다른 테이블의 필드에서 한 테이블의 SQL 업데이트 필드 (0) | 2023.05.15 |