Yapıcıyı Clone () ile kopyala


120

C # 'da, bir sınıfa (derin) kopyalama işlevi eklemenin tercih edilen yolu nedir? Kopya oluşturucusunu uygulamak mı yoksa daha doğrusu yöntemi türetmek ICloneableve uygulamak Clone()mı gerekir?

Not : Parantez içine "derin" yazdım çünkü alakasız olduğunu düşündüm. Görünüşe göre diğerleri aynı fikirde değil, bu yüzden bir kopya oluşturucu / işleç / işlevin hangi kopya varyantını uyguladığını netleştirmesi gerekip gerekmediğini sordum .

Yanıtlar:


91

Bundan türetmemelisin ICloneable.

Bunun nedeni, Microsoft .net çerçevesini tasarladığında, Clone()yöntemin ICloneablederin veya sığ bir klon olup olmayacağını asla belirtmemiş olmasıdır , bu nedenle arayanlar, aramanın nesneyi derin mi yoksa sığ klonlayacağını bilemeyecekleri için arayüz anlamsal olarak bozuktur.

Bunun yerine, kendi IDeepCloneable(ve IShallowCloneable) arayüzlerinizi DeepClone()(ve ShallowClone()) yöntemleriyle tanımlamalısınız .

İki arabirim tanımlayabilirsiniz; biri güçlü biçimde yazılmış klonlamayı desteklemek için genel bir parametreye sahipken, diğeri farklı türden klonlanabilir nesnelerin koleksiyonlarıyla çalışırken zayıf yazılan klonlama yeteneğini korumaksızın:

public interface IDeepCloneable
{
    object DeepClone();
}
public interface IDeepCloneable<T> : IDeepCloneable
{
    T DeepClone();
}

Bunu daha sonra şu şekilde uygularsınız:

public class SampleClass : IDeepCloneable<SampleClass>
{
    public SampleClass DeepClone()
    {
        // Deep clone your object
        return ...;
    }
    object IDeepCloneable.DeepClone()   
    {
        return this.DeepClone();
    }
}

Genel olarak, bir kopya oluşturucunun aksine açıklanan arayüzleri kullanmayı tercih ederim, bu da amacı çok net tutar. Bir kopya oluşturucunun muhtemelen derin bir klon olduğu varsayılır, ancak kesinlikle bir IDeepClonable arayüzü kullanmak kadar net bir amaç değildir.

Bu, .net Çerçeve Tasarım Kılavuzunda ve Brad Abrams'ın blogunda tartışılmaktadır.

(Sanırım bir uygulama yazıyorsanız (bir çerçeve / kitaplığın aksine) ekibinizin dışından hiç kimsenin kodunuzu çağırmayacağından emin olabilirsiniz, bu çok da önemli değil ve anlamsal bir anlam atayabilirsiniz. .net ICloneable arayüzüne "deepclone", ancak bunun iyi belgelendiğinden ve ekibiniz içinde iyi anlaşıldığından emin olmalısınız. Şahsen ben çerçeve kurallarına bağlı kalırım.)


2
Bir arayüz için gidiyorsanız, DeepClone (of T) () ve DeepClone (of T) (T olarak kukla), her ikisi de T döndüren ne dersiniz? İkinci sözdizimi, T'nin argümana dayalı olarak çıkarılmasına izin verir.
supercat

@supercat: Türün çıkarılabilmesi için kukla bir parametreniz olduğunu mu söylüyorsunuz? Sanırım bu bir seçenek. Türün otomatik olarak çıkarılmasını sağlamak için sahte bir parametrenin olmasını sevdiğimden emin değilim. Belki seni yanlış anlıyorum. (Belki yeni bir cevaba biraz kod gönderin, böylece ne demek istediğinizi anlayabilirim).
Simon P Stevens

@supercat: Kukla parametre tam olarak tür çıkarımına izin vermek için var olacaktır. Bazı kodların, türün ne olduğuna hazır erişime sahip olmadan bir şeyi klonlamak isteyebileceği durumlar vardır (örneğin, başka bir sınıftan bir alan, özellik veya işlev dönüşü olduğu için) ve bir kukla parametrenin, türü doğru bir şekilde çıkarmanın bir yoluna izin vereceği durumlar vardır. Bunu düşündüğümüzde, bir arayüzün tüm amacı derin klonlanabilir bir koleksiyon gibi bir şey yaratmak olduğundan, bu durumda muhtemelen koleksiyonun jenerik türü olmalıdır.
supercat

2
Soru! Hangi durumda jenerik olmayan versiyonu istersiniz? Bana göre, sadece IDeepCloneable<T>var olmak mantıklı , çünkü ... kendi uygulamanızı yaparsanız ne olduğunu biliyorsunuz, yaniSomeClass : IDeepCloneable<SomeClass> { ... }
Kyle Baran

2
@Kyle, klonlanabilir nesneleri alan bir yönteminiz olduğunu MyFunc(IDeepClonable data)söylüyor, bu durumda yalnızca belirli bir tür değil, tüm klonlanabilirler üzerinde çalışabilir. Veya bir klonlanabilir koleksiyonunuz varsa. IEnumerable<IDeepClonable> lotsOfCloneablesdaha sonra aynı anda birçok nesneyi klonlayabilirsiniz. Yine de bu tür bir şeye ihtiyacınız yoksa, jenerik olmayan olanı dışarıda bırakın.
Simon P Stevens

33

C # 'da, bir sınıfa (derin) kopyalama işlevi eklemenin tercih edilen yolu nedir? Kopya yapıcısı mı uygulanmalı, yoksa ICloneable'dan türetilmeli ve Clone () yöntemi uygulanmalı mı?

Bununla ilgili sorun ICloneable, diğerlerinin de belirttiği gibi, derin veya sığ bir kopya olup olmadığını belirtmemesi, bu da onu pratikte kullanılamaz hale getirmesi ve pratikte nadiren kullanmasıdır. Aynı zamanda objectçok fazla döküm gerektirdiği için acı veren geri dönüyor . (Ve soruda özellikle sınıflardan bahsetmiş olsanız da ICloneable, bir üzerine uygulamak structkutulama gerektirir.)

Bir kopya oluşturucu da ICloneable ile ilgili sorunlardan birinden muzdariptir. Bir kopya oluşturucunun derin veya sığ bir kopya mı yaptığı açık değildir.

Account clonedAccount = new Account(currentAccount); // Deep or shallow?

Bir DeepClone () yöntemi oluşturmak en iyisidir. Bu şekilde niyet tamamen açıktır.

Bu, statik mi yoksa örnek yöntem mi olması gerektiği sorusunu gündeme getirir.

Account clonedAccount = currentAccount.DeepClone();  // instance method

veya

Account clonedAccount = Account.DeepClone(currentAccount); // static method

Bazen statik versiyonu biraz tercih ederim, çünkü klonlama, nesnenin yaptığı bir şeyden ziyade bir nesneye yapılan bir şey gibi görünüyor. Her iki durumda da, bir kalıtım hiyerarşisinin parçası olan nesneleri klonlarken ilgilenilmesi gereken sorunlar olacaktır ve bu sorunların nasıl çözüleceği nihayetinde tasarımı yönlendirebilir.

class CheckingAccount : Account
{
    CheckAuthorizationScheme checkAuthorizationScheme;

    public override Account DeepClone()
    {
        CheckingAccount clone = new CheckingAccount();
        DeepCloneFields(clone);
        return clone;
    }

    protected override void DeepCloneFields(Account clone)
    {
        base.DeepCloneFields(clone);

        ((CheckingAccount)clone).checkAuthorizationScheme = this.checkAuthorizationScheme.DeepClone();
    }
}

1
DeepClone () seçeneğinin en iyisi olup olmadığını bilmeme rağmen, bence temel programlama dili özelliği hakkında var olan kafa karıştırıcı durumun altını çizdiği için cevabınızı çok beğeniyorum. Sanırım hangi seçeneği en çok sevdiğini seçmek kullanıcıya kalmış.
Dimitri C.

11
Buradaki noktaları tartışmayacağım, ancak bence arayan kişi Clone () aradıklarında derin veya sığ olmayı çok fazla önemsememelidir. Geçersiz paylaşılan durumu olmayan bir klon aldıklarını bilmeleri gerekir. Örneğin, derin bir klonda her öğeyi derinlemesine klonlamak istemeyebilirim. Clone'u arayan kişinin umursaması gereken tek şey, orijinaline geçersiz ve desteklenmeyen referanslar içermeyen yeni bir kopya almalarıdır. Metodun 'DeepClone' olarak adlandırılması, arayan kişiye çok fazla uygulama ayrıntısı iletiyor gibi görünüyor.
zumalifeguard

1
Statik bir yöntemle kopyalanmak yerine kendini nasıl klonlayacağını bilen bir nesne örneğinin nesi yanlış? Bu, gerçek dünyada biyolojik hücrelerde her zaman meydana gelir. Siz bunu okurken bedeninizdeki hücreler şu anda kendilerini klonlamakla meşguller. Statik yöntem seçeneği olan IMO daha zahmetlidir, işlevselliği gizleme eğilimindedir ve başkalarının yararına "en az şaşırtıcı" uygulamayı kullanmaktan sapar.
Ken Beckett

8
@KenBeckett - Klonlamanın bir nesneye yapılmış bir şey olduğunu hissetmemin nedeni, bir nesnenin "bir şeyi yapması ve onu iyi yapması" gerektiğidir. Normalde, kendi kopyalarını yapmak bir sınıfın temel yeterliliği değildir, daha çok üzerinde durulan işlevselliktir. Bir BankAccount klonu yapmak, çok iyi yapmak isteyebileceğiniz bir şeydir, ancak kendi klonlarını yapmak bir banka hesabının özelliği değildir. Hücre örneğiniz genel olarak öğretici değildir, çünkü üreme tam olarak hücrelerin yapmak için evrimleştiği şeydir. Cell.Clone iyi bir örnek yöntem olabilir, ancak bu diğer birçok şey için geçerli değildir.
Jeffrey L Whitledge

23

Öncelikle bir klon yöntemi üzerinde bir kopya oluşturucu kullanmanızı öneririm çünkü bir klon yöntemi, readonlybunun yerine bir yapıcı kullanmış olsaydınız olabilecek alanları oluşturmanızı önleyecektir .

Polimorfik klonlamaya ihtiyaç duyuyorsanız , temel sınıfınıza copy yapıcısına yapılan bir çağrı ile uyguladığınız bir abstractveya virtual Clone()yöntemi ekleyebilirsiniz .

Birden fazla tür kopyaya ihtiyacınız varsa (örn: derin / sığ), bunu kopya oluşturucudaki bir parametre ile belirtebilirsiniz, ancak deneyimlerime göre genellikle derin ve yüzeysel kopyalamanın bir karışımına ihtiyacım olduğunu görüyorum.

Ör:

public class BaseType {
   readonly int mBaseField;

   public BaseType(BaseType pSource) =>
      mBaseField = pSource.mBaseField;

   public virtual BaseType Clone() =>
      new BaseType(this);
}

public class SubType : BaseType {
   readonly int mSubField;

   public SubType(SubType pSource)
   : base(pSource) =>
      mSubField = pSource.mSubField;

   public override BaseType Clone() =>
      new SubType(this);
}

8
+1 Polimorfik klonlamayı ele almak için; önemli bir klonlama uygulaması.
samis

18

Korumalı bir kopya oluşturucu kullanarak clone () uygulamanız gerektiğine dair harika bir argüman var

Korumalı (genel olmayan) bir kopya oluşturucu sağlamak ve bunu klon yönteminden çağırmak daha iyidir. Bu bize, bir sınıfın kendi örneğine bir nesne yaratma görevini devretme, böylece genişletilebilirlik sağlama ve ayrıca korumalı kopya yapıcısını kullanarak nesneleri güvenli bir şekilde oluşturma yeteneği verir.

Yani bu bir "karşı" soru değil. Bunu doğru yapmak için hem kopya yapıcılara hem de bir klon arayüzüne ihtiyacınız olabilir.

(Önerilen ortak arabirim, Oluşturucu tabanlı değil Clone () arabirimi olsa da.)

Diğer yanıtlardaki açık derin veya sığ tartışmaya kapılmayın. Gerçek dünyada bu neredeyse her zaman arada bir şeydir ve her iki durumda da arayanın endişesi olmamalıdır.

Clone () sözleşmesi basitçe "ilkini değiştirdiğimde değişmeyecek" dir. Grafiğin ne kadarını kopyalamanız gerektiği veya bunun gerçekleşmesi için sonsuz yinelemeden nasıl kaçındığınız arayan kişiyi ilgilendirmemelidir.


"Arayanın endişesi olmamalı". Daha fazla aynı fikirde değildim ama işte burada, List <T> aList = new List <T> (aFullListOfT) derin bir kopya mı (istediğim şey bu) yoksa sığ bir Kopya mı (kırılacak kodum) ve işi bitirmek için başka bir yol uygulamam gerekip gerekmediğini!
ThunderGr

3
Bir <T> listesi, klonun anlamlı olması için çok geneldir (ha ha). Sizin durumunuzda, bu kesinlikle listenin yalnızca bir kopyasıdır ve listenin gösterdiği nesneler DEĞİLDİR. Yeni listeyi değiştirmek ilk listeyi etkilemeyecektir, ancak nesneler aynıdır ve sabit olmadıkları sürece, ikinci sette olanları değiştirirseniz, ilk sette olanlar değişecektir. Kitaplığınızda list.Clone () işlemi varsa, "ilkine bir şey yaptığımda değişmeyecek" gibi sonucun tam bir klon olmasını beklemelisiniz. bu, içerilen nesneler için de geçerlidir.
DanO

1
Bir Liste <T>, içeriğini doğru bir şekilde klonlamak hakkında sizden daha fazla şey bilmeyecektir. Altta yatan nesne değişmez ise, gitmekte fayda var. Aksi takdirde, temeldeki nesnenin bir Clone () yöntemi varsa, onu kullanmanız gerekecektir. Liste <T> aList = new List <T> (aFullListOfT.Select (t = t.Clone ())
DanO

1
Hibrit yaklaşım için +1. Her iki yaklaşımın da bir avantajı ve dezavantajı vardır, ancak bunun daha genel bir faydası olduğu görülmektedir.
Kyle Baran

12

ICloneable'ın uygulanması , derin veya sığ bir kopya olup olmadığı belirtilmediği için tavsiye edilmez , bu yüzden kurucuya gidebilir veya sadece bir şeyi kendim uygularım. Belki bunu gerçekten açık hale getirmek için DeepCopy () olarak adlandırın!


5
@Grant, kurucu röle niyeti nasıl? IOW, bir nesne kurucuda kendini almışsa, kopya derin mi yoksa sığ mı? Aksi takdirde, DeepCopy () (veya başka türlü) önerisine tamamen katılıyorum.
Marc

7
Bir kurucunun ICloneable arayüz kadar belirsiz olduğunu iddia ediyorum - derin bir klon yapıp yapmadığını bilmek için API belgelerini / kodunu okumanız gerekir. Sadece IDeepCloneable<T>bir DeepClone()yöntemle bir arayüz tanımlıyorum .
Kent Boogaart

2
@Jon - Reaktör asla bitmedi!
Grant Crofton

@Marc, @Kent - evet doğru nokta, kurucu da muhtemelen iyi bir fikir değil.
Grant Crofton

3
Bilinmeyen türde bir nesnede iCloneable'ın kullanıldığı bir kullanım gören oldu mu? Arayüzlerin tüm amacı, bilinmeyen tipteki nesneler üzerinde kullanılabilmeleridir; aksi takdirde Clone, söz konusu türü döndüren standart bir yöntem haline getirilebilir.
supercat

12

Kopyalama yapıcıları ve soyut sınıflarla ilgili sorunlarla karşılaşacaksınız. Aşağıdakileri yapmak istediğinizi hayal edin:

abstract class A
{
    public A()
    {
    }

    public A(A ToCopy)
    {
        X = ToCopy.X;
    }
    public int X;
}

class B : A
{
    public B()
    {
    }

    public B(B ToCopy) : base(ToCopy)
    {
        Y = ToCopy.Y;
    }
    public int Y;
}

class C : A
{
    public C()
    {
    }

    public C(C ToCopy)
        : base(ToCopy)
    {
        Z = ToCopy.Z;
    }
    public int Z;
}

class Program
{
    static void Main(string[] args)
    {
        List<A> list = new List<A>();

        B b = new B();
        b.X = 1;
        b.Y = 2;
        list.Add(b);

        C c = new C();
        c.X = 3;
        c.Z = 4;
        list.Add(c);

        List<A> cloneList = new List<A>();

        //Won't work
        //foreach (A a in list)
        //    cloneList.Add(new A(a)); //Not this time batman!

        //Works, but is nasty for anything less contrived than this example.
        foreach (A a in list)
        {
            if(a is B)
                cloneList.Add(new B((B)a));
            if (a is C)
                cloneList.Add(new C((C)a));
        }
    }
}

Yukarıdakileri yaptıktan hemen sonra, bir arayüz kullanacağınızı veya bir DeepCopy () / ICloneable.Clone () uygulamasına karar vereceğinizi dilemeye başlıyorsunuz.


2
Arayüz tabanlı bir yaklaşım için iyi bir argüman.
DanO

4

ICloneable ile ilgili sorun hem niyet hem de tutarlılıktır. Derin veya sığ bir kopya olup olmadığı asla net değildir. Bu nedenle, muhtemelen hiçbir zaman yalnızca bir şekilde kullanılmaz.

Bu konuda daha açık bir halka açık kopya kurucu bulmuyorum.

Bununla birlikte, sizin için çalışan ve amacı aktaran bir yöntem sistemi sunacağım (a'la bir şekilde kendi kendini belgeleyen)


3

Kopyalamaya çalıştığınız nesne Seri hale getirilebilir ise, onu seri hale getirip serisini kaldırarak klonlayabilirsiniz. O zaman her sınıf için bir kopya oluşturucu yazmanıza gerek kalmaz.

Şu anda koda erişimim yok ama bunun gibi bir şey

public object DeepCopy(object source)
{
   // Copy with Binary Serialization if the object supports it
   // If not try copying with XML Serialization
   // If not try copying with Data contract Serailizer, etc
}

6
Derin klonlamanın bir aracı olarak serileştirmenin kullanılması, derin klonun bir ctor veya yöntem olarak ortaya çıkması gerekip gerekmediği sorusuyla ilgisizdir.
Kent Boogaart

1
Bunun başka bir geçerli alternatif olduğunu düşünüyorum. Bu iki derin kopyalama yöntemiyle sınırlı olduğunu düşünmemiştim.
Shaun Bowe

5
@Kent Boogaart - OP'nin "C # içinde, bir sınıfa (derin) kopyalama işlevselliği eklemenin tercih edilen yolu nedir" satırıyla başladığı göz önüne alındığında, Shaun'un farklı alternatifler sunmasının yeterince adil olduğunu düşünüyorum. Özellikle klon işlevini uygulamak istediğiniz çok sayıda sınıfa sahip olduğunuz eski bir senaryoda, bu numara yararlı olabilir; doğrudan kendi klonunuzu uygulamak kadar hafif değil, ancak yine de kullanışlı. İnsanlar sorularıma "hiç düşündün mü?"
Rob Levine

2

Kendinizi geliştirici olarak tanımlamanız gereken söz konusu sınıfın kopyalama anlamlarına bağlıdır. Seçilen yöntem genellikle sınıfın amaçlanan kullanım durumlarına dayanmaktadır. Belki her iki yöntemi de uygulamak mantıklı olacaktır. Ancak her ikisi de benzer dezavantajları paylaşıyor - hangi kopyalama yöntemini uyguladıkları tam olarak belli değil. Bu, sınıfınızın belgelerinde açıkça belirtilmelidir.

Benim için sahip olunan:

// myobj is some transparent proxy object
var state = new ObjectState(myobj.State);

// do something

myobject = GetInstance();
var newState = new ObjectState(myobject.State);

if (!newState.Equals(state))
    throw new Exception();

onun yerine:

// myobj is some transparent proxy object
var state = myobj.State.Clone();

// do something

myobject = GetInstance();
var newState = myobject.State.Clone();

if (!newState.Equals(state))
    throw new Exception();

daha net bir niyet beyanı olarak görünüyordu.


0

Klonlanabilir nesneler için standart bir model olması gerektiğini düşünüyorum, ancak desenin tam olarak ne olması gerektiğinden emin değilim. Klonlama ile ilgili olarak, üç tür sınıf var gibi görünüyor:

  1. Derin klonlamayı açıkça destekleyenler
  2. Üye bazında klonlamanın derin klonlama olarak çalışacağı, ancak açık desteğe sahip olmayan veya buna ihtiyaç duymayanlar.
  3. Yararlı bir şekilde derin klonlanamayanlar ve üyelere göre klonlamanın kötü sonuçlar vereceği yerler.

Anlayabildiğim kadarıyla, mevcut bir nesneyle aynı sınıftan yeni bir nesne almanın tek yolu (en azından .net 2.0'da) MemberwiseClone kullanmaktır. Hoş bir model, "yeni" / "Gölgeler" işlevine sahip gibi görünebilir ve her zaman mevcut türü döndürür, tanımı her zaman MemberwiseClone'u çağırır ve ardından korumalı bir sanal alt yordamı CleanupClone (originalObject) çağırır. CleanupCode rutini, temel türün klonlama ihtiyaçlarını karşılamak için base.Cleanupcode'u çağırmalı ve ardından kendi temizleme işlemini eklemelidir. Klonlama rutininin orijinal nesneyi kullanması gerekiyorsa, typecast olmalıdır, ancak aksi takdirde tek tip yayınlama MemberwiseClone çağrısında olacaktır.

Ne yazık ki, tip (2) yerine yukarıda tip (1) olan en düşük sınıf seviyesinin, daha düşük türlerinin klonlama için herhangi bir açık desteğe ihtiyaç duymayacağını varsaymak için kodlanması gerekecektir. Ben bunun etrafında gerçekten bir yol göremiyorum.

Yine de, belirli bir modele sahip olmanın hiç yoktan daha iyi olacağını düşünüyorum.

Bu arada, birinin temel türünün iCloneable'ı desteklediğini bilir, ancak kullandığı işlevin adını bilmiyorsa, kişinin temel türünün iCloneable.Clone işlevine başvurmanın herhangi bir yolu var mı?


0

Tüm ilginç cevapları ve tartışmaları okursanız, yine de kendinize mülkleri tam olarak nasıl kopyaladığınızı sorabilirsiniz - hepsini açık bir şekilde veya bunu yapmanın daha zarif bir yolu var mı? Geri kalan sorunuz buysa, şuna bir göz atın (StackOverflow'da):

Genel bir uzantı yöntemi kullanarak 3. taraf sınıfların özelliklerini nasıl "derinlemesine" klonlayabilirim?

CreateCopy()Nesnenin tüm özellikleri içeren "derin" bir kopyasını oluşturan bir uzantı yönteminin nasıl uygulanacağını açıklar (özelliği manuel olarak özelliğe göre kopyalamak zorunda kalmadan).

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.