딥 클로닝 객체


2226

나는 다음과 같은 것을하고 싶다 :

MyObject myObj = GetMyObj(); // Create and fill a new object
MyObject newObj = myObj.Clone();

그런 다음 원래 객체에 반영되지 않은 새 객체를 변경하십시오.

나는 종종이 기능이 필요하지 않으므로 필요할 때 새 객체를 만든 다음 각 속성을 개별적으로 복사하는 것에 의지했지만 항상 더 나은 또는 우아한 처리 방법이 있다는 느낌을 갖게됩니다. 그 상황.

원본 객체에 변경 사항을 반영하지 않고 복제 된 객체를 수정할 수 있도록 객체를 복제하거나 딥 카피하려면 어떻게해야합니까?


81
유용 할 수 있습니다 : "왜 객체를 복사하는 것이 끔찍한 일입니까?" agiledeveloper.com/articles/cloning072002.htm
Pedro77

stackoverflow.com/questions/8025890 / ... 또 다른 솔루션 ...
Felix K.

18
AutoMapper를 살펴 봐야합니다
Daniel Little

3
귀하의 솔루션은 훨씬 더 복잡합니다. 읽지 못했습니다 ... hehehe. DeepClone 인터페이스를 사용하고 있습니다. 공용 인터페이스 IDeepCloneable <T> {T DeepClone (); }
Pedro77

1
@ Pedro77-흥미롭게도,이 기사는 clone클래스에서 메소드 를 작성하고 전달 된 내부 개인 생성자를 호출하도록 this합니다. 따라서 복사는 끔찍한 것이지만,주의 깊게 복사하는 것은 기사의 가치가 있습니다. ; ^)
ruffin

답변:


1715

표준 관행은 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);
}

24
stackoverflow.com/questions/78536/cloning-objects-in-c/...는 [내 상황에 더 적절한 중 하나는 두 참고 문헌의 다른 이러한 구현 이상 코드에 대한 링크를 갖는다
루벤 Bartelink

102
직렬화 / 역 직렬화에는 필요하지 않은 상당한 오버 헤드가 포함됩니다. C #의 ICloneable 인터페이스 및 .MemberWise () 복제 방법을 참조하십시오.
3Dave

18
@David는 당연하지만 객체가 가벼워서 사용할 때 성능 저하가 요구 사항에 비해 너무 높지 않은 경우 유용한 팁입니다. 루프에서 많은 양의 데이터를 집중적으로 사용하지는 않았지만 단일 성능 문제는 본 적이 없습니다.
johnc

16
@Amir : 실제로, 아니오 : typeof(T).IsSerializable유형이 [Serializable]속성 으로 표시된 경우에도 true 입니다. ISerializable인터페이스 를 구현할 필요는 없습니다 .
Daniel Gehriger 2016 년

11
이 방법이 유용하고 많은 시간 동안 직접 사용했지만 중간 신뢰와 전혀 호환되지 않는다는 점을 언급 했으므로 호환성이 필요한 코드를 작성하고 있는지 확인하십시오. BinaryFormatter는 개인 필드에 액세스하므로 부분 신뢰 환경의 기본 권한 세트에서 작동 할 수 없습니다. 다른 직렬 변환기를 사용해 볼 수 있지만 들어오는 객체가 개인 필드에 의존하는 경우 호출자가 클론이 완벽하지 않을 수 있음을 알리십시오.
Alex Norcliffe

298

나는 주로 프리미티브와리스트의 매우 간단한 객체에 대한 복제기를 원했습니다. 객체가 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);
    }
}

13
솔루션은 BinaryFormatter 솔루션보다 훨씬 빠릅니다. .NET 직렬화 성능 비교
esskar

3
고마워 C # 용 MongoDB 드라이버와 함께 제공되는 BSON 시리얼 라이저와 본질적으로 동일한 작업을 수행 할 수있었습니다.
Mark Ewer

3
이것은 나에게 가장 좋은 방법 Newtonsoft.Json.JsonConvert이지만 , 사용 하지만 동일합니다
Pierre

1
이것이 작동하려면 복제 할 객체를 이미 언급 한 것처럼 직렬화 할 수 있어야합니다. 예를 들어 순환 종속성이 없을 수도 있습니다.
radomeit

2
구현이 대부분의 프로그래밍 언어에 적용될 수 있기 때문에 이것이 최선의 해결책이라고 생각합니다.
mr5

178

사용하지 않는 이유 ICloneable가 있다 되지 는 일반적인 인터페이스를 가지고 있지 않기 때문에. 그것을 사용하지 않는 이유는 모호하기 때문 입니다. 얕거나 깊은 사본을 받고 있는지 분명하지 않습니다. 그것은 구현 자에게 달려 있습니다.

예, MemberwiseClone얕게 복사하지만 그 반대 MemberwiseClone는 아닙니다 Clone. DeepClone존재하지 않는 것일 수도 있습니다. ICloneable 인터페이스를 통해 객체를 사용하면 기본 객체가 어떤 종류의 복제를 수행하는지 알 수 없습니다. (그리고 XML 주석은 객체의 Clone 메소드에 대한 주석이 아닌 인터페이스 주석을 얻으므로 명확하지 않습니다.)

내가 보통하는 일은 단순히 Copy내가 원하는 것을 정확하게 하는 방법을 만드는 것입니다.


왜 ICloneable이 애매 모호한 지 잘 모르겠습니다. Dictionary (Of T, U)와 같은 유형을 감안할 때 ICloneable.Clone은 새로운 사전을 동일한 T와 U를 포함하는 독립적 인 사전으로 만들기 위해 깊고 얕은 복사 수준이 필요한 모든 수준을 수행해야합니다 (구조 내용, 및 / 또는 객체 참조)를 원본으로합니다. 모호성은 어디에 있습니까? 확실히, "Self"메소드를 포함하는 ISelf (Of T)를 상속 한 일반 ICloneable (Of T)이 훨씬 나을 것이지만, 심층 복제와 얕은 복제에 대해서는 모호함이 보이지 않습니다.
supercat

31
귀하의 예는 문제를 보여줍니다. Dictionary <string, Customer>가 있다고 가정하십시오. 복제 된 사전 에 원본 또는 해당 고객 객체의 사본동일한 고객 객체 가 있어야합니까 ? 어느 쪽이든 합리적인 사용 사례가 있습니다. 그러나 ICloneable은 어떤 것을 얻을 수 있는지 명확하지 않습니다. 그것이 유용하지 않은 이유입니다.
Ryan Lundy

@Kyralessa Microsoft MSDN 기사는 실제로 깊거나 얕은 복사본을 요청하는지 알지 못하는 문제를 말합니다.
호감

중복에서 대답 stackoverflow.com/questions/129389/...는 재귀 MembershipClone에 따라 복사 확장에 대해 설명
마이클 Freidgeim

123

여기에 링크 된 많은 옵션 과이 문제에 대한 가능한 솔루션에 대해 많이 읽은 후에는 모든 옵션이 Ian P 의 링크 (다른 모든 옵션은 그 변형)에 잘 요약되어 있으며 최상의 솔루션은 질문에 대한 Pedro77 의 링크 .

여기서는 2 개의 참고 문헌 중 관련 부분을 복사하겠습니다. 그렇게 할 수있는 방법은 다음과 같습니다.

C sharp에서 객체를 복제하는 가장 좋은 방법!

무엇보다도, 우리의 모든 옵션은 다음과 같습니다.

표현 나무로 기사 빠른 깊은 복사 도 직렬화, 반사 및 표현의 나무에 의해 복제의 성능 비교가 있습니다.

ICloneable (즉 수동)을 선택하는 이유

Mr. Venkat Subramaniam (여기의 링크는 여기에 있음)가 그 이유에 대해 자세히 설명합니다 .

그의 모든 기사는 Person , BrainCity의 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

개체 수를 유지하면 여기에 구현 된 복제본은 개체 수를 올바르게 유지합니다.


6
MS ICloneable는 공개 멤버를 사용하지 않는 것이 좋습니다 . "Clone의 호출자는 예측 가능한 복제 작업을 수행하는 방법에 의존 할 수 없으므로 ICloneable은 공용 API로 구현하지 않는 것이 좋습니다." msdn.microsoft.com/ko-kr/library/… 그러나 링크 된 기사에서 Venkat Subramaniam이 제공 한 설명 에 따르면 ICloneable 개체의 제작자가 깊이 있는 한이 상황에서 사용하는 것이 좋습니다. 어느 이해하는 특성이 깊은 대 얕은 복사해야합니다 (즉 얕은 깊이가 도시를 복사, 뇌를 복사)
BateTech

먼저이 주제의 전문가 (공용 API)와는 거리가 멀습니다. 나는 MS의 발언이 많은 의미가 있다고 생각 합니다. 그리고 그 API 사용자 가 그렇게 깊이 이해 한다고 가정하는 것이 안전하지 않다고 생각합니다 . 따라서 공개 API 를 사용하려는 사람에게 중요하지 않은 경우에만 공개 API 에서 구현 하는 것이 좋습니다. 나는 추측 UML이 매우 명시 적으로 각 속성의 구분이 도움이 될 만드는 몇 가지 종류를 가지고. 그러나 더 많은 경험을 가진 사람의 의견을 듣고 싶습니다. : P
cregox

CGbR Clone Generator를 사용하여 수동으로 코드를 작성하지 않고도 비슷한 결과를 얻을 수 있습니다.
Toxantron 2016 년

중급 언어 구현이 유용합니다
Michael Freidgeim

C #에는 최종 결과가 없습니다
Konrad

84

복제본보다 복사 생성자를 선호합니다. 의도가 더 명확합니다.


5
.Net에는 복사 생성자가 없습니다.
팝 카탈린

48
new MyObject (objToCloneFrom) 객체를 매개 변수로 복제하는 ctor를 선언하기 만하면됩니다.
Nick

30
같은 것이 아닙니다. 모든 수업에 수동으로 추가해야하며 딥 카피를 보증하는지조차 알 수 없습니다.
Dave Van den Eynde

15
사본 ctor의 경우 +1 각 유형의 객체에 대해 clone () 함수를 수동으로 작성해야하며 클래스 계층 구조가 몇 단계 깊어지면 행운을 빕니다.
앤드류 그랜트

3
복사 생성자를 사용하면 계층 구조가 손실됩니다. agiledeveloper.com/articles/cloning072002.htm

41

모든 공용 속성을 복사하는 간단한 확장 방법입니다. 모든 객체에 적용되며 클래스가 필요 하지 않습니다[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 ) } );
        }
    };
}

15
불행히도 이것은 결함이 있습니다. objectOne.MyProperty = objectTwo.MyProperty를 호출하는 것과 같습니다 (즉, 참조를 복사합니다). 속성 값을 복제하지 않습니다.
Alex Norcliffe

1
Alex Norcliffe에게 : 질문의 저자는 복제보다는 "각 자산을 복사하는"것에 대해 물었습니다. 대부분의 경우 정확한 속성 복제가 필요하지 않습니다.
Konstantin Salavatov

1
나는이 방법을 사용하지만 재귀를 생각합니다. 따라서 속성 값이 참조 인 경우 새 개체를 만들고 CopyTo를 다시 호출하십시오. 방금 사용 된 모든 클래스에 매개 변수가없는 생성자가 있어야한다는 한 가지 문제가 있습니다. 아무도 이미 이것을 시도? 또한 이것이 실제로 DataRow 및 DataTable과 같은 .net 클래스를 포함하는 속성에서 작동하는지 궁금합니다.
Koryu

33

방금 CloneExtensions라이브러리 프로젝트를 만들었습니다 . Expression Tree 런타임 코드 컴파일로 생성 된 간단한 할당 작업을 사용하여 빠르고 깊은 복제를 수행합니다.

사용 방법?

필드와 속성 사이에 할당 톤으로 자신 Clone이나 Copy메서드 를 작성하는 대신 Expression Tree를 사용하여 프로그램에서 직접 수행 할 수 있습니다. GetClone<T>()확장 메서드로 표시된 메서드를 사용하면 인스턴스에서 간단히 메서드를 호출 할 수 있습니다.

var newInstance = source.GetClone();

enum sourcenewInstance사용하여 복사 할 대상을 선택할 수 있습니다 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>가요?


2
이것이 NuGet에 걸릴 가능성은 무엇입니까? 최상의 솔루션 인 것 같습니다. NClone 과 어떻게 비교 됩니까 ?
호감

이 답변은 더 많이지지되어야한다고 생각합니다. ICloneable을 수동으로 구현하는 것은 번거롭고 오류가 발생하기 쉽습니다. 성능이 중요하고 짧은 시간 동안 수천 개의 개체를 복사해야하는 경우 리플렉션 또는 직렬화를 사용하면 속도가 느려집니다.
나이트 코더

전혀, 당신은 리플렉션에 대해 잘못, 당신은 단순히 이것을 올바르게 캐시해야합니다. 아래의 답변을 확인하십시오 stackoverflow.com/a/34368738/4711853
Roma Borodov

31

글쎄, Silverlight에서 ICloneable을 사용하는 데 문제가 있었지만 seralization이라는 아이디어가 마음에 들었습니다. XML을 seralize 할 수 있으므로 다음과 같이했습니다.

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);
    }
}

31

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 딥 클로닝 샘플을 참조하십시오 .


25

가장 좋은 방법은 다음 과 같은 확장 방법 을 구현하는 것입니다

public static T DeepClone<T>(this T originalObject)
{ /* the cloning code */ }

그런 다음 솔루션의 어느 곳에서나 사용하십시오.

var copy = anyObject.DeepClone();

다음과 같은 세 가지 구현을 가질 수 있습니다.

  1. 직렬화 (가장 짧은 코드)
  2. 반사로 -5 배 더 빠름
  3. Expression Trees - 20 배 더 빠름

연결된 모든 방법이 제대로 작동하고 철저한 테스트를 거쳤습니다.


codeproject.com/Articles/1111658/… 을 게시 한 Expression tree를 사용하여 코드를 복제 하는 중 보안 예외가있는 최신 버전의 .Net framework에서 실패하고 있습니다. 작업이 런타임을 불안정하게 만들 수 있습니다 . 이는 당신이 간단한 쉽게 복사됩니다 난 단지 깊은 계층 구조와 복잡한 객체로 문제를 본 일부 solution.In 사실이 있는지 확인하십시오, 런타임에 Func을을 생성하는 데 사용됩니다
Mrinal Kamboj

1
ExpressionTree 구현이 매우 좋아 보입니다. 순환 참조 및 개인 멤버와도 작동합니다. 속성이 필요하지 않습니다. 내가 찾은 최고의 답변.
N73k

가장 좋은 답변, 잘 작동, 당신은 내 하루를 저장
Adel Mourad

23

짧은 대답은 ICloneable 인터페이스에서 상속 한 다음 .clone 함수를 구현하는 것입니다. 복제는 구성원 별 복사를 수행하고 필요한 구성원에 대해 깊은 복사를 수행 한 다음 결과 개체를 반환해야합니다. 이는 재귀 작업입니다 (복제하려는 클래스의 모든 구성원이 값 유형이거나 ICloneable을 구현하고 구성원이 값 유형 또는 ICloneable을 구현해야합니다).

ICloneable을 사용한 복제에 대한 자세한 설명은 이 기사를 확인 하십시오 .

대답은 "이 달려있다"입니다. 다른 사람들이 언급했듯이 ICloneable은 제네릭에서 지원하지 않으며 순환 클래스 참조에 대한 특별한 고려가 필요하며 실제로 일부 는 .NET Framework에서 "실수" 로 간주합니다 . 직렬화 방법은 직렬화 할 수있는 개체에 따라 다르며 제어 할 수없는 개체 일 수 있습니다. 커뮤니티에서 여전히 "최상의"관행 인 많은 논쟁이 있습니다. 실제로, 어떤 솔루션도 ICloneable이 원래 해석 된 것처럼 모든 상황에 가장 적합한 방법은 아닙니다.

몇 가지 추가 옵션 (크레딧을 Ian으로)에 대해서는이 개발자 코너 기사 를 참조하십시오 .


1
ICloneable에는 일반 인터페이스가 없으므로 해당 인터페이스를 사용하지 않는 것이 좋습니다.
Karg

솔루션은 순환 참조를 처리해야 할 때까지 작동하고 상황이 복잡해지기 때문에 딥 직렬화를 사용하여 딥 클로닝을 구현하는 것이 좋습니다.
팝 카탈린

불행히도, 모든 객체가 직렬화 가능하지는 않으므로 항상 해당 방법을 사용할 수는 없습니다. 이안의 링크는 지금까지 가장 포괄적 인 답변입니다.
Zach Burlingame

19
  1. 기본적으로 ICloneable 인터페이스를 구현 한 다음 객체 구조 복사를 실현해야합니다.
  2. 모든 회원의 딥 카피 인 경우 모든 아동이 복제 할 수 있는지 확인해야합니다 (선택한 솔루션과 관련이 없음).
  3. 예를 들어 ORM 객체를 복사 할 때 대부분의 프레임 워크가 세션에 연결된 객체를 하나만 허용하고이 객체를 복제해서는 안되거나 신경 써야하는 경우와 같이이 프로세스 중에 일부 제한 사항을 알고 있어야하는 경우가 있습니다. 이러한 객체의 세션 연결에 대해

건배.


4
ICloneable에는 일반 인터페이스가 없으므로 해당 인터페이스를 사용하지 않는 것이 좋습니다.
Karg

간단하고 간결한 답변이 가장 좋습니다.
DavidGuaita

17

편집 : 프로젝트가 중단되었습니다

알 수없는 유형에 대한 진정한 복제를 원한다면 fastclone을 살펴볼 수 있습니다 .

이는 이진 직렬화보다 약 10 배 빠르게 작동하고 완전한 객체 그래프 무결성을 유지하는 표현식 기반 복제입니다.

즉, 계층 구조에서 동일한 개체를 여러 번 참조하면 복제본에도 단일 인스턴스 꿀벌이 참조됩니다.

복제되는 객체에 대한 인터페이스, 속성 또는 기타 수정이 필요하지 않습니다.


이것은 매우 유용한 것 같습니다
LuckyLikey

전체 시스템, 특히 닫힌 시스템보다 하나의 코드 스냅 샷에서 작업을 시작하는 것이 더 쉽습니다. 한 번의 촬영으로 모든 문제를 해결할 수있는 라이브러리는 없습니다. 약간의 휴식을 취해야합니다.
TarmoPikaro

1
귀하의 솔루션을 시도했지만 잘 작동하는 것 같습니다. 감사합니다! 이 답변은 더 많이지지되어야한다고 생각합니다. ICloneable을 수동으로 구현하는 것은 번거롭고 오류가 발생하기 쉽습니다. 성능이 중요하고 짧은 시간 동안 수천 개의 개체를 복사해야하는 경우 리플렉션 또는 직렬화를 사용하면 속도가 느려집니다.
나이트 코더

나는 그것을 시도했지만 그것은 전혀 효과가 없었다. MemberAccess 예외를 발생시킵니다.
Michael Brown

그것은 .NET의 최신 버전과 함께 작동하지 않고 중단
마이클 샌더

14

일을 단순하게 유지 하고 다른 사람들이 언급 한 것처럼 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();

이 점에주의하십시오. 실제로 성능이 떨어집니다. 나는 이것만큼 짧은 johnc 답변으로 전환하고 훨씬 더 잘 수행했습니다.
Agorilla

1
얕은 사본 만 수행합니다.
N73k

11

.NET 을 극복하기 위해 이것을 생각해 냈습니다 .List <T>를 수동으로 딥 카피 해야하는 단점 문제를 해결했습니다.

나는 이것을 사용한다 :

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();
        }
    }
}

10

Q. 왜이 답변을 선택해야합니까?

  • 가장 빠른 속도의 .NET이 가능한 경우이 답변을 선택하십시오.
  • 정말로, 정말로 쉬운 복제 방법을 원한다면이 답변을 무시하십시오.

다시 말해, 수정이 필요한 성능 병목 현상이 없으면 다른 답변을 사용하여 프로파일 러로이를 입증 할 수 있습니다 .

다른 방법보다 10 배 빠름

딥 클론을 수행하는 다음 방법은 다음과 같습니다.

  • 직렬화 / 역 직렬화와 관련된 것보다 10 배 빠릅니다.
  • 이론상 최대 속도 인 .NET과 거의 비슷합니다.

그리고 방법은 ...

최고의 속도를 위해 Nested MemberwiseClone을 사용 하여 딥 카피를 수행 할 수 있습니다 . 값 구조체를 복사하는 것과 거의 같은 속도이며 (a) 리플렉션 또는 (b) 직렬화 (이 페이지의 다른 답변에서 설명)보다 훨씬 빠릅니다.

깊은 복사에 Nested MemberwiseClone을 사용 하는 경우 클래스의 각 중첩 수준에 대해 ShallowCopy를 수동으로 구현해야하며 DeepCopy는 모든 ShallowCopy 메소드를 호출하여 완전한 복제본을 작성해야합니다. 이것은 간단합니다. 총 몇 줄 밖에되지 않습니다. 아래 데모 코드를 참조하십시오.

100,000 개의 클론에 대한 상대적인 성능 차이를 보여주는 코드 출력은 다음과 같습니다.

  • 중첩 된 구조체에서 Nested MemberwiseClone의 경우 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;
    }
}

그런 다음 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를 수동으로 구현해야하며 DeepCopy는 모든 ShallowCopy 메소드를 호출하여 완전한 복제본을 작성해야합니다. 이것은 간단합니다. 총 몇 줄 밖에되지 않습니다. 위의 데모 코드를 참조하십시오.

값 유형과 참조 유형

객체 복제 와 관련하여 " struct "와 " class " 사이에는 큰 차이가 있습니다 .

  • " struct " 가있는 경우 값 유형 이므로 복사 만하면 내용이 복제됩니다 (그러나이 게시물의 기술을 사용하지 않는 한 얕은 복제본 만 생성 함).
  • " 클래스 " 가있는 경우 참조 유형 이므로 복사하면 포인터를 복사하기 만하면됩니다. 실제 복제본을 만들려면보다 창의적이어야 하고 메모리에 원래 객체의 다른 사본을 생성하는 값 유형과 참조 유형의 차이를 사용해야 합니다.

값 유형과 참조 유형의 차이점을 참조하십시오 .

디버깅을 돕는 체크섬

  • 객체를 잘못 복제하면 매우 찾기 어려운 버그가 발생할 수 있습니다. 프로덕션 코드에서는 객체가 올바르게 복제되었으며 다른 참조로 손상되지 않았는지 확인하는 체크섬을 구현하는 경향이 있습니다. 이 체크섬은 해제 모드에서 끌 수 있습니다.
  • 이 방법이 매우 유용하다는 것을 알았습니다. 종종 전체가 아닌 객체의 일부만 복제하려고합니다.

많은 다른 스레드에서 많은 스레드를 분리하는 데 매우 유용합니다.

이 코드의 훌륭한 사용 사례 중 하나는 중첩 클래스 또는 구조체의 복제본을 큐에 공급하여 생산자 / 소비자 패턴을 구현하는 것입니다.

  • 하나 이상의 스레드가 소유 한 클래스를 수정 한 다음이 클래스의 전체 사본을 ConcurrentQueue 있습니다.
  • 그런 다음 하나 이상의 스레드가 이러한 클래스의 복사본을 꺼내어 처리합니다.

이것은 실제로 매우 잘 작동하며 하나 이상의 스레드 (소비자)에서 많은 스레드 (생산자)를 분리 할 수 ​​있습니다.

중첩 된 구조체를 사용하면 중첩 된 클래스를 직렬화 / 역 직렬화하는 것보다 35 배 빠르며 머신에서 사용 가능한 모든 스레드를 활용할 수 있습니다.

최신 정보

분명히 ExpressMapper는 위와 같은 수동 코딩보다 빠르지는 않지만 빠릅니다. 그들이 프로파일 러와 어떻게 비교되는지보아야 할 수도 있습니다.


구조체를 복사하면 얕게 복사되지만 딥 카피에는 특정 구현이 필요할 수 있습니다.
Lasse V. Karlsen

@ 래스 V. 칼슨. 예, 당신은 절대적으로 맞습니다.이 사실을 명확하게하기 위해 답변을 업데이트했습니다. 이 메소드는 구조체 클래스 의 깊은 복사본을 만드는 데 사용할 수 있습니다 . 포함 된 예제 데모 코드를 실행하여 수행 방법을 보여줄 수 있으며 중첩 된 구조체를 딥 복제하는 예제와 중첩 된 클래스를 딥 복제하는 다른 예제가 있습니다.
Contango

9

일반적으로 ICloneable 인터페이스를 구현하고 직접 복제를 구현합니다. C # 개체에는 기본 형식의 멤버 복사를 수행하는 얕은 복사를 수행하는 MemberwiseClone 메서드가 내장되어 있습니다.

딥 카피의 경우 자동으로 수행하는 방법을 알 수있는 방법이 없습니다.


ICloneable에는 일반 인터페이스가 없으므로 해당 인터페이스를 사용하지 않는 것이 좋습니다.
Karg

8

리플렉션을 통해 구현 된 것을 보았습니다. 기본적으로 객체의 멤버를 반복하고 새 객체에 적절하게 복사하는 방법이 있습니다. 그것이 참조 유형이나 컬렉션에 도달했을 때 나는 그 자체로 재귀 호출을했다고 생각합니다. 반사는 비싸지 만 꽤 잘 작동했습니다.


8

깊은 복사 구현은 다음과 같습니다.

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;
}

2
이것은 참조 유형 속성을 인식하지 못하기 때문에 멤버 단위 복제본처럼 보입니다
sll

1
맹목적으로 빠른 성능을 원한다면이 구현을하지 마십시오. 리플렉션을 사용하므로 그렇게 빠르지는 않습니다. 반대로, "조기 최적화는 악의적 인 것"이므로 프로파일 러를 실행할 때까지 성능 측면을 무시하십시오.
Contango

1
CreateInstanceOfType이 정의되지 않았습니까?
MonsterMMORPG

interger에서 실패합니다 : "정적이지 않은 메소드에는 대상이 필요합니다."
Mr.B

8

다른 프로젝트에서 모든 요구 사항을 충족시키는 클론을 찾을 수 없으므로 복제기 요구 사항에 맞게 코드를 조정하는 대신 다른 코드 구조에 맞게 구성하고 조정할 수있는 딥 클론을 만들었습니다. 복제 될 코드에 주석을 추가하거나 기본 동작을 유지하기 위해 코드를 그대로두면됩니다. 리플렉션, 유형 캐시를 사용하며 fasterflect를 기반으로 합니다 . 복제 프로세스는 많은 양의 데이터와 높은 객체 계층 구조 (다른 리플렉션 / 직렬화 기반 알고리즘에 비해)가 매우 빠릅니다.

https://github.com/kalisohn/CloneBehave

너겟 패키지로도 이용 가능 : https://www.nuget.org/packages/Clone.Behave/1.0.0

예를 들면 다음과 같습니다. 다음 코드는 deepClone 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

7

코드 생성기

우리는 수동 구현을 통한 직렬화에서 리플렉션에 이르기까지 많은 아이디어를 보았고 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 검사가 있지만 이해를 돕기 위해 생략했습니다.


6

나는 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));
        }
    }

복사 할 것이 더 있으면 추가하십시오


6

이 방법으로 문제가 해결되었습니다.

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);


6

여기 직렬화 / 역 직렬화를 중계하지 않고 나를 위해 일한 빠르고 쉬운 솔루션입니다.

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();
}

5

이 단계를 따르세요:

  • 정의 ISelf<T>읽기 전용으로 Self반환하는 재산 TICloneable<out T>에서하는 도출 ISelf<T>및 방법을 포함한다 T Clone().
  • 그런 다음 전달 된 유형 CloneBase으로 protected virtual generic VirtualClone캐스트 MemberwiseClone를 구현 하는 유형을 정의하십시오 .
  • 각 파생 유형은 VirtualClone기본 클론 메소드를 호출 한 다음 상위 VirtualClone 메소드가 아직 처리하지 않은 파생 유형의 측면을 올바르게 복제하기 위해 수행해야하는 모든 작업을 수행하여 구현해야 합니다.

상속의 다양성을 극대화하기 위해 공용 복제 기능 sealed을 제공하는 클래스 는 복제 기능 이없는 것을 제외하고는 기본 클래스에서 파생 되어야합니다 . 명시 적 복제 가능 유형의 변수를 전달하는 대신 유형의 매개 변수를 사용하십시오 ICloneable<theNonCloneableType>. 이를 통해 복제 가능한 파생물이의 복제 가능한 파생물과 Foo작동 할 것으로 기대되는 루틴을 DerivedFoo허용 할뿐만 아니라의 복제 불가능한 파생물을 생성 할 수 Foo있습니다.


5

나는 당신이 이것을 시도 할 수 있다고 생각합니다.

MyObject myObj = GetMyObj(); // Create and fill a new object
MyObject newObj = new MyObject(myObj); //DeepClone it

4

'[Serializable]'및 '[DataContract]'모두에서 작동하는 허용되는 답변 버전을 만들었습니다. 내가 쓴 이래 오랜 시간이 지났지 만, 정확하게 기억한다면 [DataContract]는 다른 시리얼 라이저가 필요했습니다.

필요 시스템, System.IO,들은 System.Runtime.Serialization, System.Runtime.Serialization.Formatters.Binary,에서 System.Xml을 ;

public static class ObjectCopier
{

    /// <summary>
    /// Perform a deep Copy of an object that is marked with '[Serializable]' or '[DataContract]'
    /// </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 == true)
        {
            return CloneUsingSerializable<T>(source);
        }

        if (IsDataContract(typeof(T)) == true)
        {
            return CloneUsingDataContracts<T>(source);
        }

        throw new ArgumentException("The type must be Serializable or use DataContracts.", "source");
    }


    /// <summary>
    /// Perform a deep Copy of an object that is marked with '[Serializable]'
    /// </summary>
    /// <remarks>
    /// Found on http://stackoverflow.com/questions/78536/cloning-objects-in-c-sharp
    /// Uses code found on CodeProject, which allows free use in third party apps
    /// - http://www.codeproject.com/KB/tips/SerializedObjectCloner.aspx
    /// </remarks>
    /// <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 CloneUsingSerializable<T>(T source)
    {
        if (!typeof(T).IsSerializable)
        {
            throw new ArgumentException("The type must be serializable.", "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);
        }
    }


    /// <summary>
    /// Perform a deep Copy of an object that is marked with '[DataContract]'
    /// </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 CloneUsingDataContracts<T>(T source)
    {
        if (IsDataContract(typeof(T)) == false)
        {
            throw new ArgumentException("The type must be a data contract.", "source");
        }

        // ** Don't serialize a null object, simply return the default for that object
        if (Object.ReferenceEquals(source, null))
        {
            return default(T);
        }

        DataContractSerializer dcs = new DataContractSerializer(typeof(T));
        using(Stream stream = new MemoryStream())
        {
            using (XmlDictionaryWriter writer = XmlDictionaryWriter.CreateBinaryWriter(stream))
            {
                dcs.WriteObject(writer, source);
                writer.Flush();
                stream.Seek(0, SeekOrigin.Begin);
                using (XmlDictionaryReader reader = XmlDictionaryReader.CreateBinaryReader(stream, XmlDictionaryReaderQuotas.Max))
                {
                    return (T)dcs.ReadObject(reader);
                }
            }
        }
    }


    /// <summary>
    /// Helper function to check if a class is a [DataContract]
    /// </summary>
    /// <param name="type">The type of the object to check.</param>
    /// <returns>Boolean flag indicating if the class is a DataContract (true) or not (false) </returns>
    public static bool IsDataContract(Type type)
    {
        object[] attributes = type.GetCustomAttributes(typeof(DataContractAttribute), false);
        return attributes.Length == 1;
    }

} 

4

좋아,이 게시물에는 리플렉션이있는 명백한 예가 있지만, 제대로 캐시하기 시작할 때까지는 일반적으로 리플렉션이 느립니다.

올바르게 캐시하면 1000000 개의 개체를 4,6 초 깊숙이 복제합니다 (Watcher로 측정).

static readonly Dictionary<Type, PropertyInfo[]> ProperyList = new Dictionary<Type, PropertyInfo[]>();

캐시 된 속성을 가져 오거나 사전에 새 속성을 추가하고 단순히 사용하는 것보다

foreach (var prop in propList)
{
        var value = prop.GetValue(source, null);   
        prop.SetValue(copyInstance, value, null);
}

다른 답변으로 내 게시물의 전체 코드 확인

https://stackoverflow.com/a/34365709/4711853


2
통화 prop.GetValue(...)는 여전히 반영 중이며 캐시 할 수 없습니다. 그러나 식 트리에서는 컴파일 속도가 훨씬 빠릅니다.
Tseng

4

이 질문에 대한 거의 모든 답변이 만족스럽지 않거나 내 상황에서 제대로 작동하지 않으므로 AnyClone 을 작성했습니다 . 복잡한 구조의 복잡한 시나리오에서 직렬화를 수행 할 수 없었 IClonable으며 이상적이지 않습니다. 실제로 필요하지 않아야합니다.

표준 무시 속성은 [IgnoreDataMember],을 사용하여 지원됩니다 [NonSerialized]. 복잡한 컬렉션, setter가없는 속성, 읽기 전용 필드 등을 지원합니다.

나는 그것이 내가했던 것과 같은 문제에 부딪친 다른 누군가를 돕기를 바랍니다.


4

면책 조항 : 나는 언급 한 패키지의 저자입니다.

2019 년이 질문에 대한 최고 답변이 여전히 직렬화 또는 반영을 사용하는 방법에 놀랐습니다.

직렬화가 제한적이며 (속성, 특정 생성자 등이 필요) 매우 느립니다.

BinaryFormatterSerializable속성이 필요 합니다.JsonConverter 매개 변수없는 생성자 또는 속성을 필요로하지도 핸들은 잘 필드 만 또는 인터페이스를 읽어 둘 필요 이상으로 느린 10-30x 있습니다.

표현식 트리

대신 Expression Trees 또는 Reflection을 사용할 수 있습니다. 을 사용하여 복제 코드를 한 번만 생성 한 다음 느린 반영 또는 직렬화 대신 컴파일 된 코드를 사용할 수 있습니다.

문제를 직접 겪고 만족스러운 해결책을 찾지 못했을 때, 나는 그 일을하고 모든 유형에서 작동하며 거의 사용자 정의 코드만큼 빠른 패키지를 만들기로 결정했습니다. .

GitHub에서 프로젝트를 찾을 수 있습니다 : https://github.com/marcelltoth/ObjectCloner

용법

NuGet에서 설치할 수 있습니다. ObjectCloner패키지를 가져 와서 다음 과 같이 사용하십시오.

var clone = ObjectCloner.DeepClone(original);

또는 확장으로 객체 유형을 오염시키는 것이 마음에 들지 않으면 ObjectCloner.Extensions다음과 같이 작성하십시오.

var clone = original.DeepClone();

공연

클래스 계층 복제의 간단한 벤치 마크는 리플렉션을 사용하는 것보다 ~ 3 배, Newtonsoft.Json 직렬화보다 ~ 12 배, 권장되는 것보다 ~ 36 배 빠른 성능을 보여주었습니다 BinaryFormatter.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.