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.
c#
.net
icloneable
Bluenuance
sumber
sumber
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.
sumber
List<T>
memiliki metode kloning, saya akan berharap untuk menghasilkanList<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": jikaCloneableFoo
mewarisi dari [tidak dapat dikloning secara publik]Foo
, harusCloneableDerivedFoo
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 mengimplementasikanClone
. 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 dipanggilShallowCopy
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 masyarakatClone()
kembali objek diketik:Tentu saja ada masalah kedua dengan generik
ICloneable<T>
- warisan.Jika saya punya:
Dan saya menerapkan
ICloneable<T>
, lalu apakah saya mengimplementasikanICloneable<Foo>
?ICloneable<Bar>
? Anda dengan cepat mulai mengimplementasikan banyak antarmuka identik ... Bandingkan dengan para pemain ... dan apakah itu benar-benar buruk?sumber
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)
sumber
ICloneable<out T>
bisa sangat berguna jika diwarisi dariISelf<out T>
, dengan metodeSelf
tipe tunggalT
. Seseorang tidak sering membutuhkan "sesuatu yang dapat dikloning", tetapi orang mungkin sangat membutuhkan sesuatuT
yang dapat dikloning. Jika objek cloneable mengimplementasikanISelf<itsOwnType>
, rutin yang membutuhkanT
cloneable dapat menerima parameter tipeICloneable<T>
, bahkan jika tidak semua turunan cloneableT
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 yangFoo
mengandung beberapa jenis tetapi tidak akan bermutasi atau berharap bahwa itu tidak akan pernah berubah dapat menggunakanIReadableFoo
, sementara ...Foo
dapat menggunakanImmutableFoo
kode sementara yang ingin dimanipulasi dapat menggunakan aMutableFoo
. Kode yang diberikan jenis apa punIReadableFoo
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-samaICloneable<T>
dengan membuat salinan kelas yang tidak dapat diubah yang berisi kelasT
'.List<T>
, sedemikian rupa sehingga yang dikloningList<T>
adalah penunjuk koleksi penunjuk baru untuk semua objek yang sama dalam koleksi asli, ada dua cara mudah untuk melakukannya tanpaICloneable<T>
. Yang pertama adalahEnumerable.ToList()
metode ekstensi:List<foo> clone = original.ToList();
Yang kedua adalahList<T>
konstruktor yang mengambilIEnumerable<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.
sumber
Cukup mudah untuk menulis antarmuka sendiri jika Anda membutuhkannya:
sumber
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 menerimanyaICloneable<T>
. Anda perlu mengeditnya.Kemudian, Anda bisa mendapatkan metode yang memeriksa apakah suatu objek
is ICloneable
. Apa sekarang? Anda tidak dapat melakukanis 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>
danICloneable
, yang pertama mengimplementasikan yang terakhir. Jadi pelaksana perlu mengimplementasikan kedua metode -object Clone()
danT Clone()
. Tidak, terima kasih, kami sudah cukup bersenang-senangIEnumerable
.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 membuatICloneable<T>
. Jadi, tambahkannew
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 harusvirtual
)! Namun, sayangnya, Anda tidak dapat keduanyaoverride
dannew
metode dengan tanda tangan yang sama. Memilih kata kunci pertama, Anda akan kehilangan tujuan yang ingin Anda miliki saat menambahkanICloneable<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 mengimplementasikanICloneable<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 dariICloneable<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:
Metode tambahan
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?
Metode ekstensi!
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 menerapkanICloneable
sama sekali.sumber
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):
sumber
Masalah besar adalah mereka tidak bisa membatasi T untuk menjadi kelas yang sama. Contoh sebelumnya apa yang akan mencegah Anda melakukan ini:
Mereka membutuhkan batasan parameter seperti:
sumber
class A : ICloneable { public object Clone() { return 1; } /* I can return whatever I want */ }
ICloneable<T>
bisa membatasiT
untuk mencocokkan tipenya sendiri, itu tidak akan memaksa implementasiClone()
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 mengimplementasikanICloneable
disegel, memiliki antarmukaICloneable<out T>
termasukSelf
properti yang diharapkan untuk mengembalikan sendiri, dan ...ICloneable<BaseType>
atauICloneable<ICloneable<BaseType>>
. TheBaseType
bersangkutan harus memilikiprotected
metode untuk kloning, yang akan disebut oleh jenis yang mengimplementasikanICloneable
. Desain ini akan memungkinkan untuk kemungkinan seseorang ingin memiliki aContainer
, aCloneableContainer
, aFancyContainer
, dan aCloneableFancyContainer
, yang terakhir dapat digunakan dalam kode yang memerlukan turunan klonContainer
atau yang membutuhkanFancyContainer
(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 fiturFancyList
tetapi tidak perlu untuk mengkloningnya.Ini pertanyaan yang sangat bagus ... Anda bisa membuatnya sendiri:
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.
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.
sumber