[c#] 딥 복제 객체


Answers

나는 대부분 기본 요소와리스트의 아주 단순한 객체에 대한 복제자를 원했습니다. 개체가 상자 JSON 직렬 가능이면이 메서드는 트릭을 수행합니다. JSON.NET과 같은 JSON 시리얼 라이저 만 복제 된 클래스에 인터페이스를 수정하거나 구현할 필요가 없습니다.

public static T Clone<T>(T source)
{
    var serialized = JsonConvert.SerializeObject(source);
    return JsonConvert.DeserializeObject<T>(serialized);
}
Question

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

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

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

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

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




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 딥 클로징 솔루션을 참조하십시오.




Here is a deep copy implementation:

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



여기에 링크 된 많은 옵션과이 문제에 대한 가능한 해결책에 대해 많은 것을 읽은 후에 나는 모든 옵션이 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

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




As I couldn't find a cloner that meets all my requirements in different projects, I created a deep cloner that can be configured and adapted to different code structures instead of adapting my code to meet the cloners requirements. Its achieved by adding annotations to the code that shall be cloned or you just leave the code as it is to have the default behaviour. It uses reflection, type caches and is based on fasterflect . The cloning process is very fast for a huge amount of data and a high object hierarchy (compared to other reflection/serialization based algorithms).

https://github.com/kalisohn/CloneBehave

Also available as a nuget package: https://www.nuget.org/packages/Clone.Behave/1.0.0

For example: The following code will deepClone Address, but only perform a shallow copy of the _currentJob field.

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



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



.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를 만들려고했지만, 익명 메소드 블록 내에서 작동하지 않는 yield 때문에 가능하지 않습니다.

Better still, use generic List<T> cloner:

class Utility<T> where T : ICloneable
{
    static public IEnumerable<T> CloneList(List<T> tl)
    {
        foreach (T t in tl)
        {
            yield return (T)t.Clone();
        }
    }
}



I like Copyconstructors like that:

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

If you have more things to copy add them




Here a solution fast and easy that worked for me without relaying on 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;
    }
}

EDIT : requires

    using System.Linq;
    using System.Reflection;

That's How I used it

public MyClass Clone(MyClass theObjectIneededToClone)
{
    MyClass clonedObj = theObjectIneededToClone.DeepClone();
}



I've seen it implemented through reflection as well. Basically there was a method that would iterate through the members of an object and appropriately copy them to the new object. When it reached reference types or collections I think it did a recursive call on itself. Reflection is expensive, but it worked pretty well.




가장 좋은 방법은 다음 과 같은 확장 메소드 를 구현하는 것입니다.

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

그런 다음 솔루션의 어느 곳에서나 사용할 수 있습니다.

var copy = anyObject.DeepClone();

우리는 다음 세 가지 구현을 할 수 있습니다.

  1. 직렬화 (가장 짧은 코드)
  2. 반사에 의한 - 5 배 빠른
  3. 표현식 트리 사용 - 20 배 빨라짐

링크 된 모든 메소드는 잘 작동하며 심층적으로 테스트되었습니다.




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

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

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

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




다음과 같이하세요:

  • Define an ISelf<T> with a read-only Self property that returns T , and ICloneable<out T> , which derives from ISelf<T> and includes a method T Clone() .
  • Then define a CloneBase type which implements a protected virtual generic VirtualClone casting MemberwiseClone to the passed-in type.
  • Each derived type should implement VirtualClone by calling the base clone method and then doing whatever needs to be done to properly clone those aspects of the derived type which the parent VirtualClone method hasn't yet handled.

For maximum inheritance versatility, classes exposing public cloning functionality should be sealed , but derive from a base class which is otherwise identical except for the lack of cloning. Rather than passing variables of the explicit clonable type, take a parameter of type ICloneable<theNonCloneableType> . This will allow a routine that expects a cloneable derivative of Foo to work with a cloneable derivative of DerivedFoo , but also allow the creation of non-cloneable derivatives of Foo .




If your Object Tree is Serializeable you could also use something like this

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

be informed that this Solution is pretty easy but it's not as performant as other solutions may be.

And be sure that if the Class grows, there will still be only those fields cloned, which also get serialized.




To clone your class object you can use the Object.MemberwiseClone method,

just add this function to your class :

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

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

then to perform a deep independant copy, just call the DeepCopy method :

yourClass newLine = oldLine.DeepCopy();

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




Related



Tags

c# c#   .net .net   clone