c# 복사 - 딥 복제 객체





15 Answers

나는 대부분 기본 요소와리스트의 아주 단순한 객체에 대한 복제자를 원했습니다. 개체가 상자 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);
    }
}
c# .net clone

나는 다음과 같이하고 싶다.

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

그런 다음 원본 개체에 반영되지 않은 새 개체를 변경합니다.

필자는이 기능이 필요하지 않기 때문에 필요할 때마다 새로운 개체를 만든 다음 각 속성을 개별적으로 복사하는 방법을 사용했지만보다 나은 또는보다 우아한 처리 방법이 있다는 느낌을 항상 남깁니다. 그 상황.

원본 개체에 변경 사항을 반영하지 않고 복제 된 개체를 수정할 수 있도록 개체를 복제 또는 완전 복사 할 수 있습니까?




여기에 링크 된 많은 옵션과이 문제에 대한 가능한 해결책에 대해 많은 것을 읽은 후에 나는 모든 옵션이 Ian P 의 링크 (모든 다른 옵션은 이들의 변형입니다)에 잘 정리되어 있으며 최상의 솔루션은 다음과 같은 방법으로 제공됩니다. 질문에 agiledeveloper.com/articles/cloning072002.htm 는 덧글을 남깁니다.

여기서는이 두 가지 참조의 관련 부분을 복사합니다. 우리가 가질 수있는 그런 식으로 :

날카로운 개체를 복제 할 수있는 가장 좋은 방법!

무엇보다 먼저 모든 것이 우리의 선택입니다 :

Expression Trees에 의한 Fast Deep Copy 문서 에는 직렬화, 반사 및 표현식 트리에 의한 복제의 성능 비교 기능도 있습니다.

왜 내가 ICloneable을 선택 하는가?

agiledeveloper.com/articles/cloning072002.htm .

그의 모든 기사는 Person , BrainCity의 3 가지 객체를 사용하여 대부분의 경우 적용 할 수있는 예제 주위에 표시되어 있습니다. 우리는 사람을 복제하고 싶습니다. 사람은 뇌를 가지지 만 같은 도시를 가질 것입니다. 위의 다른 방법 중 하나를 사용하여 기사를 가져 오거나 읽을 수있는 모든 문제를 그림으로 나타낼 수 있습니다.

이것은 내 결론을 약간 수정 한 것입니다.

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

이제 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

우리가 객체의 수를 유지한다면 여기에 구현 된 복제는 객체 수의 정확한 수를 유지한다는 것을 관찰하십시오.




모든 public 속성을 복사하는 간단한 확장 메소드. 모든 객체에 대해 작동하며 클래스가 [Serializable] 이 아니 [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 ) } );
        }
    };
}



ValueInjecter 또는 Automapper 와 같은 타사 응용 프로그램을 이미 사용하고 있다면 다음과 같이 할 수 있습니다.

MyObject oldObj; // The existing object to clone

MyObject newObj = new MyObject();
newObj.InjectFrom(oldObj); // Using ValueInjecter syntax

이 메서드를 사용하면 개체에 ISerializable 또는 ICloneable을 구현할 필요가 없습니다. 이는 MVC / MVVM 패턴과 공통적이므로 이와 같은 간단한 도구가 만들어졌습니다.

CodePlex의 valueinjecter 딥 클로징 솔루션을 참조하십시오.




간단한 대답은 ICloneable 인터페이스를 상속 한 다음 .clone 함수를 구현하는 것입니다. Clone은 멤버 전용 복사본을 수행하고이를 필요로하는 멤버에서 전체 복사본을 수행 한 다음 결과 개체를 반환해야합니다. 이는 재귀 적 작업입니다 (복제하려는 클래스의 모든 멤버가 값 유형이거나 ICloneable을 구현하고 해당 멤버가 값 유형이거나 ICloneable을 구현하는 등).

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

대답은 "의존적"입니다. 다른 사람들이 언급했듯이 ICloneable은 제네릭에서 지원되지 않으며 원형 클래스 참조에 대한 특별한 고려 사항이 필요하며 실제로 .NET Framework에서 일부는 "실수" 로 간주됩니다. 직렬화 방법은 객체가 직렬화 될 수 있는지 여부에 따라 달라지며 제어 할 수 없습니다. "최고의"관행 인 공동체에서 여전히 많은 논쟁이 있습니다. 실제로, ICloneable과 같은 모든 상황에 적합한 모든 솔루션에 적합한 솔루션은 없습니다.

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




  1. 기본적으로 ICloneable 인터페이스를 구현 한 다음 객체 구조 복사를 구현해야합니다.
  2. 모든 회원의 완전한 사본 인 경우 모든 아동이 복제 가능하다는 것을 보장해야합니다 (선택한 솔루션과 관련이 없음).
  3. 때로는이 과정에서 몇 가지 제한 사항을 알아야합니다. 예를 들어 ORM 개체를 복사하면 대부분의 프레임 워크에서 세션에 연결된 하나의 개체 만 허용되며이 개체의 복제본을 만들지 않아도됩니다. 이러한 개체의 세션 연결에 대해 설명합니다.

건배.




일을 간단하게 유지하고 AutoMapper 를 다른 언급과 같이 사용 AutoMapper 하나의 객체를 다른 객체에 매핑하는 간단한 라이브러리입니다 ... 동일한 유형의 다른 객체에 객체를 복사하려면 3 줄의 코드 만 있으면됩니다.

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 메서드가 있습니다.

전체 복사본의 경우 자동으로 수행하는 방법을 알 수있는 방법이 없습니다.




전체 복사본 구현 방법은 다음과 같습니다.

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



서로 다른 프로젝트에서 모든 요구 사항을 충족시키는 클론 어가 없었기 때문에 클론 요구 사항을 충족시키기 위해 코드를 수정하는 대신 다른 코드 구조에 맞게 구성하고 적용 할 수있는 깊은 클론 어를 만들었습니다. 코드에 주석을 추가하여 복제하거나 코드를 그대로두면 기본 동작을 유지할 수 있습니다. 반사, 유형 캐시를 사용하며 fasterflect 기반으로 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



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

복사 할 항목이 더 많은 경우 추가하십시오.




Serialization / Deserialization에 중계하지 않고도 빠르고 쉽게 사용할 수있는 솔루션입니다.

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



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

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



클래스 개체를 복제하려면 Object.MemberwiseClone 메서드를 사용할 수 있습니다.

이 함수를 클래스에 추가하면됩니다.

public class yourClass
{
    // ...
    // ...

    public yourClass DeepCopy()
    {
        yourClass othercopy = (yourClass)this.MemberwiseClone();
        return othercopy;
    }
}

깊은 독립 복사본을 수행하려면 DeepCopy 메서드를 호출하면됩니다.

yourClass newLine = oldLine.DeepCopy();

이것이 도움이되기를 바랍니다.




Object Tree가 Serializeable이라면 다음과 같이 사용할 수도 있습니다

static public MyClass Clone(MyClass myClass)
{
    MyClass clone;
    XmlSerializer ser = new XmlSerializer(typeof(MyClass), _xmlAttributeOverrides);
    using (var ms = new MemoryStream())
    {
        ser.Serialize(ms, myClass);
        ms.Position = 0;
        clone = (MyClass)ser.Deserialize(ms);
    }
    return clone;
}

이 솔루션은 매우 쉽지만 다른 솔루션보다 성능이 좋지는 않습니다.

클래스가 커지면 복제 된 필드 만 계속 존재하며 일련 화 된 필드도 있어야합니다.




Related


Tags

c#   .net   clone