나는 다음과 같은 것을하고 싶다 :
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.
/// </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 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 (Object.ReferenceEquals(source, null))
{
return default(T);
}
IFormatter formatter = new BinaryFormatter();
Stream stream = new MemoryStream();
using (stream)
{
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을 사용하기 시작 말할 수 있어야 가볍고, 그리고 [직렬화]가 태그의 오버 헤드를 피할 수 있습니다. ( NB @atconway는 비공개 멤버가 JSON 메소드를 사용하여 복제되지 않았다는 의견에서 지적했습니다)
/// <summary>
/// Perform a deep Copy of the object, using Json as a serialisation 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 (Object.ReferenceEquals(source, null))
{
return default(T);
}
// 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.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);
}
}
답변
사용하지 않는 이유 ICloneable가 있다 되지 는 일반적인 인터페이스를 가지고 있지 않기 때문에. 그것을 사용하지 않는 이유는 모호하기 때문 입니다. 얕거나 깊은 사본을 받고 있는지 분명하지 않습니다. 그것은 구현 자에게 달려 있습니다.
예, MemberwiseClone
얕게 복사하지만 그 반대 MemberwiseClone
는 아닙니다 Clone
. DeepClone
존재하지 않는 것일 수도 있습니다. ICloneable 인터페이스를 통해 객체를 사용하면 기본 객체가 어떤 종류의 복제를 수행하는지 알 수 없습니다. (그리고 XML 주석은 객체의 Clone 메소드에 대한 주석이 아닌 인터페이스 주석을 얻으므로 명확하지 않습니다.)
내가 보통하는 일은 단순히 Copy
내가 원하는 것을 정확하게 하는 방법을 만드는 것입니다.
답변
여기에 링크 된 많은 옵션 과이 문제에 대한 가능한 솔루션에 대해 많이 읽은 후에는 모든 옵션이 Ian P 의 링크 (다른 모든 옵션은 그 변형)에 잘 요약되어 있으며 최상의 솔루션은 질문에 대한 Pedro77 의 링크 .
여기서는 2 개의 참고 문헌 중 관련 부분을 복사하겠습니다. 그렇게 할 수있는 방법은 다음과 같습니다.
C sharp에서 객체를 복제하는 가장 좋은 방법!
무엇보다도, 우리의 모든 옵션은 다음과 같습니다.
- 수동 ICloneable 입니다, 얕은 하지 및 입력 – 안전
- ICloneable을 사용하는 MemberwiseClone
- Activator.CreateInstance 및 재귀 MemberwiseClone 을 사용하여 반영
- johnc의 선호 답변이 지적한 직렬화
- 어떻게 작동하는지 잘 모르는 중급 언어
- Havard Straden 의이 사용자 정의 클론 프레임 워크 와 같은 확장 방법
- 표현식 트리
표현 나무로 기사 빠른 깊은 복사 도 직렬화, 반사 및 표현의 나무에 의해 복제의 성능 비교가 있습니다.
ICloneable (즉 수동)을 선택하는 이유
Mr. Venkat Subramaniam (여기의 링크는 여기에 있음)가 그 이유에 대해 자세히 설명합니다 .
그의 모든 기사는 Person , Brain 및 City의 3 가지 객체를 사용하여 대부분의 경우에 적용하려고하는 예제를 중심으로합니다 . 우리는 자신의 두뇌를 가지지 만 같은 도시를 가진 사람을 복제하려고합니다. 위의 다른 방법으로 기사를 가져 오거나 읽을 수있는 모든 문제를 파악할 수 있습니다.
이것은 그의 결론을 약간 수정 한 것입니다.
New
클래스 이름 을 지정하여 객체를 복사하면 확장 할 수없는 코드가 생길 수 있습니다. 프로토 타입 패턴을 적용한 clone을 사용하는 것이 더 좋은 방법입니다. 그러나 C # (및 Java)에서 제공되는 클론을 사용하는 것도 상당히 문제가 될 수 있습니다. 보호 된 (비 공용) 복사 생성자를 제공하고 clone 메소드에서 호출하는 것이 좋습니다. 이를 통해 객체 생성 작업을 클래스 자체의 인스턴스에 위임하여 확장 성을 제공하고 보호 된 복사 생성자를 사용하여 안전하게 객체를 생성 할 수 있습니다.
이 구현이 다음 사항을 명확하게 할 수 있기를 바랍니다.
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
개체 수를 유지하면 여기에 구현 된 복제본은 개체 수를 올바르게 유지합니다.
답변
복제본보다 복사 생성자를 선호합니다. 의도가 더 명확합니다.
답변
모든 공용 속성을 복사하는 간단한 확장 방법입니다. 모든 객체에 적용되며 클래스가 필요 하지 않습니다[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 ) } );
}
};
}
답변
방금 CloneExtensions
라이브러리 프로젝트를 만들었습니다 . Expression Tree 런타임 코드 컴파일로 생성 된 간단한 할당 작업을 사용하여 빠르고 깊은 복제를 수행합니다.
사용 방법?
필드와 속성 사이에 할당 톤으로 자신 Clone
이나 Copy
메서드 를 작성하는 대신 Expression Tree를 사용하여 프로그램에서 직접 수행 할 수 있습니다. GetClone<T>()
확장 메서드로 표시된 메서드를 사용하면 인스턴스에서 간단히 메서드를 호출 할 수 있습니다.
var newInstance = source.GetClone();
enum source
을 newInstance
사용하여 복사 할 대상을 선택할 수 있습니다 CloningFlags
.
var newInstance
= source.GetClone(CloningFlags.Properties | CloningFlags.CollectionItems);
무엇을 복제 할 수 있습니까?
- 기본 (int, uint, byte, double, char 등), 알려진 불변 유형 (DateTime, TimeSpan, String) 및 대리자 (Action, Func 등 포함)
- 널 입력 가능
- T [] 배열
- 일반 클래스 및 구조체를 포함한 사용자 정의 클래스 및 구조체
다음 클래스 / 구조 멤버는 내부적으로 복제됩니다.
- 읽기 전용 필드가 아닌 공용 값
- get 및 set 접근자를 모두 사용하는 공용 속성 값
- ICollection을 구현하는 형식의 컬렉션 항목
얼마나 빠릅니까?
GetClone<T>
주어진 유형에 대해 처음으로 회원 정보를 사용 하기 전에 회원 정보를 한 번만 수집해야하기 때문에 솔루션이 반영보다 빠릅니다 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;
}
자신 만의 Clone
방법을 쓰는 방법과는 다른 List<int>
가요?