[c#] 딥 클로닝 객체

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

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 (즉 수동)을 선택하는 이유

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

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


답변

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


답변

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