Jenerik ICloneable<T>
olmamasının özel bir nedeni var mı?
Çok daha rahat olurdu, eğer bir şeyi klonladığım her şeyi yapmam gerekmiyorsa.
Jenerik ICloneable<T>
olmamasının özel bir nedeni var mı?
Çok daha rahat olurdu, eğer bir şeyi klonladığım her şeyi yapmam gerekmiyorsa.
Yanıtlar:
ICloneable şu anda kötü bir API olarak kabul edilmektedir, çünkü sonucun derin mi yoksa sığ bir kopya mı olduğunu belirtmemektedir. Bence bu yüzden bu arayüzü geliştirmiyorlar.
Muhtemelen yazılan bir klonlama uzantısı yöntemi yapabilirsiniz, ancak uzantı yöntemleri orijinal olanlardan daha az önceliğe sahip olduğundan farklı bir ad gerektireceğini düşünüyorum.
List<T>
bir klon yöntemi vardı, ben bir verim beklenir List<T>
Öğeleri orijinal listede aynı kimlikleri var, ama ben etkileyecek bir listeye yapılan hiçbir şeyin sağlamak için gerektiği gibi herhangi bir iç veri yapıları çoğaltılamaz olacağını beklenebilir kimlikler öğelerin diğer saklanan. Belirsizlik nerede? Klonlama ile ilgili daha büyük bir sorun, "elmas sorununun" bir varyasyonu ile birlikte gelir: CloneableFoo
[halka açık olarak klonlanamaz] 'dan miras alınırsa Foo
, CloneableDerivedFoo
...
identity
, örneğin listenin kendisini neyi dikkate alacağınız gibi (listelerin listesi durumunda)? Ancak, bunu göz ardı ederek, beklentileriniz insanların arama veya uygulama sırasında sahip olabileceği tek fikir değildir Clone
. Başka bir liste uygulayan kütüphane yazarları beklentilerinizi karşılamazsa ne olur? API önemsiz derecede açık olmalı, tartışmasız açık olmamalıdır.
Clone
tüm parçalarını çağıran bir sınıf yaparsa , bu parçanın sizin tarafınızdan mı yoksa o kişi tarafından mı uygulandığına bağlı olarak tahmin edilemez derin klonlamayı seven örüntüler hakkındaki noktanız geçerlidir, ancak API'de IMHO'ya sahip olmak yeterince açık değildir - ya ShallowCopy
konuyu vurgulamak için çağrılmalı ya da hiç verilmemelidir.
Andrey bu cevabına ek olarak (benim de katıldığım, 1) - zaman ICloneable
olduğu yapılan, ayrıca halka açık hale getirmek açık uygulanmasını seçebilir Clone()
dönüşü Yazılan nesnesi:
public Foo Clone() { /* your code */ }
object ICloneable.Clone() {return Clone();}
Elbette jenerik ile ilgili ikinci bir sorun var ICloneable<T>
- kalıtımla .
Sahip olursam:
public class Foo {}
public class Bar : Foo {}
Ve uyguladım ICloneable<T>
, sonra uygularım ICloneable<Foo>
mı? ICloneable<Bar>
? Hızla birçok özdeş arabirim uygulamaya başlıyorsunuz ... Bir oyuncu kadrosu ile karşılaştırın ... ve gerçekten çok mu kötü?
Sormak zorundayım, arabirimi uygulamak dışında tam olarak ne yapardınız ? Arayüzler tipik olarak sadece ona yayın yaptığınızda (yani, bu sınıf 'IBar'ı destekliyorsa) ya da onu alan parametreler veya ayarlayıcılara sahip olduğunda (yani bir' IBar 'alırım) kullanışlıdır. ICloneable ile - tüm Çerçeveyi inceledik ve uygulanmasından başka bir yerde tek bir kullanım bulamadık. Ayrıca 'gerçek dünyada' onu uygulamaktan başka bir şey yapan herhangi bir kullanım bulamadık (eriştiğimiz ~ 60.000 uygulamada).
Şimdi 'klonlanabilir' nesnelerinizin uygulanmasını istediğiniz bir deseni uygulamak istiyorsanız, bu tamamen iyi bir kullanımdır ve devam edin. Ayrıca "klonlamanın" sizin için ne anlama geldiğine de karar verebilirsiniz (yani derin veya sığ). Ancak, bu durumda, onu (BCL) tanımlamamıza gerek yoktur. BCL'deki soyutlamaları yalnızca ilişkisiz kütüphaneler arasında soyutlama olarak yazılan örneklerin değiş tokuşuna ihtiyaç duyulduğunda tanımlarız.
David Kean (BCL Takımı)
ICloneable<out T>
miras alındığında ISelf<out T>
tek bir Self
tür yöntemle oldukça yararlı olabilir T
. Kişi genellikle "klonlanabilir bir şeye" ihtiyaç duymaz, fakat klonlanabilir bir şeye çok iyi ihtiyaç T
duyabilir. Klonlanabilir bir nesne uygulanırsa ISelf<itsOwnType>
, klonlanabilir olana ihtiyaç duyan bir rutin, ortak bir atayı paylaşabilecek tüm klonlanabilir türevleri olmasa bile bir T
tür parametresini kabul edebilir . ICloneable<T>
T
ICloneable<T>
bunun için yararlı olabilir, ancak paralel değişken ve değişmez sınıfları korumak için daha geniş bir çerçeve daha yararlı olabilir. Başka bir deyişle, ne tür Foo
içerdiğini görmek gerekir , ancak ne onu mutasyona ne de hiç değişmeyeceğini beklemek için IReadableFoo
Foo
bir ImmutableFoo
kodu kullanabilir MutableFoo
. Herhangi bir tür verilen kod IReadableFoo
değişebilir veya değişmez bir versiyona sahip olmalıdır. Böyle bir çerçeve iyi olurdu, ama ne yazık ki işleri genel bir şekilde kurmak için güzel bir yol bulamıyorum. Bir sınıf için salt okunur bir sarıcı yapmanın tutarlı bir yolu olsaydı, böyle bir şey ICloneable<T>
, bir sınıfın değişmez bir kopyasını yapmak için birlikte kullanılabilir T
'.
List<T>
Klonlanmış List<T>
orijinal koleksiyondaki tüm aynı nesnelere işaret eden yeni bir koleksiyon olacak şekilde bir klonlamak istiyorsanız , bunu yapmanın iki kolay yolu vardır ICloneable<T>
. Birincisi, Enumerable.ToList()
eklenti yöntemidir: List<foo> clone = original.ToList();
İkincisi ise bir List<T>
yapıcıdır IEnumerable<T>
: List<foo> clone = new List<foo>(original);
Uzantı yönteminin muhtemelen sadece yapıcıyı çağırdığından şüpheleniyorum, ancak bunların her ikisi de istediğinizi yapacak. ;)
"Neden" sorusunun gereksiz olduğunu düşünüyorum. Çok yararlı olan, ancak .NET Frameworku temel kitaplığının bir parçası olmayan çok sayıda arabirim / sınıf / vb. Var.
Ancak, esas olarak bunu kendiniz yapabilirsiniz.
public interface ICloneable<T> : ICloneable {
new T Clone();
}
public abstract class CloneableBase<T> : ICloneable<T> where T : CloneableBase<T> {
public abstract T Clone();
object ICloneable.Clone() { return this.Clone(); }
}
public abstract class CloneableExBase<T> : CloneableBase<T> where T : CloneableExBase<T> {
protected abstract T CreateClone();
protected abstract void FillClone( T clone );
public override T Clone() {
T clone = this.CreateClone();
if ( object.ReferenceEquals( clone, null ) ) { throw new NullReferenceException( "Clone was not created." ); }
return clone
}
}
public abstract class PersonBase<T> : CloneableExBase<T> where T : PersonBase<T> {
public string Name { get; set; }
protected override void FillClone( T clone ) {
clone.Name = this.Name;
}
}
public sealed class Person : PersonBase<Person> {
protected override Person CreateClone() { return new Person(); }
}
public abstract class EmployeeBase<T> : PersonBase<T> where T : EmployeeBase<T> {
public string Department { get; set; }
protected override void FillClone( T clone ) {
base.FillClone( clone );
clone.Department = this.Department;
}
}
public sealed class Employee : EmployeeBase<Employee> {
protected override Employee CreateClone() { return new Employee(); }
}
İhtiyacınız olursa arayüzü kendiniz yazmak oldukça kolaydır :
public interface ICloneable<T> : ICloneable
where T : ICloneable<T>
{
new T Clone();
}
Son zamanlarda neden bir nesne kopyalamak yapmak korkunç bir şey? , Bu sorunun ek onaylanmaya ihtiyacı olduğunu düşünüyorum. Buradaki diğer cevaplar iyi tavsiyeler veriyor, ancak yine de cevap tam değil - neden hayır ICloneable<T>
?
kullanım
Yani, onu uygulayan bir sınıfınız var. Önceden istediğiniz bir yönteminiz olsa da ICloneable
, şimdi kabul etmek için genel olması gerekir ICloneable<T>
. Düzenlemeniz gerekir.
Sonra, bir nesnenin olup olmadığını kontrol eden bir yöntem olabilir is ICloneable
. Şimdi ne var? Yapamazsınis ICloneable<>
ve derleme türü de nesnenin türünü bilmiyorum, sen yöntem jenerik yapamazsınız. İlk gerçek sorun.
Yani her ikisine ICloneable<T>
ve ICloneable
ikincisine sahip olmanız gerekir . Böylece bir uygulayıcısı her iki yöntemi uygulamak gerekir - object Clone()
ve T Clone()
. Hayır, teşekkürler, zaten yeterince eğleniyoruz IEnumerable
.
Daha önce de belirtildiği gibi, kalıtımın karmaşıklığı da vardır. Kovaryans bu sorunu çözmüş gibi görünse de, türetilmiş bir türün ICloneable<T>
kendi türünde uygulanması gerekir , ancak Clone()
temel sınıfın aynı imzasına (= parametreler, temelde) sahip bir yöntem zaten vardır . Yeni klon yöntemi arayüzünüzü açık yapmak anlamsızdır, oluştururken aradığınız avantajı kaybedersiniz ICloneable<T>
. new
Anahtar kelimeyi ekleyin . Ancak, temel sınıfı geçersiz kılmanız gerektiğini de unutmayın Clone()
(uygulama, türetilmiş tüm sınıflar için tekdüze kalmalıdır, yani aynı nesneyi her klon yönteminden döndürmek için temel klon yönteminin olması gerekir virtual
)! Ama ne yazık ki, ikisini birden yapamazsınız override
venew
aynı imzalı yöntemler. İlk anahtar kelimeyi seçtiğinizde, eklerken olmasını istediğiniz hedefi kaybedersinizICloneable<T>
. İkincisini seçerek, aynı dönüşü farklı nesneler yapması gereken yöntemler yaparak arayüzün kendisini kırarsınız.
Nokta
İstediğiniz ICloneable<T>
Konfor için , ancak konfor arayüzlerin tasarlandığı şey değildir, anlamları (genel olarak OOP) nesnelerin davranışını birleştirmektir (C # 'da, dış davranışı birleştirmekle sınırlıdır, örneğin yöntemler ve özellikler, onların çalışmaları).
İlk neden sizi ikna ICloneable<T>
etmediyse, klon yönteminden döndürülen türü sınırlamak için kısıtlayıcı olarak da çalışabilecek itiraz edebilirsiniz. Bununla birlikte, kötü programcı ICloneable<T>
T'nin onu uygulayan tip olmadığı durumlarda uygulayabilir. Bu nedenle, kısıtlamanıza ulaşmak için, genel parametreye hoş bir kısıtlama ekleyebilirsiniz:
public interface ICloneable<T> : ICloneable where T : ICloneable<T>
Kesinlikle daha kısıtlayıcı olan, where
yine de T'nin arabirimi uygulayan tür olduğunu kısıtlayamazsınız (ICloneable<T>
(farklı türden uygular).
Görüyorsunuz, bu amaca ulaşılamamıştır (orijinal ICloneable
de bu konuda başarısız olur, hiçbir arabirim gerçekleyen sınıfın davranışını gerçekten sınırlayamaz).
Gördüğünüz gibi, bu genel arayüzün hem tamamen uygulanması zor hem de gerçekten gereksiz ve işe yaramaz hale geldiğini kanıtlıyor.
Ancak soruya geri dönersek, gerçekten aradığınız şey, bir nesneyi klonlarken rahatlık sağlamaktır. Bunu yapmanın iki yolu vardır:
public class Base : ICloneable
{
public Base Clone()
{
return this.CloneImpl() as Base;
}
object ICloneable.Clone()
{
return this.CloneImpl();
}
protected virtual object CloneImpl()
{
return new Base();
}
}
public class Derived : Base
{
public new Derived Clone()
{
return this.CloneImpl() as Derived;
}
protected override object CloneImpl()
{
return new Derived();
}
}
Bu çözüm, kullanıcılara hem konfor hem de amaçlanan davranış sağlar, ancak uygulanması çok uzun. Mevcut türü döndüren "rahat" yönteme sahip olmak istemiyorsak, sadece sahip olmak çok daha kolaydır public virtual object Clone()
.
Öyleyse "nihai" çözümü görelim - C # 'da bize rahatlık sağlamak için gerçekten ne amaçlanıyor?
public class Base : ICloneable
{
public virtual object Clone()
{
return new Base();
}
}
public class Derived : Base
{
public override object Clone()
{
return new Derived();
}
}
public static T Copy<T>(this T obj) where T : class, ICloneable
{
return obj.Clone() as T;
}
Geçerli Klon yöntemleriyle çakışmayacak şekilde Kopyala olarak adlandırılır (derleyici, türün kendi uzantı yöntemlerine göre bildirilen yöntemlerini tercih eder). Kısıt hızı için orada (vs. kontrol boş gerektirmez) 'dir.class
Umarım bu yapmamanın nedenini açıklar ICloneable<T>
. Ancak, hiç uygulamanız tavsiye edilmez ICloneable
.
ICloneable
, Klon yönteminin boksunu atlatabileceği ve kutunun değerinin sizin elinizde olduğu anlamına gelen değer türleri içindir. Ve yapılar otomatik olarak klonlanabildiğinden (sığ), onu uygulamaya gerek yoktur (derin kopya anlamına gelmediği sürece).
Soru çok eski olmasına rağmen (bu cevapları yazdıktan 5 yıl sonra) ve zaten cevaplanmıştı, ancak bu makalenin soruyu oldukça iyi yanıtladığını buldum, buradan kontrol edin
DÜZENLE:
İşte soruyu cevaplayan makaleden alıntı (makalenin tamamını okuduğunuzdan emin olun, diğer ilginç şeyleri içerir):
İnternette Brad Abrams'ın - Microsoft'ta çalıştığı sırada - 2003'te ICloneable hakkında bazı düşüncelerin tartışıldığı bir blog yayınına işaret eden birçok referans var. Blog girişi şu adreste bulunabilir: ICloneable uygulanması . Yanıltıcı başlığa rağmen, bu blog girişi, esasen sığ / derin karışıklık nedeniyle ICloneable uygulamamayı çağırıyor. Makale doğrudan bir öneriyle bitiyor: Bir klonlama mekanizmasına ihtiyacınız varsa, kendi Klonunuzu veya Kopyalama yönteminizi tanımlayın ve derin ya da sığ bir kopya olup olmadığını açıkça belgelediğinizden emin olun. Uygun bir örüntü:
public <type> Copy();
Büyük bir sorun, T'nin aynı sınıf olmasını kısıtlayamamalarıdır. Örneğin, bunu yapmanıza engel olan şey:
interface IClonable<T>
{
T Clone();
}
class Dog : IClonable<JackRabbit>
{
//not what you would expect, but possible
JackRabbit Clone()
{
return new JackRabbit();
}
}
Aşağıdaki gibi bir parametre kısıtlamasına ihtiyaç duyarlar:
interfact IClonable<T> where T : implementing_type
class A : ICloneable { public object Clone() { return 1; } /* I can return whatever I want */ }
ICloneable<T>
kısıtlasa bile T
, bu, bir uygulamanın Clone()
, klonlandığı nesneye uzaktan benzeyen bir şeyi geri döndürmeye zorlamaz. Ayrıca, eğer bir arabirim kovaryansı kullanılıyorsa, ICloneable
mühürlenmesi gereken sınıflara sahip olmak, arabirimin kendisini geri döndürmesi beklenen ICloneable<out T>
bir Self
özellik içermesi en iyisi olabilir ve ...
ICloneable<BaseType>
ya ICloneable<ICloneable<BaseType>>
. Söz BaseType
konusu soru, protected
klonlama için, uygulayan tip tarafından çağrılacak bir yönteme sahip olmalıdır ICloneable
. Bu tasarım, a Container
, a CloneableContainer
, a FancyContainer
ve a CloneableFancyContainer
, ikincisinin klonlanabilir bir türevi Container
gerektiren veya gerektiren bir FancyContainer
(ancak klonlanabilir olup olmadığı umurumda değil) kodunda kullanılabilir olmasını isteyebilir.
FancyList
mantıklı bir şekilde klonlanabilen bir türe sahip olabilir, ancak bir türev otomatik olarak durumunu bir disk dosyasında (yapıcıda belirtilen) devam ettirebilir. Türetilmiş tür klonlanamadı, çünkü durumu değiştirilebilir bir tekil dosyaya (dosyaya) eklenecekti, ancak bu, türünün çoğu özelliğine ihtiyaç duyan FancyList
ancak ihtiyaç duymayacak yerlerde türetilmiş türün kullanılmasını engellememelidir. klonlamak için.
Bu çok iyi bir soru ... Yine de kendi sorularınızı yapabilirsiniz:
interface ICloneable<T> : ICloneable
{
new T Clone ( );
}
Andrey, kötü bir API olarak kabul edildiğini söylüyor, ancak bu arayüzün kullanımdan kaldırılmasıyla ilgili bir şey duymadım. Ve bu tonlarca arayüze zarar verir ... Klon yöntemi sığ bir kopya yapmalıdır. Nesne ayrıca derin kopya sağlarsa, aşırı yüklenmiş bir Klon (bool deep) kullanılabilir.
EDIT: bir nesneyi "klonlamak" için kullandığım desen yapıcıda bir prototip geçiriyor.
class C
{
public C ( C prototype )
{
...
}
}
Bu, olası gereksiz kod uygulama durumlarını kaldırır. BTW, ICloneable sınırlamalarından bahsetmişken, sığ bir klon veya derin klonun, hatta kısmen sığ / kısmen derin bir klonun yapılmasına karar vermek gerçekten nesnenin kendisine bağlı değil mi? Nesne amaçlandığı gibi çalıştığı sürece gerçekten önemsemeli miyiz? Bazı durumlarda, iyi bir Klon uygulaması hem sığ hem de derin klonlamayı içerebilir.