programing

심층 복제 개체

yellowcard 2023. 5. 15. 21:26
반응형

심층 복제 개체

다음과 같은 작업을 수행하고 싶습니다.

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 샤프에서 물체를 복제하기 위해 해야 할 가장 좋은 일!

무엇보다도, 이것들이 우리의 모든 선택사항입니다.

표현 트리의 빠른 딥 복사 기사에는 직렬화, 반사 및 표현 트리별 클로닝 성능 비교도 나와 있습니다.

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

여러분은 대할상선수있다니에서 할 수 .sourcenewInstance용사를 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();

다음과 같은 세 가지 구현이 가능합니다.

  1. 직렬화 기준(최단 코드)
  2. 반사 성능 - 5배 향상
  3. 표현 트리 기준 - 20배 더 빠름

연결된 모든 방법은 잘 작동하며 깊이 테스트되었습니다.

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 패키지

모든 답변을 읽은 후 아무도 이 훌륭한 패키지에 대해 언급하지 않아 놀랐습니다.

DeepCloner GitHub 프로젝트

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에 대한 크레딧)은 개발자 코너 기사를 참조하십시오.

  1. 기본적으로 IC 복제 가능 인터페이스를 구현한 다음 객체 구조 복사를 실현해야 합니다.
  2. 모든 구성원의 전체 복사본인 경우 모든 하위 항목도 복제할 수 있는지 확인해야 합니다(선택한 솔루션과 관련이 없음).
  3. 이 프로세스 중에 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 VirtualCloneMemberwiseClone전달된 형식으로.
  • 된 각 은 파된각유구합야니다해현은형생을 .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

반응형