[c#] Tief klonen von Objekten


Answers

Ich wollte einen Cloner für sehr einfache Objekte von meist Primitiven und Listen. Wenn Ihr Objekt JSON serialisierbar ist, wird diese Methode den Zweck erfüllen. Dies erfordert keine Modifikation oder Implementierung von Schnittstellen in der geklonten Klasse, nur ein JSON-Serializer wie JSON.NET.

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

Ich möchte etwas tun wie:

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

Nehmen Sie dann Änderungen am neuen Objekt vor, die nicht im ursprünglichen Objekt enthalten sind.

Ich brauche diese Funktionalität nicht oft, also habe ich, wenn es nötig war, ein neues Objekt erstellt und dann jede Eigenschaft einzeln kopiert, aber es hat immer das Gefühl, dass es eine bessere oder elegantere Handhabung gibt die Situation.

Wie kann ich ein Objekt klonen oder tiefkopieren, damit das geklonte Objekt geändert werden kann, ohne dass Änderungen am ursprünglichen Objekt vorgenommen werden?




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




Nach vielen Lektüren über viele der hier verlinkten Optionen und mögliche Lösungen für dieses Problem, glaube ich, dass alle Optionen ziemlich gut auf Ian Ps Link zusammengefasst sind (alle anderen Optionen sind Variationen davon) und die beste Lösung wird durch agiledeveloper.com/articles/cloning072002.htm zu den agiledeveloper.com/articles/cloning072002.htm .

Ich werde nur relevante Teile dieser 2 Referenzen hier kopieren. So können wir haben:

Das beste zum Klonen von Objekten in cis!

Das sind vor allem unsere Möglichkeiten:

Der Artikel Fast Deep Copy von Expression Trees enthält ebenfalls einen Leistungsvergleich des Klonens durch Serialisierung, Reflexion und Expression Trees.

Warum wähle ich ICloneable (dh manuell)

agiledeveloper.com/articles/cloning072002.htm .

All sein Artikel kreist um ein Beispiel, das versucht, für die meisten Fälle anwendbar zu sein, mit 3 Objekten: Person , Gehirn und Stadt . Wir wollen eine Person klonen, die ihr eigenes Gehirn hat, aber dieselbe Stadt. Sie können sich entweder alle Probleme vorstellen, die einer der anderen oben genannten Methoden zum Lesen oder Lesen des Artikels bringen kann.

Dies ist meine leicht modifizierte Version seiner Schlussfolgerung:

Das Kopieren eines Objekts durch Angabe von New gefolgt von dem Klassennamen führt oft zu Code, der nicht erweiterbar ist. Mit Clone ist die Anwendung des Prototypmusters ein besserer Weg, dies zu erreichen. Die Verwendung von Clone, wie es in C # (und Java) bereitgestellt wird, kann jedoch auch ziemlich problematisch sein. Es ist besser, einen geschützten (nicht öffentlichen) Kopierkonstruktor bereitzustellen und diesen aus der Klonmethode aufzurufen. Dies gibt uns die Möglichkeit, die Aufgabe des Erstellens eines Objekts auf eine Instanz einer Klasse selbst zu delegieren, wodurch eine Erweiterbarkeit bereitgestellt wird und die Objekte unter Verwendung des Konstruktors für geschützte Kopien sicher erzeugt werden.

Hoffentlich kann diese Implementierung klarstellen:

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

Ziehen Sie in Betracht, eine Klasse von Person abzuleiten.

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

Sie können versuchen, den folgenden Code auszuführen:

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

Die produzierte Produktion wird sein:

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

Beachten Sie, dass der Clone, der hier implementiert wird, die Anzahl der Objekte korrekt zählt, wenn wir die Anzahl der Objekte zählen.




Wenn Sie bereits eine Drittanbieteranwendung wie ValueInjecter oder Automapper , können Sie ValueInjecter tun:

MyObject oldObj; // The existing object to clone

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

Mit dieser Methode müssen Sie ISerializable oder ICloneable für Ihre Objekte nicht implementieren. Dies ist üblich mit dem MVC / MVVM-Muster, also wurden einfache Werkzeuge wie dieses erstellt.

Siehe die ValueInjecter-Deep-Cloning-Lösung auf CodePlex .




Folge diesen Schritten:

  • 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.




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



Am besten ist es, eine Erweiterungsmethode wie zu implementieren

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

und dann benutze es überall in der Lösung von

var copy = anyObject.DeepClone();

Wir können die folgenden drei Implementierungen haben:

  1. Durch Serialisierung (der kürzeste Code)
  2. Durch Reflexion - 5x schneller
  3. Durch Ausdrucksbäume - 20x schneller

Alle verknüpften Methoden funktionieren gut und wurden gründlich getestet.




Ich kam auf, um einen .NET Fehler zu beheben, indem ich List <T> manuell tief kopieren musste.

Ich benutze das:

static public IEnumerable<SpotPlacement> CloneList(List<SpotPlacement> spotPlacements)
{
    foreach (SpotPlacement sp in spotPlacements)
    {
        yield return (SpotPlacement)sp.Clone();
    }
}

Und an einem anderen Ort:

public object Clone()
{
    OrderItem newOrderItem = new OrderItem();
    ...
    newOrderItem._exactPlacements.AddRange(SpotPlacement.CloneList(_exactPlacements));
    ...
    return newOrderItem;
}

Ich habe versucht, oneliner zu finden, der das tut, aber es ist nicht möglich, weil es nicht funktioniert, innerhalb anonymer Methodenblöcke zu arbeiten.

Besser noch, benutze generische 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();
        }
    }
}



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

hoffe das hilft.




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



Einfache Erweiterungsmethode zum Kopieren aller öffentlichen Eigenschaften. Funktioniert für beliebige Objekte und erfordert keine Klasse [Serializable] . Kann für andere Zugriffsebenen erweitert werden.

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



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.




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



Die kurze Antwort ist, dass Sie von der ICloneable-Schnittstelle erben und dann die .clone-Funktion implementieren. Clone sollte eine Memberwise-Kopie ausführen und eine Tiefenkopie für jedes Member ausführen, für das es erforderlich ist, und das resultierende Objekt zurückgeben. Dies ist eine rekursive Operation (es erfordert, dass alle Elemente der Klasse, die Sie klonen möchten, entweder Werttypen oder Implementieren von ICloneable sind und dass ihre Member entweder Werttypen sind oder ICloneable implementieren usw.).

Weitere Informationen zum Klonen mit ICloneable finden Sie in diesem Artikel .

Die lange Antwort ist "es kommt darauf an". Wie von anderen erwähnt, wird ICloneable nicht von Generics unterstützt, erfordert spezielle Überlegungen für zirkuläre Klassenreferenzen und wird von einigen als "Fehler" in .NET Framework angesehen. Die Serialisierungsmethode hängt davon ab, ob Ihre Objekte serialisiert werden können. Dies kann nicht der Fall sein, und Sie haben möglicherweise keine Kontrolle darüber. In der Gemeinschaft gibt es immer noch viele Diskussionen darüber, welche die "beste" Praxis ist. In der Realität ist keine der Lösungen die Einheitsgröße aller Best Practices für alle Situationen, für die ICloneable ursprünglich ausgelegt wurde.

Weitere Optionen finden Sie in diesem Developer's Corner-Artikel (Kredit für Ian).




Related



Tags

c# c#   .net .net   clone