Derin klonlama nesneleri


2226

Gibi bir şey yapmak istiyorum:

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

Ardından, orijinal nesneye yansıtılmayan yeni nesnede değişiklikler yapın.

Sıklıkla bu işlevselliğe ihtiyacım yok, bu yüzden gerekli olduğunda, yeni bir nesne oluşturmaya ve sonra her bir mülkü ayrı ayrı kopyalamaya başvurdum, ancak her zaman beni daha iyi veya daha zarif bir yolun olduğu hissiyle bırakır durum.

Klonlanan nesnenin, orijinal nesneye herhangi bir değişiklik yansıtılmadan değiştirilebilmesi için bir nesneyi nasıl kopyalayabilir veya derin kopyalayabilirim?


81
Yararlı olabilir: "Bir Nesneyi Kopyalamak neden korkunç bir şey?" agiledeveloper.com/articles/cloning072002.htm
Pedro77


18
AutoMapper
Daniel Little

3
Çözümünüz çok daha karmaşık, okurken kayboldum ... hehehe. DeepClone arayüzü kullanıyorum. genel arayüz IDeepCloneable <T> {T DeepClone (); }
Pedro77

1
@ Pedro77 - İlginçtir ki, bu makale clonesınıfta bir yöntem oluşturmayı söylese de, daha sonra geçilen bir iç, özel kurucu çağırmasını sağlayın this. Bu yüzden kopyalamak geri döndürülebilir [sic], ama dikkatlice kopyalamak (ve makalenin kesinlikle okunmaya değer) değil. ; ^)
Ruffin

Yanıtlar:


1715

Standart uygulama ICloneablearayüzü ( burada açıklandığı gibi , yeniden pişman olmayacağım) uygulamak olsa da, Kod Projesi'nde bir süre önce bulduğum ve eşyalarımıza dahil ettiğim güzel bir derin klon nesne fotokopi makinesi .

Başka bir yerde belirtildiği gibi, nesnelerin serileştirilebilir olmasını gerektirir.

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

Fikir, nesnenizi serileştirip yeni bir nesneye serileştirmesi. Avantajı, bir nesne çok karmaşık olduğunda her şeyi klonlama konusunda endişelenmenize gerek olmamasıdır.

Ve uzatma yöntemlerinin kullanılmasıyla (orijinal olarak referansta bulunulan kaynaktan da):

C # 3.0'ın yeni uzantı yöntemlerini kullanmayı tercih ederseniz , yöntemi aşağıdaki imzaya sahip olacak şekilde değiştirin:

public static T Clone<T>(this T source)
{
   //...
}

Şimdi yöntem çağrısı basitçe olur objectBeingCloned.Clone();.

EDIT (10 Ocak 2015) Bunu tekrar ziyaret edeceğimi düşündüm, yakın zamanda bunu yapmak için (Newtonsoft) Json kullanmaya başladım, daha hafif olmalı ve [Serializable] etiketlerinin ek yükünü önler. ( NB @atconway yorumlarda özel üyelerin JSON yöntemi kullanılarak klonlanmadığına dikkat çekti)

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

24
stackoverflow.com/questions/78536/cloning-objects-in-c/… yukarıdaki koda bir bağlantı vardır [ve bu bağlamda biri daha bağlamımda daha uygun olan iki diğer uygulamaya
başvurur

102
Serileştirme / serileştirme, gerekli olmayan önemli ek yükü içerir. ICloneable arabirimi ve C # .MemberWise () klon yöntemlerine bakın.
3Dave

18
@David, verilmiş, ancak nesneler hafifse ve onu kullanırken performans isabet gereksinimleri için çok yüksek değilse, yararlı bir ipucu. Bir döngüde büyük miktarda veri ile yoğun bir şekilde kullanmadım, itiraf ediyorum, ancak hiçbir zaman tek bir performans endişesi görmedim.
johnc

16
@Amir: aslında, typeof(T).IsSerializabletür [Serializable]öznitelikle işaretlenmişse , no: da doğrudur . ISerializableArayüzü uygulamak zorunda değildir .
Daniel Gehriger

11
Sadece bu yöntemin yararlı olmasına rağmen, kendimi birçok kez kullandığımdan bahsettiğimi düşündüm, Orta Güven ile hiç uyumlu değil - bu yüzden uyumluluk gerektiren bir kod yazıyorsanız dikkat edin. BinaryFormatter özel alanlara erişir ve bu nedenle kısmi güven ortamları için varsayılan izin kümesinde çalışamaz. Başka bir serileştiriciyi deneyebilirsiniz, ancak gelen nesne özel alanlara dayanıyorsa arayanın klonun mükemmel olmayabileceğini bildiğinden emin olun.
Alex Norcliffe

298

Çoğunlukla ilkel ve listelerden oluşan çok basit nesneler için bir klon istedim. Nesneniz JSON serileştirilebilir kutunun dışındaysa, bu yöntem hile yapar. Bu, klonlanmış sınıfta arabirimlerin değiştirilmesini veya uygulanmasını gerektirmez, sadece JSON.NET gibi bir JSON serileştiricisidir.

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

Ayrıca, bu uzantı yöntemini kullanabilirsiniz

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

13
solutiojn, BinaryFormatter çözümü, .NET Serileştirme Performans Karşılaştırması'ndan
esskar

3
Bunun için teşekkürler. Temelde aynı şeyi C # için MongoDB sürücüsü ile birlikte gelen BSON serileştirici ile başardı.
Mark Ewer

3
Bu benim için en iyi yol, Ancak, ben kullanıyorum Newtonsoft.Json.JsonConvertama aynı
Pierre

1
Bunun çalışabilmesi için klonlama nesnesinin daha önce belirtildiği gibi serileştirilebilir olması gerekir - bu da örneğin dairesel bağımlılıklara sahip olmayabileceği anlamına gelir
radomeit

2
Uygulama çoğu programlama dilinde uygulanabileceğinden, bunun en iyi çözüm olduğunu düşünüyorum.
mr5

178

Kullanmamaya nedeni ICloneable olduğu değil , genel bir arayüze sahip olmadığı için. Kullanmamanın nedeni belirsiz olmasıdır . Sığ ya da derin bir kopya alıp almadığınız açık değildir; uygulayıcıya bağlı.

Evet, MemberwiseClonesığ bir kopya yapıyor, ama tam tersi MemberwiseClonedeğil Clone; belki de DeepClonevar olmayacaktı. Bir nesneyi ICloneable arabirimi aracılığıyla kullandığınızda, alttaki nesnenin hangi tür klonlamanın gerçekleştirildiğini bilemezsiniz. (Ve XML yorumları bunu netleştirmeyecektir, çünkü nesnenin Klonlama yönteminden ziyade arayüz yorumlarını alacaksınız.)

Genellikle yaptığım şey Copytam olarak istediğimi yapan bir yöntem yapmak .


ICloneable'un neden muğlak olduğu düşünülmüyor. Sözlük (Of T, U) gibi bir tür verildiğinde, ICloneable.Clone'un yeni sözlüğün aynı T ve U'ları içeren bağımsız bir sözlük olmasını sağlamak için gerekli olan derin ve sığ kopyalama düzeyini yapmasını beklerim (yapı içeriği, ve / veya nesne referansları) orijinal olarak. Belirsizlik nerede? Emin olmak için, bir "Benlik" yöntemi içeren ISelf'i (Of T) devralan jenerik bir ICloneable (Of T) çok daha iyi olurdu, ama derin ve sığ klonlamada belirsizliği görmüyorum.
Supercat

31
Örneğiniz sorunu göstermektedir. Bir Sözlük <dizeniz, Müşteri> olduğunu varsayalım. Klonlanmış Sözlük orijinaliyle aynı Müşteri nesnelerine mi yoksa bu Müşteri nesnelerinin kopyalarına mı sahip olmalıdır? Her ikisi için de makul kullanım durumları vardır. Ancak ICloneable hangisini alacağınızı netleştirmiyor. Bu yüzden faydalı değil.
Ryan Lundy

@Kyralessa Microsoft MSDN makalesi aslında derin ya da sığ bir kopya isteyip istemediğinizi bilmeme problemini belirtir.
ezmek


123

Burada bağlantılı seçeneklerin birçoğu ve bu sorun için olası çözümler hakkında çok fazla okuma yaptıktan sonra, tüm seçeneklerin Ian P'nin bağlantısında oldukça iyi özetlendiğine inanıyorum (diğer tüm seçenekler bunların varyasyonlarıdır) ve en iyi çözüm Pedro77 'in soru üzerine bağlantısı .

Bu yüzden bu 2 referansın ilgili kısımlarını buraya kopyalayacağım. Bu şekilde sahip olabiliriz:

C keskin nesneleri klonlamak için yapılacak en iyi şey!

İlk ve en önemlisi, bunların hepsi bizim seçeneklerimiz:

İfade Ağaçlar tarafından makale Hızlı Derin Kopya da Diziselleştirme, Yansıma ve İfade Ağaçlar tarafından klonlama performans karşılaştırmasını vardır.

Neden ICloneable'i seçiyorum (yani manuel olarak)

Bay Venkat Subramaniam (buradaki gereksiz bağlantı) nedenini çok ayrıntılı olarak açıklıyor .

Makalesinin tamamı 3 nesne kullanarak çoğu vaka için geçerli olmaya çalışan bir örnek etrafında toplanır: Kişi , Beyin ve Şehir . Kendi beynine sahip ama aynı şehre sahip bir insanı klonlamak istiyoruz. Yukarıdaki diğer yöntemlerden herhangi birinin makaleyi getirebileceği veya okuyabileceği tüm sorunları hayal edebilirsiniz.

Bu onun sonucunun biraz değiştirilmiş versiyonum:

Bir nesneyi, Newardından sınıf adını belirterek kopyalamak, genellikle genişletilemeyen bir koda yol açar. Prototip paterninin uygulanması olan klon kullanılması, bunu başarmanın daha iyi bir yoludur. Bununla birlikte, C # (ve Java) 'da sağlanan klonun kullanılması da oldukça sorunlu olabilir. Korumalı (halka açık olmayan) bir kopya oluşturucu sağlamak ve bunu klon yönteminden çağırmak daha iyidir. Bu bize bir nesne yaratma görevini bir sınıfın kendisinin örneğine devretme, böylece genişletilebilirlik sağlama ve ayrıca korunan kopya yapıcıyı kullanarak nesneleri güvenli bir şekilde oluşturma görevini verir.

Umarım bu uygulama bazı şeyleri netleştirebilir:

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

Şimdi Kişiden türeyen bir sınıfa sahip olmayı düşünün.

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

Aşağıdaki kodu çalıştırmayı deneyebilirsiniz:

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

Üretilen çıktı:

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

Nesnelerin sayısını tutarsak, burada uygulanan klonun nesne sayısını doğru tutacağını gözlemleyin.


6
MS ICloneable, kamu üyeleri için kullanılmamasını önerir . "Clone arayanları tahmin edilebilir bir klonlama işlemi gerçekleştirme yöntemine bağlı olamadıklarından, ICloneable'nin genel API'larda uygulanmamasını öneririz." msdn.microsoft.com/tr-tr/library/… Ancak, bağlantılı makalenizde Venkat Subramaniam tarafından verilen açıklamaya dayanarak, bu durumda ICloneable nesnelerin yaratıcıları derin olduğu sürece bu durumda kullanmanın mantıklı olduğunu düşünüyorum. hangi özelliklerin derin ve sığ kopyalar olması gerektiğini anlama (örn. derin kopya Beyin, sığ kopya Şehir)
BateTech

Öncelikle, bu konuda (herkese açık API'lar) uzman olmaktan uzaktayım. Bir keresinde MS sözlerinin çok anlamlı olduğunu düşünüyorum . Ve bu API'nın kullanıcılarının bu kadar derin bir anlayışa sahip olacağını varsaymanın güvenli olduğunu düşünmüyorum . Bu yüzden, herkese açık bir API üzerinde uygulamak, onu kimin kullanacağı için gerçekten önemli değilse mantıklıdır . Her tür ayrımı yardımcı olabilir çok açıkça UML bir tür olması sanırım . Ama daha fazla tecrübesi olan birinden haber almak istiyorum. : P
cregox

CGbR Clone Generator'ı kullanabilir ve kodu elle yazmadan benzer bir sonuç elde edebilirsiniz.
Toxantron

Orta Düzey Dil uygulaması yararlıdır
Michael Freidgeim

C # 'da final yok
Konrad

84

Bir kopya yapıcıyı bir klon tercih ederim. Niyet daha açık.


5
.Net'in kopya oluşturucuları yoktur.
Pop Catalin

48
Elbette öyle: new MyObject (objToCloneFrom) Nesneyi parametre olarak klonlamaya götüren bir ctor bildirmeniz yeterlidir.
Nick

30
Aynı şey değil. Her sınıfa manuel olarak eklemek zorundasınız ve derin bir kopyayı garanti edip etmediğinizi bile bilmiyorsunuz.
Dave Van den Eynde

15
Kopya ctor için +1. Her nesne türü için de bir clone () işlevi yazmanız gerekir ve sınıf hiyerarşiniz birkaç seviye derinleştiğinde iyi şanslar elde edersiniz.
Andrew Grant

3
Kopya oluşturucularla hiyerarşiyi kaybedersiniz. agiledeveloper.com/articles/cloning072002.htm
Will

41

Tüm ortak özellikleri kopyalamak için basit bir uzantı yöntemi. Herhangi bir nesne için çalışır ve yok olmaya sınıfını gerektirir [Serializable]. Diğer erişim seviyesi için genişletilebilir.

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

15
Maalesef bu kusurlu. ObjectOne.MyProperty = objectTwo.MyProperty öğesini çağırmaya eşdeğerdir (yani, sadece referansı kopyalar). Özelliklerin değerlerini klonlamaz.
Alex Norcliffe

1
Alex Norcliffe'e: soru yazarı klonlamak yerine "her mülkü kopyalamak" hakkında sorular sordu. çoğu durumda özelliklerin tam olarak çoğaltılmasına gerek yoktur.
Konstantin Salavatov

1
Bu yöntemi kullanmayı düşünüyorum ama özyineleme ile. bir özelliğin değeri bir referanssa, yeni bir nesne oluşturun ve CopyTo öğesini tekrar arayın. i sadece kullanılan bir sorun, tüm kullanılan sınıflar parametreleri olmayan bir yapıcı olması gerekir. Bunu zaten deneyen var mı? Ayrıca bu aslında DataRow ve DataTable gibi .net sınıfları içeren özellikleri ile çalışacak merak ediyorum?
Koryu

33

Az önce CloneExtensionskütüphane projesi oluşturdum . İfade Ağacı çalışma zamanı kod derlemesi tarafından oluşturulan basit atama işlemlerini kullanarak hızlı, derin klonlama gerçekleştirir.

Bu nasıl kullanılır?

Bunun yerine kendi yazma Cloneveya Copyalanları ve özellikleri arasındaki atamaları tonu ile yöntemlerini programı İfade Ağacı kullanarak, kendin için yap olun. GetClone<T>()uzantı yöntemi olarak işaretlenen yöntem, bunu örneğinizde çağırmanıza olanak tanır:

var newInstance = source.GetClone();

Sen kopyalanabilir gerektiğini seçebilir sourceiçin newInstancekullanarak CloningFlagsenum:

var newInstance 
    = source.GetClone(CloningFlags.Properties | CloningFlags.CollectionItems);

Ne klonlanabilir?

  • İlkel (int, uint, bayt, çift, karakter vb.), Bilinen değişmez türler (DateTime, TimeSpan, String) ve delegeler (Eylem, İşlev, vb. Dahil)
  • null
  • T [] diziler
  • Genel sınıflar ve yapılar dahil özel sınıflar ve yapılar.

Aşağıdaki sınıf / yapı üyeleri dahili olarak klonlanır:

  • Salt okunur alanların değil, genel değerler
  • Hem alma hem de ayarlama erişimcileriyle genel mülklerin değerleri
  • ICollection uygulayan tipler için koleksiyon öğeleri

Ne kadar hızlı?

Çözüm, yansımadan daha hızlıdır, çünkü üyelerin bilgileri GetClone<T>belirli bir tür için ilk kez kullanılmadan önce yalnızca bir kez toplanmalıdır T.

Aynı türden birkaç örnekten daha fazlasını kopyaladığınızda seri hale getirme tabanlı çözümden de daha hızlıdır T.

ve dahası...

Belgelerde üretilen ifadeler hakkında daha fazla bilgi edinin .

Şunun için örnek ifade hata ayıklama listesi 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:
}

}

aşağıdaki c # kodu gibi aynı anlama sahip olan:

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

Kendi Cloneyönteminizi nasıl yazacağınız gibi değil List<int>mi?


2
Bunun NuGet'e girme şansı nedir? En iyi çözüm gibi görünüyor. NClone ile nasıl karşılaştırılır ?
ezmek

Bu cevabın daha fazla kaldırılması gerektiğini düşünüyorum. Manuel olarak ICloneable uygulamak sıkıcı ve hataya açıktır, performans önemliyse yansıma veya serileştirme kullanmak yavaştır ve kısa bir süre içinde binlerce nesneyi kopyalamanız gerekir.
nightcoder

Hiç değil, yansıma konusunda yanılıyorsunuz, bunu düzgün bir şekilde önbelleğe almalısınız. Cevabımın altındaki kontrol edin stackoverflow.com/a/34368738/4711853
Roma Borodov

31

Silverlight'ta ICloneable kullanarak sorun yaşıyordum, ancak seralizasyon fikrini beğendim, XML'i seralize edebilirim, bu yüzden bunu yaptım:

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

31

Zaten ValueInjecter veya Automapper gibi bir üçüncü taraf uygulaması kullanıyorsanız, şöyle bir şey yapabilirsiniz:

MyObject oldObj; // The existing object to clone

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

Bu yöntemi kullanarak nesnelerinize ISerializableveya ICloneablenesnelerinize uygulamanız gerekmez . Bu, MVC / MVVM modelinde yaygındır, bu nedenle bunun gibi basit araçlar oluşturulmuştur.

GitHub'daki ValueInjecter derin klonlama örneğine bakın .


25

En iyisi, gibi bir uzantı yöntemi uygulamaktır

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

ve sonra çözümün herhangi bir yerinde

var copy = anyObject.DeepClone();

Aşağıdaki üç uygulamaya sahip olabiliriz:

  1. Serileştirme ile (en kısa kod)
  2. Reflection ile - 5 kat daha hızlı
  3. İfade Ağaçları ile - 20 kat daha hızlı

Bağlantılı tüm yöntemler iyi çalışıyor ve derinlemesine test edildi.


codeproject.com/Articles/1111658/… yayınladığınız İfade ağaçlarını kullanarak kod klonlama , .Net framework'ün daha yeni sürümlerinde bir güvenlik istisnası ile başarısız oluyor, İşlem çalışma zamanını dengesizleştirebilir , temel olarak hatalı biçimlendirilmiş ifade ağacı nedeniyle bir istisnadır, hangi zamanında Func oluşturmak için kullanılan, bazı çözüm olup olmadığını kontrol
edin.Aslında

1
ExpreeTree uygulaması çok iyi görünüyor. Dairesel referanslar ve özel üyelerle bile çalışır. Özellik gerekmez. Bulduğum en iyi cevap.
N73k

En iyi cevap, çok iyi çalıştı, günümü kurtardın
Adel Mourad

23

Kısa cevap, ICloneable arabiriminden miras alıp .clone işlevini uygulamanızdır. Klon, üye olarak bir kopya yapmalı ve bunu gerektiren herhangi bir üye üzerinde derin bir kopya yapmalı, sonra ortaya çıkan nesneyi döndürmelidir. Bu özyinelemeli bir işlemdir (klonlamak istediğiniz sınıfın tüm üyelerinin değer türleri veya ICloneable uygulamak ve üyelerinin değer türleri veya ICloneable uygulamak vb.).

ICloneable kullanarak Klonlama hakkında daha ayrıntılı bir açıklama için bu makaleye göz atın .

Uzun cevap ise "değişir" dir. Başkaları tarafından belirtildiği gibi, ICloneable jenerikler tarafından desteklenmez, dairesel sınıf referansları için özel hususlar gerektirir ve bazıları tarafından .NET Framework'te "hata" olarak görülür . Serileştirme yöntemi, nesnelerin serileştirilebilir olmasına bağlıdır, bunlar olmayabilir ve üzerinde herhangi bir kontrolünüz olmayabilir. Toplumda hala üzerinde "en iyi" uygulama olan çok fazla tartışma var. Gerçekte, çözümlerin hiçbiri ICloneable orijinal olarak yorumlandığı gibi tüm durumlar için en iyi uygulamalara uyan tek boyutlu değildir.

Birkaç seçenek daha öğrenmek için bu Geliştirici Köşesi makalesine bakın (Ian'a kredi).


1
ICloneable genel bir arayüze sahip olmadığından, bu arayüzün kullanılması önerilmez.
Karg

Çözümünüz dairesel referansları işlemesi gerekene kadar çalışır, daha sonra işler karmaşıklaşmaya başlar, derin serileştirme kullanarak derin klonlamayı denemek daha iyidir.
Pop Catalin

Ne yazık ki, tüm nesneler de serileştirilemez, bu nedenle bu yöntemi de her zaman kullanamazsınız. Ian'ın bağlantısı şimdiye kadarki en kapsamlı cevap.
Zach Burlingame

19
  1. Temel olarak ICloneable arabirimini uygulamanız ve daha sonra nesne yapısı kopyalamayı gerçekleştirmeniz gerekir.
  2. Tüm üyelerin derin bir kopyasıysa, (seçtiğiniz çözümle ilgili olmayan) tüm çocukların da klonlanabilir olduğundan emin olmanız gerekir.
  3. Bazen bu işlem sırasında bazı kısıtlamaların farkında olmanız gerekir, örneğin ORM nesnelerini kopyalarsanız, çerçevelerin çoğu oturuma yalnızca bir nesneye izin verir ve bu nesnenin klonlarını YAPMAMALISINIZ veya mümkünse bu nesnelerin oturuma eklenmesi hakkında.

Şerefe.


4
ICloneable genel bir arayüze sahip olmadığından, bu arayüzün kullanılması önerilmez.
Karg

Basit ve özlü cevaplar en iyisidir.
DavidGuaita

17

DÜZENLEME: proje durduruldu

Bilinmeyen türlere gerçek klonlama yapmak istiyorsanız fastclone'a bakabilirsiniz .

Bu ifade tabanlı klonlama, ikili serileştirmeden yaklaşık 10 kat daha hızlı çalışır ve tam nesne grafiği bütünlüğünü korur.

Bunun anlamı şudur: hiyerarşinizdeki aynı nesneye birden çok kez atıfta bulunursanız, klonun atıfta bulunulan tek bir örneği olacaktır.

Klonlanan nesnelerde arayüzlere, niteliklere veya başka herhangi bir modifikasyona gerek yoktur.


Bu oldukça yararlı görünüyor
LuckyLikey

Bir kod anlık görüntüsünden çalışmaya başlamak genel sistemden, özellikle kapalı olandan daha kolaydır. Hiçbir kütüphanenin tüm sorunları tek bir atışla çözememesi oldukça anlaşılabilir. Bazı rahatlamalar yapılmalıdır.
TarmoPikaro

1
Çözümünüzü denedim ve iyi çalışıyor gibi görünüyor, teşekkürler! Bu cevabın daha fazla kaldırılması gerektiğini düşünüyorum. Manuel olarak ICloneable uygulamak sıkıcı ve hataya açıktır, performans önemliyse yansıma veya serileştirme kullanmak yavaştır ve kısa bir süre içinde binlerce nesneyi kopyalamanız gerekir.
nightcoder

Denedim ve benim için hiç işe yaramadı. Bir MemberAccess istisnası atar.
Michael Brown

.NET'in yeni sürümleriyle çalışmaz ve kullanımdan kaldırılır
Michael Sander

14

İşleri basit tutun ve AutoMapper'ı diğerlerinin belirttiği gibi kullanın , bir nesneyi diğerine eşlemek için basit bir küçük kütüphane ... Bir nesneyi aynı türden diğerine kopyalamak için tek ihtiyacınız olan üç satır kod:

MyType source = new MyType();
Mapper.CreateMap<MyType, MyType>();
MyType target = Mapper.Map<MyType, MyType>(source);

Hedef nesne artık kaynak nesnenin bir kopyasıdır. Yeterince basit değil mi? Çözümünüzün her yerinde kullanmak için bir uzantı yöntemi oluşturun:

public static T Copy<T>(this T source)
{
    T copy = default(T);
    Mapper.CreateMap<T, T>();
    copy = Mapper.Map<T, T>(source);
    return copy;
}

Genişletme yöntemi aşağıdaki gibi kullanılabilir:

MyType copy = source.Copy();

Bu konuda dikkatli olun, gerçekten kötü performans gösteriyor. Bu kadar kısa olan ve çok daha iyi performans gösteren johnc cevabına geçtim.
Agorilla

1
Bu sadece sığ bir kopya yapar.
N73k

11

Bu liste <T> el ile derin kopyalamak zorunda bir .NET eksikliğini aşmak için geldi .

Bunu kullanıyorum:

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

Ve başka bir yerde:

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

Bunu yapan oneliner ile gelmeye çalıştım, ancak anonim yöntem blokları içinde çalışmayan verim nedeniyle mümkün değil.

Daha da iyisi, genel Liste <T> klonlayıcı kullanın:

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

10

S. Bu cevabı neden seçeyim?

  • .NET'in yapabileceği en yüksek hızı istiyorsanız bu yanıtı seçin.
  • Gerçekten, gerçekten kolay bir klonlama yöntemi istiyorsanız bu cevabı göz ardı edin.

Başka bir deyişle, düzeltilmesi gereken bir performans darboğunuz yoksa ve bir profil oluşturucu ile kanıtlayamazsanız başka bir yanıtla devam edin .

Diğer yöntemlerden 10 kat daha hızlı

Derin bir klon gerçekleştirmenin aşağıdaki yöntemi:

  • Serileştirme / serileştirme içeren her şeyden 10 kat daha hızlı;
  • .NET'in yapabileceği teorik maksimum hıza oldukça yakın.

Ve yöntem ...

En yüksek hız için, derin bir kopya yapmak üzere Nested MemberwiseClone'u kullanabilirsiniz . Bir değer yapısını kopyalamakla neredeyse aynı hızdır ve (a) yansıma veya (b) serileştirmeden (bu sayfadaki diğer yanıtlarda açıklandığı gibi) çok daha hızlıdır.

O Not eğer kullandığınız derin kopya için İçiçe MemberwiseClone , el sınıftaki her iç içe düzeyde ve tüm basitkopyasını yöntemleri tam bir klon oluşturmak için adı geçen çağıran bir deepcopy için basitkopyasını uygulamak zorunda. Bu basit: toplamda sadece birkaç satır, aşağıdaki demo koduna bakın.

İşte 100.000 klon için göreceli performans farkını gösteren kod çıktısı:

  • İç içe yapılarda İç içe Memberwise için 1.08 saniye
  • İç içe sınıflarda İç içe MemberwiseClone için 4.77 saniye
  • Serileştirme / Serileştirme için 39.93 saniye

Neredeyse bir yapıyı kopyalamak kadar hızlı bir sınıfta Nested MemberwiseClone kullanmak ve bir yapıyı kopyalamak, .NET'in yapabileceği teorik maksimum hıza çok yakındır.

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 kullanarak nasıl derin bir kopya yapılacağını anlamak için, yukarıdaki zamanları oluşturmak için kullanılan demo projesi:

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

Ardından demoyu ana hattan arayın:

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

Yine, bu not eğer kullandığınız derin kopya için İçiçe MemberwiseClone , sahip manuel sınıftaki her iç içe düzeyde ve tüm basitkopyasını yöntemleri tam bir klon oluşturmak için adı geçen çağıran bir deepcopy için basitkopyasını uygulamaktır. Bu basit: toplamda sadece birkaç satır, yukarıdaki demo koduna bakın.

Değer türleri ve Referans Türleri

Bir nesneyi klonlama söz konusu olduğunda, bir " yapı " ve " sınıf " arasında büyük bir fark olduğunu unutmayın :

  • Bir " yapınız " varsa, bu sadece kopyalayabilmeniz için bir değer türüdür ve içerik klonlanır (ancak bu yazıdaki teknikleri kullanmadığınız sürece sadece sığ bir klon yapar).
  • Bir " sınıfınız " varsa, bu bir referans türüdür , bu yüzden kopyalarsanız, yaptığınız tek şey işaretçiyi buna kopyalamaktır. Gerçek bir klon oluşturmak için, daha yaratıcı olmanız ve bellekte orijinal nesnenin başka bir kopyasını oluşturan değer türleri ve başvuru türleri arasındaki farkları kullanmanız gerekir .

Değer türleri ve referans türleri arasındaki farkları görün .

Hata ayıklamaya yardımcı olacak sağlama toplamları

  • Nesneleri yanlış klonlamak, sabitlenmesi çok zor olan hatalara yol açabilir. Üretim kodunda, nesnenin düzgün bir şekilde klonlandığını ve başka bir referansla bozulmadığını iki kez kontrol etmek için bir sağlama toplamı uygulama eğilimindeyim. Bu sağlama toplamı Serbest Bırakma modunda kapatılabilir.
  • Bu yöntemi oldukça kullanışlı buluyorum: genellikle, tüm nesnenin değil, yalnızca nesnenin parçalarını klonlamak istiyorsunuz.

Diğer birçok iplikten birçok ipliğin ayrıştırılması için gerçekten yararlıdır

Bu kod için mükemmel bir kullanım örneği, üretici / tüketici modelini uygulamak için iç içe bir sınıfın veya yapının klonlarını bir kuyruğa beslemektir.

  • Sahip oldukları bir sınıfı değiştiren bir (veya daha fazla) iş parçacığımız olabilir ve bu sınıfın tam bir kopyasını bir ConcurrentQueue.
  • Daha sonra bu sınıfların kopyalarını çekip onlarla ilgilenen bir (veya daha fazla) iş parçacığımız var.

Bu pratikte son derece iyi çalışır ve birçok iş parçacığını (üretici) bir veya daha fazla iş parçacığından (tüketiciler) ayırmamızı sağlar.

Ve bu yöntem de körü körüne hızlıdır: İç içe geçmiş yapılar kullanırsak, iç içe geçmiş sınıfları serileştirmek / serileştirmekten 35 kat daha hızlıdır ve makinede bulunan tüm iş parçacıklarından yararlanmamızı sağlar.

Güncelleme

Görünüşe göre, ExpressMapper yukarıdaki gibi el kodlamasından daha hızlı olmasa da daha hızlıdır. Bir profilerle nasıl karşılaştırdıklarını görmek zorunda kalabilirim.


Sığ bir kopya elde ettiğiniz bir yapıyı kopyalarsanız, derin bir kopya için yine de belirli bir uygulamaya ihtiyacınız olabilir.
Lasse V. Karlsen

@Lasse V. Karlsen. Evet, kesinlikle haklısın, cevabı daha açık hale getirmek için güncelledim. Bu yöntem, yapıların ve sınıfların derin kopyalarını yapmak için kullanılabilir . Nasıl yapıldığını göstermek için dahil edilen örnek demo kodunu çalıştırabilirsiniz, iç içe geçmiş bir yapının derin klonlama örneğine ve iç içe geçmiş bir sınıfın derin klonlama örneğine sahiptir.
Contango

9

Genel olarak, ICloneable arabirimini uygular ve Clone'u kendiniz uygularsınız. C # nesneleri, tüm temel öğeler için size yardımcı olabilecek sığ bir kopya gerçekleştiren yerleşik bir MemberwiseClone yöntemine sahiptir.

Derin bir kopya için, otomatik olarak nasıl yapılacağını bilemez.


ICloneable genel bir arayüze sahip olmadığından, bu arayüzün kullanılması önerilmez.
Karg

8

Ben de yansıtma yoluyla uygulandığını gördüm. Temel olarak, bir nesnenin üyeleri arasında yineleme yapacak ve bunları uygun şekilde yeni nesneye kopyalayacak bir yöntem vardı. Referans türlerine veya koleksiyonlarına ulaştığında kendi kendine tekrarlı bir çağrı yaptığını düşünüyorum. Yansıma pahalıdır, ancak oldukça iyi çalıştı.


8

İşte derin bir kopya uygulaması:

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

2
Üyelik klonuna benziyor, çünkü referans tipi özelliklerin farkında değil
sll

1
Körü körüne hızlı performans istiyorsanız, bu uygulamaya gitmeyin: yansıma kullanır, bu yüzden bu kadar hızlı olmayacaktır. Tersine, "erken optimizasyon tüm kötülüklerden biridir", bu nedenle bir profil oluşturucu çalışana kadar performans tarafını göz ardı edin.
Contango

1
CreateInstanceOfType tanımlı değil mi?
MonsterMMORPG

Interger üzerinde başarısız olur: "Statik olmayan yöntem bir hedef gerektirir."
Mr.B

8

Farklı projelerde tüm gereksinimlerimi karşılayan bir klon bulamadığım için, kodumu klon gereksinimlerini karşılamak için uyarlamak yerine yapılandırılabilen ve farklı kod yapılarına uyarlanabilen derin bir klonlayıcı oluşturdum. Klonlanacak koda ek açıklamalar ekleyerek elde edilir veya kodu varsayılan davranışa olduğu gibi bırakırsınız. Yansımayı , tip önbellekleri kullanır ve faslektife dayanır . Klonlama işlemi büyük miktarda veri ve yüksek nesne hiyerarşisi için çok hızlıdır (diğer yansıma / serileştirme tabanlı algoritmalara kıyasla).

https://github.com/kalisohn/CloneBehave

Nuget paketi olarak da mevcuttur: https://www.nuget.org/packages/Clone.Behave/1.0.0

Örneğin: Aşağıdaki kod DeepClone Adresi olacaktır, ancak _currentJob alanının yalnızca sığ bir kopyasını oluşturacaktır.

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

7

Kod üreteci

El ile uygulamadan serileştirmeden yansımaya kadar birçok fikir gördük ve CGbR Kod Oluşturucu'yu kullanarak tamamen farklı bir yaklaşım önermek istiyorum . Oluşturma klonu yöntemi bellek ve CPU verimlidir ve bunun için standart DataContractSerializer'den 300 kat daha hızlıdır.

İhtiyacınız olan tek şey kısmi bir sınıf tanımıdır ICloneableve gerisini jeneratör yapar:

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

Not: Son sürümde daha boş denetimler vardır, ancak daha iyi anlamak için onları dışarıda bıraktım.


6

Bu gibi Kopya Oluşturucular seviyorum:

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

Kopyalanacak başka şeyleriniz varsa bunları ekleyin


6

Bu yöntem benim için sorunu çözdü:

private static MyObj DeepCopy(MyObj source)
        {

            var DeserializeSettings = new JsonSerializerSettings { ObjectCreationHandling = ObjectCreationHandling.Replace };

            return JsonConvert.DeserializeObject<MyObj >(JsonConvert.SerializeObject(source), DeserializeSettings);

        }

Şöyle kullanın: MyObj a = DeepCopy(b);


6

İşte Seri ve Deserialization geçişi olmadan benim için çalışan hızlı ve kolay bir çözüm.

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 : gerektirir

    using System.Linq;
    using System.Reflection;

Ben böyle kullandım

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

5

Bu adımları takip et:

  • Bir tanımlama ISelf<T>, bir salt okunur ile Selfdöndürdüğü özelliği Tve ICloneable<out T>gelen olan türemiştir ISelf<T>ve bir yöntem içermektedir T Clone().
  • Ardından , aktarılan türe döküm CloneBaseuygulayan bir tür tanımlayın . protected virtual generic VirtualCloneMemberwiseClone
  • Her türetilmiş tür VirtualClonetemel klon yöntemini çağırarak ve sonra ana VirtualClone yönteminin henüz ele almadığı türetilmiş türün yönlerini düzgün bir şekilde klonlamak için yapılması gerekenleri yaparak uygulanmalıdır .

Maksimum kalıtım çok yönlülüğü için, genel klonlama işlevselliğini ortaya koyan sınıflar olmalıdır sealed, ancak klonlama eksikliği dışında tamamen aynı olan bir temel sınıftan türetilmelidir. Açık klonlanabilir tipteki değişkenleri iletmektense, bir tür parametresi alın ICloneable<theNonCloneableType>. Bu, klonlanabilir bir türevinin klonlanabilir bir türevi Fooile çalışmasını bekleyen bir rutine DerivedFooizin verir, fakat aynı zamanda klonlanamayan türevlerinin oluşturulmasına da izin verir Foo.


5

Bence bunu deneyebilirsin.

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

4

Hem '[Serializable]' hem de '[DataContract]' ile çalışan kabul edilen yanıtın bir sürümünü oluşturdum. Yazdığımdan bu yana bir süre geçti, ama doğru hatırlarsam [DataContract] farklı bir serileştiriciye ihtiyaç duydu.

Gerektirir Sistemi, System.ıo, System.Runtime.Serialization, System.Runtime.Serialization.Formatters.Binary, System.Xml ;

public static class ObjectCopier
{

    /// <summary>
    /// Perform a deep Copy of an object that is marked with '[Serializable]' or '[DataContract]'
    /// </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 == true)
        {
            return CloneUsingSerializable<T>(source);
        }

        if (IsDataContract(typeof(T)) == true)
        {
            return CloneUsingDataContracts<T>(source);
        }

        throw new ArgumentException("The type must be Serializable or use DataContracts.", "source");
    }


    /// <summary>
    /// Perform a deep Copy of an object that is marked with '[Serializable]'
    /// </summary>
    /// <remarks>
    /// Found on http://stackoverflow.com/questions/78536/cloning-objects-in-c-sharp
    /// Uses code found on CodeProject, which allows free use in third party apps
    /// - http://www.codeproject.com/KB/tips/SerializedObjectCloner.aspx
    /// </remarks>
    /// <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 CloneUsingSerializable<T>(T source)
    {
        if (!typeof(T).IsSerializable)
        {
            throw new ArgumentException("The type must be serializable.", "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);
        }
    }


    /// <summary>
    /// Perform a deep Copy of an object that is marked with '[DataContract]'
    /// </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 CloneUsingDataContracts<T>(T source)
    {
        if (IsDataContract(typeof(T)) == false)
        {
            throw new ArgumentException("The type must be a data contract.", "source");
        }

        // ** Don't serialize a null object, simply return the default for that object
        if (Object.ReferenceEquals(source, null))
        {
            return default(T);
        }

        DataContractSerializer dcs = new DataContractSerializer(typeof(T));
        using(Stream stream = new MemoryStream())
        {
            using (XmlDictionaryWriter writer = XmlDictionaryWriter.CreateBinaryWriter(stream))
            {
                dcs.WriteObject(writer, source);
                writer.Flush();
                stream.Seek(0, SeekOrigin.Begin);
                using (XmlDictionaryReader reader = XmlDictionaryReader.CreateBinaryReader(stream, XmlDictionaryReaderQuotas.Max))
                {
                    return (T)dcs.ReadObject(reader);
                }
            }
        }
    }


    /// <summary>
    /// Helper function to check if a class is a [DataContract]
    /// </summary>
    /// <param name="type">The type of the object to check.</param>
    /// <returns>Boolean flag indicating if the class is a DataContract (true) or not (false) </returns>
    public static bool IsDataContract(Type type)
    {
        object[] attributes = type.GetCustomAttributes(typeof(DataContractAttribute), false);
        return attributes.Length == 1;
    }

} 

4

Tamam, bu yazıda yansıması olan bazı belirgin örnekler var, ancak düzgün bir şekilde önbelleğe almaya başlayana kadar, yansıması genellikle yavaştır.

düzgün bir şekilde önbelleğe alırsanız, 1000000 nesnesini 4,6s (Watcher tarafından ölçülür) derin klonlayacaktır.

static readonly Dictionary<Type, PropertyInfo[]> ProperyList = new Dictionary<Type, PropertyInfo[]>();

önbelleğe alınmış özellikleri alıp sözlüğe yeni eklediğinizden ve bunları

foreach (var prop in propList)
{
        var value = prop.GetValue(source, null);   
        prop.SetValue(copyInstance, value, null);
}

başka bir cevapta yazımda tam kod kontrolü

https://stackoverflow.com/a/34365709/4711853


2
Arama prop.GetValue(...)hala yansıtmadır ve önbelleğe alınamaz. Bir ifade ağacında daha derlenmiş, çok daha hızlı
Tseng

4

Bu sorunun cevaplarının neredeyse tamamı tatmin edici olmadığı veya durumumda açıkça işe yaramadığı için , tamamen yansıma ile uygulanan ve burada tüm ihtiyaçları çözen AnyClone'u yazdım . Serileştirmeyi karmaşık yapıya sahip karmaşık bir senaryoda çalıştıramadım veIClonable ideal olandan daha az - aslında gerekli bile değil.

Standart yok sayma öznitelikleri [IgnoreDataMember],[NonSerialized] . Karmaşık koleksiyonları, ayarlayıcısız özellikleri, salt okunur alanları vb. Destekler.

Umarım aynı problemlerle karşılaşan başka birine yardımcı olur.


4

Feragatname: Söz konusu paketin yazarıyım.

2019'da bu soruya verilen en iyi cevapların hala serileştirme veya yansıma kullandığına şaşırdım.

Serileştirme sınırlayıcı (nitelikler, belirli kurucular vb. Gerektirir) ve çok yavaş

BinaryFormatterözelliği gerektirir Serializable,JsonConverter parametresiz yapıcı veya özelliklerini gerektirir ne kulp çok iyi sadece alanları veya arayüzler okuyup hem gerekenden daha yavaş 10-30x bulunmaktadır.

İfade Ağaçları

Bunun yerine İfade Ağaçlarını veya Yansımayı kullanabilirsiniz. kullanabilirsiniz. Klonlama kodunu yalnızca bir kez oluşturmak için gönderin, ardından yavaş yansıma veya serileştirme yerine bu derlenmiş kodu kullanın.

Sorunu kendim buldum ve tatmin edici bir çözüm göremedim, sadece bunu yapan ve her türle çalışan ve neredeyse özel yazılı kod kadar hızlı bir paket oluşturmaya karar verdim. .

Projeyi GitHub'da bulabilirsiniz: https://github.com/marcelltoth/ObjectCloner

kullanım

NuGet'ten yükleyebilirsiniz. Ya olsun ObjectClonerpaketi ve olarak kullanmak:

var clone = ObjectCloner.DeepClone(original);

ya da nesne türünüzü uzantılarla kirletmenin sakıncası yoksa ObjectCloner.Extensions, şunu da yazın ve yazın:

var clone = original.DeepClone();

Verim

Bir sınıf hiyerarşisini klonlamanın basit bir ölçütü performansı Reflection kullanmaktan ~ 3 kat daha hızlı, Newtonsoft'tan ~ 12 kat daha hızlı gösterdi BinaryFormatter.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.