Apakah ada alasan khusus mengapa obat generik ICloneable<T>
tidak ada?
Akan jauh lebih nyaman, jika saya tidak perlu membuangnya setiap kali saya mengkloning sesuatu.
Apakah ada alasan khusus mengapa obat generik ICloneable<T>
tidak ada?
Akan jauh lebih nyaman, jika saya tidak perlu membuangnya setiap kali saya mengkloning sesuatu.
Jawaban:
ICloneable dianggap sebagai API yang buruk sekarang, karena tidak menentukan apakah hasilnya adalah salinan yang dalam atau dangkal. Saya pikir ini sebabnya mereka tidak meningkatkan antarmuka ini.
Anda mungkin dapat melakukan metode ekstensi kloning yang diketik, tetapi saya pikir itu akan membutuhkan nama yang berbeda karena metode ekstensi memiliki prioritas yang kurang dari yang asli.
List<T>
memiliki metode kloning, saya akan berharap untuk menghasilkan List<T>
item yang memiliki identitas yang sama dengan yang ada di daftar asli, tetapi saya berharap bahwa setiap struktur data internal akan digandakan sesuai kebutuhan untuk memastikan bahwa tidak ada yang dilakukan pada satu daftar akan mempengaruhi yang identitas item disimpan dalam lainnya. Di mana ambiguitasnya? Masalah yang lebih besar dengan kloning datang dengan variasi dari "masalah berlian": jika CloneableFoo
mewarisi dari [tidak dapat dikloning secara publik] Foo
, harus CloneableDerivedFoo
berasal dari ...
identity
dari daftar itu sendiri, misalnya (dalam hal daftar daftar)? Namun, mengabaikan itu, harapan Anda bukan satu-satunya ide yang mungkin dimiliki orang ketika menelepon atau mengimplementasikan Clone
. Bagaimana jika penulis perpustakaan yang menerapkan beberapa daftar lain tidak mengikuti harapan Anda? API harus sepele ambigu, tidak bisa dibilang ambigu.
Clone
semua bagiannya, itu tidak akan bisa diprediksi - tergantung pada apakah bagian itu dilaksanakan oleh Anda atau orang itu yang suka kloning dalam. poin Anda tentang pola adalah valid tetapi memiliki IMHO di API tidak cukup jelas - itu harus dipanggil ShallowCopy
untuk menekankan titik, atau tidak disediakan sama sekali.
Selain balasan Andrey (yang saya setuju dengan, 1) - ketika ICloneable
sedang dilakukan, Anda dapat juga memilih implementasi eksplisit untuk membuat masyarakat Clone()
kembali objek diketik:
public Foo Clone() { /* your code */ }
object ICloneable.Clone() {return Clone();}
Tentu saja ada masalah kedua dengan generik ICloneable<T>
- warisan.
Jika saya punya:
public class Foo {}
public class Bar : Foo {}
Dan saya menerapkan ICloneable<T>
, lalu apakah saya mengimplementasikan ICloneable<Foo>
? ICloneable<Bar>
? Anda dengan cepat mulai mengimplementasikan banyak antarmuka identik ... Bandingkan dengan para pemain ... dan apakah itu benar-benar buruk?
Saya perlu bertanya, apa yang akan Anda lakukan dengan antarmuka selain mengimplementasikannya? Antarmuka biasanya hanya berguna ketika Anda menggunakan itu (yaitu apakah kelas ini mendukung 'IBar'), atau memiliki parameter atau setter yang mengambilnya (yaitu saya mengambil 'IBar'). Dengan ICloneable - kami menelusuri seluruh Kerangka dan gagal menemukan satu penggunaan di mana pun yang merupakan sesuatu selain implementasi. Kami juga gagal menemukan penggunaan apa pun di 'dunia nyata' yang juga melakukan sesuatu selain menerapkannya (di ~ 60.000 aplikasi yang kami akses).
Sekarang jika Anda hanya ingin menerapkan pola yang Anda ingin objek 'cloneable' Anda untuk menerapkan, itu penggunaan yang benar-benar baik - dan teruskan. Anda juga dapat memutuskan apa arti "kloning" bagi Anda (yaitu dalam atau dangkal). Namun, dalam hal ini, tidak perlu bagi kami (BCL) untuk mendefinisikannya. Kami hanya mendefinisikan abstraksi dalam BCL ketika ada kebutuhan untuk bertukar instance yang diketik sebagai abstraksi antara pustaka yang tidak terkait.
David Kean (Tim BCL)
ICloneable<out T>
bisa sangat berguna jika diwarisi dari ISelf<out T>
, dengan metode Self
tipe tunggal T
. Seseorang tidak sering membutuhkan "sesuatu yang dapat dikloning", tetapi orang mungkin sangat membutuhkan sesuatu T
yang dapat dikloning. Jika objek cloneable mengimplementasikan ISelf<itsOwnType>
, rutin yang membutuhkan T
cloneable dapat menerima parameter tipe ICloneable<T>
, bahkan jika tidak semua turunan cloneable T
berbagi nenek moyang yang sama.
ICloneable<T>
bisa bermanfaat untuk itu, meskipun kerangka kerja yang lebih luas untuk mempertahankan kelas paralel yang tidak berubah dan tetap mungkin lebih bermanfaat. Dengan kata lain, kode yang perlu untuk melihat apa yang Foo
mengandung beberapa jenis tetapi tidak akan bermutasi atau berharap bahwa itu tidak akan pernah berubah dapat menggunakan IReadableFoo
, sementara ...
Foo
dapat menggunakan ImmutableFoo
kode sementara yang ingin dimanipulasi dapat menggunakan a MutableFoo
. Kode yang diberikan jenis apa pun IReadableFoo
harus bisa mendapatkan versi yang bisa berubah atau tidak berubah. Kerangka kerja seperti itu akan bagus, tapi sayangnya saya tidak dapat menemukan cara yang bagus untuk mengatur semuanya dengan cara yang umum. Jika ada cara yang konsisten untuk membuat pembungkus read-only untuk sebuah kelas, hal seperti itu dapat digunakan bersama-sama ICloneable<T>
dengan membuat salinan kelas yang tidak dapat diubah yang berisi kelas T
'.
List<T>
, sedemikian rupa sehingga yang dikloning List<T>
adalah penunjuk koleksi penunjuk baru untuk semua objek yang sama dalam koleksi asli, ada dua cara mudah untuk melakukannya tanpa ICloneable<T>
. Yang pertama adalah Enumerable.ToList()
metode ekstensi: List<foo> clone = original.ToList();
Yang kedua adalah List<T>
konstruktor yang mengambil IEnumerable<T>
: List<foo> clone = new List<foo>(original);
Saya menduga metode ekstensi mungkin hanya memanggil konstruktor, tetapi keduanya akan melakukan apa yang Anda minta. ;)
Saya pikir pertanyaan "mengapa" tidak perlu. Ada banyak antarmuka / kelas / dll ... yang sangat berguna, tetapi bukan bagian dari .NET Frameworku base library.
Tapi, terutama Anda bisa melakukannya sendiri.
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(); }
}
Cukup mudah untuk menulis antarmuka sendiri jika Anda membutuhkannya:
public interface ICloneable<T> : ICloneable
where T : ICloneable<T>
{
new T Clone();
}
Baru-baru ini membaca artikel Mengapa Menyalin Objek adalah hal yang mengerikan untuk dilakukan? , Saya pikir pertanyaan ini perlu clafirication tambahan. Jawaban lain di sini memberikan saran yang bagus, tapi tetap saja jawabannya tidak lengkap - mengapa tidak ICloneable<T>
?
Pemakaian
Jadi, Anda memiliki kelas yang mengimplementasikannya. Walaupun sebelumnya Anda memiliki metode yang diinginkan ICloneable
, sekarang harus generik untuk menerimanya ICloneable<T>
. Anda perlu mengeditnya.
Kemudian, Anda bisa mendapatkan metode yang memeriksa apakah suatu objek is ICloneable
. Apa sekarang? Anda tidak dapat melakukan is ICloneable<>
dan karena Anda tidak tahu jenis objek di tipe kompilasi, Anda tidak dapat membuat metode ini generik. Masalah nyata pertama.
Jadi, Anda harus memiliki keduanya ICloneable<T>
dan ICloneable
, yang pertama mengimplementasikan yang terakhir. Jadi pelaksana perlu mengimplementasikan kedua metode - object Clone()
dan T Clone()
. Tidak, terima kasih, kami sudah cukup bersenang-senang IEnumerable
.
Seperti yang telah ditunjukkan, ada juga kompleksitas warisan. Sementara kovarians tampaknya dapat mengatasi masalah ini, tipe turunan perlu mengimplementasikan ICloneable<T>
tipenya sendiri, tetapi sudah ada metode dengan tanda tangan yang sama (= parameter, pada dasarnya) - Clone()
kelas dasar. Menjadikan antarmuka metode klon baru Anda menjadi tidak ada gunanya, Anda akan kehilangan keuntungan yang Anda cari saat membuat ICloneable<T>
. Jadi, tambahkan new
kata kunci. Tetapi jangan lupa bahwa Anda juga perlu menimpa kelas dasar ' Clone()
(implementasi harus tetap seragam untuk semua kelas turunan, yaitu untuk mengembalikan objek yang sama dari setiap metode klon, sehingga metode klon dasar harus virtual
)! Namun, sayangnya, Anda tidak dapat keduanya override
dannew
metode dengan tanda tangan yang sama. Memilih kata kunci pertama, Anda akan kehilangan tujuan yang ingin Anda miliki saat menambahkan ICloneable<T>
. Memilih yang kedua, Anda akan merusak antarmuka itu sendiri, membuat metode yang harus melakukan hal yang sama mengembalikan objek yang berbeda.
Titik
Anda ingin ICloneable<T>
kenyamanan, tetapi kenyamanan bukan untuk apa antarmuka dirancang, maknanya adalah (secara umum OOP) untuk menyatukan perilaku objek (meskipun dalam C #, itu terbatas untuk menyatukan perilaku luar, misalnya metode dan properti, tidak pekerjaan mereka).
Jika alasan pertama belum meyakinkan Anda, Anda bisa keberatan yang ICloneable<T>
juga bisa bekerja secara terbatas, untuk membatasi jenis yang dikembalikan dari metode klon. Namun, programmer jahat dapat mengimplementasikan ICloneable<T>
mana T bukan tipe yang mengimplementasikannya. Jadi, untuk mencapai batasan Anda, Anda dapat menambahkan batasan yang bagus pada parameter generik:
public interface ICloneable<T> : ICloneable where T : ICloneable<T>
Tentu saja lebih ketat daripada yang tidak where
, Anda masih tidak dapat membatasi bahwa T adalah tipe yang mengimplementasikan antarmuka (Anda dapat berasal dari ICloneable<T>
jenis yang berbeda yang mengimplementasikannya).
Anda lihat, bahkan tujuan ini tidak dapat dicapai (aslinya ICloneable
juga gagal pada ini, tidak ada antarmuka yang benar-benar dapat membatasi perilaku kelas pelaksana).
Seperti yang Anda lihat, ini membuktikan membuat antarmuka generik sulit untuk sepenuhnya diimplementasikan dan juga benar-benar tidak dibutuhkan dan tidak berguna.
Tetapi kembali ke pertanyaan, apa yang sebenarnya Anda cari adalah mendapatkan kenyamanan saat mengkloning suatu objek. Ada dua cara untuk melakukannya:
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();
}
}
Solusi ini memberikan kenyamanan dan perilaku yang ditujukan kepada pengguna, tetapi juga terlalu lama untuk diterapkan. Jika kita tidak ingin memiliki metode "nyaman" untuk mengembalikan tipe saat ini, itu jauh lebih mudah untuk dilakukan public virtual object Clone()
.
Jadi mari kita lihat solusi "ultimate" - apa yang ada di C # sebenarnya dimaksudkan untuk memberi kita kenyamanan?
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;
}
Itu bernama Salin untuk tidak bertabrakan dengan metode Klon saat ini (kompiler lebih suka metode yang dideklarasikan jenis sendiri daripada yang ekstensi). The class
kendala yang ada untuk kecepatan (tidak memerlukan nol memeriksa dll).
Saya harap ini menjelaskan alasan mengapa tidak membuat ICloneable<T>
. Namun, disarankan untuk tidak menerapkan ICloneable
sama sekali.
ICloneable
adalah untuk tipe nilai, di mana ia bisa menghindari tinju metode Klon, dan itu menyiratkan Anda memiliki nilai yang tidak dikotakkan. Dan karena struktur dapat dikloning (dangkal) secara otomatis, tidak perlu untuk mengimplementasikannya (kecuali jika Anda membuatnya spesifik bahwa itu berarti salinan yang dalam).
Meskipun pertanyaannya sudah sangat lama (5 tahun sejak menulis jawaban ini :) dan sudah dijawab, tetapi saya menemukan artikel ini menjawab pertanyaan dengan cukup baik, periksa di sini
EDIT:
Berikut adalah kutipan dari artikel yang menjawab pertanyaan (pastikan untuk membaca artikel selengkapnya, termasuk hal-hal menarik lainnya):
Ada banyak referensi di Internet yang menunjuk pada posting blog 2003 oleh Brad Abrams - pada saat itu dipekerjakan di Microsoft - di mana beberapa pemikiran tentang ICloneable dibahas. Entri blog dapat ditemukan di alamat ini: Menerapkan ICloneable . Meskipun judulnya menyesatkan, entri blog ini meminta untuk tidak menerapkan ICloneable, terutama karena kebingungan yang dangkal / dalam. Artikel berakhir dengan saran langsung: Jika Anda memerlukan mekanisme kloning, tentukan Klon Anda sendiri, atau salin metodologi, dan pastikan bahwa Anda mendokumentasikan dengan jelas apakah itu salinan yang dalam atau dangkal. Pola yang sesuai adalah:
public <type> Copy();
Masalah besar adalah mereka tidak bisa membatasi T untuk menjadi kelas yang sama. Contoh sebelumnya apa yang akan mencegah Anda melakukan ini:
interface IClonable<T>
{
T Clone();
}
class Dog : IClonable<JackRabbit>
{
//not what you would expect, but possible
JackRabbit Clone()
{
return new JackRabbit();
}
}
Mereka membutuhkan batasan parameter seperti:
interfact IClonable<T> where T : implementing_type
class A : ICloneable { public object Clone() { return 1; } /* I can return whatever I want */ }
ICloneable<T>
bisa membatasi T
untuk mencocokkan tipenya sendiri, itu tidak akan memaksa implementasi Clone()
untuk mengembalikan apa pun yang menyerupai objek yang dikloning. Lebih lanjut, saya akan menyarankan bahwa jika seseorang menggunakan antarmuka kovarians, mungkin yang terbaik untuk memiliki kelas yang mengimplementasikan ICloneable
disegel, memiliki antarmuka ICloneable<out T>
termasuk Self
properti yang diharapkan untuk mengembalikan sendiri, dan ...
ICloneable<BaseType>
atau ICloneable<ICloneable<BaseType>>
. The BaseType
bersangkutan harus memiliki protected
metode untuk kloning, yang akan disebut oleh jenis yang mengimplementasikan ICloneable
. Desain ini akan memungkinkan untuk kemungkinan seseorang ingin memiliki a Container
, a CloneableContainer
, a FancyContainer
, dan a CloneableFancyContainer
, yang terakhir dapat digunakan dalam kode yang memerlukan turunan klon Container
atau yang membutuhkan FancyContainer
(tetapi tidak peduli apakah itu dapat diklon).
FancyList
tipe yang dapat dikloning secara masuk akal, tetapi turunannya secara otomatis tetap berada dalam file disk (ditentukan dalam konstruktor). Tipe turunan tidak dapat dikloning, karena kondisinya akan melekat pada singleton yang dapat berubah (file), tetapi itu tidak boleh menghalangi penggunaan tipe turunan di tempat-tempat yang membutuhkan sebagian besar fitur FancyList
tetapi tidak perlu untuk mengkloningnya.
Ini pertanyaan yang sangat bagus ... Anda bisa membuatnya sendiri:
interface ICloneable<T> : ICloneable
{
new T Clone ( );
}
Andrey mengatakan itu dianggap API yang buruk, tetapi saya belum mendengar apa-apa tentang antarmuka ini menjadi usang. Dan itu akan memecah banyak antarmuka ... Metode Clone harus melakukan salinan dangkal. Jika objek juga menyediakan salinan dalam, Clone yang kelebihan beban (kedalaman bool) dapat digunakan.
EDIT: Pola yang saya gunakan untuk "kloning" suatu objek, melewati prototipe di konstruktor.
class C
{
public C ( C prototype )
{
...
}
}
Ini menghilangkan kemungkinan situasi implementasi kode redundan. BTW, berbicara tentang keterbatasan ICloneable, bukankah itu benar-benar tergantung pada objek itu sendiri untuk memutuskan apakah klon dangkal atau klon dalam, atau bahkan sebagian klon dangkal / sebagian dalam, harus dilakukan? Haruskah kita benar-benar peduli, selama benda itu berfungsi sebagaimana dimaksud? Dalam beberapa kesempatan, implementasi Klon yang baik mungkin termasuk kloning yang dangkal dan dalam.