Mengapa tidak ada ICloneable <T>?

222

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.

Bluenuance
sumber
6
Tidak! dengan segala hormat pada 'alasan', saya setuju dengan Anda, mereka seharusnya menerapkannya!
Shimmy Weitzhandler
Ini akan menjadi hal yang baik bagi Microsoft untuk mendefinisikan (masalah dengan antarmuka buatan sendiri adalah bahwa antarmuka dalam dua majelis yang berbeda akan tidak kompatibel, bahkan jika mereka identik secara semantik). Jika saya mendesain antarmuka, itu akan memiliki tiga anggota, Clone, Self, dan CloneIfMutable, yang semuanya akan mengembalikan T (anggota terakhir akan mengembalikan Clone atau Self, yang sesuai). Anggota Mandiri akan memungkinkan untuk menerima ICloneable (dari Foo) sebagai parameter dan kemudian menggunakannya sebagai Foo, tanpa perlu typecast.
supercat
Itu akan memungkinkan untuk hierarki kelas kloning yang tepat, di mana kelas yang diwariskan mengekspos metode "klon" yang dilindungi, dan telah menyegel derivatif yang mengekspos metode publik. Sebagai contoh, seseorang dapat memiliki Pile, CloneablePile: Pile, EnhancedPile: Pile, dan CloneableEnhancedPile: EnhancedPile, tidak ada yang akan rusak jika dikloning (walaupun tidak semua memperlihatkan metode kloning publik), dan Selanjutnya Diperbaiki: EnhancedPile (yang akan rusak) jika dikloning, tetapi tidak mengekspos metode kloning apa pun). Rutin yang menerima ICloneable (Pile) dapat menerima CloneablePile atau CloneableEnhancedPile ...
supercat
... meskipun CloneableEnhancedPile tidak mewarisi dari CloneablePile. Perhatikan bahwa jika EnhancedPile diwarisi dari CloneablePile, FurtherEnhancedPile harus memaparkan metode kloning publik dan dapat diteruskan ke kode yang akan mengkloningnya, melanggar Prinsip Pengganti Liskov. Karena CloneableEnhancedPile akan mengimplementasikan ICloneable (Of EnhancedPile) dan dengan implikasi ICloneable (Of Pile), itu dapat diteruskan ke rutin mengharapkan turunan Pile yang dapat dikloning.
supercat

Jawaban:

111

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.

Andrey Shchekin
sumber
8
Saya tidak setuju, buruk atau tidak buruk, kadang-kadang sangat berguna untuk clallning SHALLOW, dan dalam kasus-kasus itu benar-benar diperlukan Klon <T>, untuk menyimpan tinju dan kotak yang tidak perlu.
Shimmy Weitzhandler
2
@AndreyShchekin: Apa yang tidak jelas tentang kloning mendalam vs dangkal? Jika 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 CloneableFoomewarisi dari [tidak dapat dikloning secara publik] Foo, harus CloneableDerivedFooberasal dari ...
supercat
1
@ supercat: Saya tidak bisa mengatakan saya mengerti harapan Anda sepenuhnya, seperti apa yang akan Anda anggap sebagai identitydari 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.
Andrey Shchekin
2
@supercat Jadi Anda berbicara tentang salinan dangkal. Namun, pada dasarnya tidak ada yang salah dengan salinan dalam, misalnya jika Anda ingin melakukan transaksi reversibel pada objek dalam memori. Harapan Anda didasarkan pada kasus penggunaan Anda, orang lain mungkin memiliki harapan yang berbeda. Jika mereka memasukkan objek Anda ke objek mereka (atau sebaliknya), hasilnya akan tak terduga.
Andrey Shchekin
5
@supercat Anda tidak memperhitungkannya adalah bagian dari .NET framework, bukan bagian dari aplikasi Anda. jadi jika Anda membuat kelas yang tidak melakukan kloning dalam, maka orang lain membuat kelas yang melakukan kloning dalam dan memanggil Clonesemua 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 ShallowCopyuntuk menekankan titik, atau tidak disediakan sama sekali.
Andrey Shchekin
141

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?

Marc Gravell
sumber
10
+1 Saya selalu berpikir bahwa masalah kovarians adalah alasan sebenarnya
Tamas Czinege
Ada masalah dengan ini: implementasi antarmuka eksplisit harus pribadi , yang akan menyebabkan masalah harus Foo di beberapa titik perlu dilemparkan ke ICloneable ... Apakah saya kehilangan sesuatu di sini?
Joel di Go
3
Kesalahan saya: ternyata, meskipun didefinisikan sebagai pribadi, metode ini akan dipanggil jika Foo dilemparkan ke IClonable (CLR via C # 2nd Ed, hal.319). Sepertinya keputusan desain yang aneh, tapi itu dia. Jadi Foo.Clone () memberikan metode pertama, dan ((ICloneable) Foo) .Clone () memberikan metode kedua.
Joel di Go
Sebenarnya, implementasi antarmuka eksplisit, sebenarnya, adalah bagian dari API publik. Dalam hal mereka dapat dihubungi secara publik melalui casting.
Marc Gravell
8
Solusi yang diusulkan kelihatannya bagus pada awalnya ... sampai Anda menyadari bahwa dalam hierarki kelas, Anda ingin 'public Foo Clone ()' menjadi virtual dan ditimpa dalam kelas turunan sehingga mereka dapat mengembalikan tipenya sendiri (dan mengkloning bidangnya sendiri). tentu saja). Sayangnya, Anda tidak dapat mengubah jenis pengembalian dalam penggantian (masalah kovarian disebutkan). Jadi, Anda kembali lagi ke masalah semula - implementasi eksplisit tidak benar-benar memberi Anda banyak (selain mengembalikan tipe dasar hierarki Anda alih-alih 'objek'), dan Anda kembali untuk casting sebagian besar dari Anda Klon () hasil panggilan. Huek!
Ken Beckett
18

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)

David Kean
sumber
1
ICloneable non-generik tidak terlalu berguna, tetapi ICloneable<out T>bisa sangat berguna jika diwarisi dari ISelf<out T>, dengan metode Selftipe tunggal T. Seseorang tidak sering membutuhkan "sesuatu yang dapat dikloning", tetapi orang mungkin sangat membutuhkan sesuatu Tyang dapat dikloning. Jika objek cloneable mengimplementasikan ISelf<itsOwnType>, rutin yang membutuhkan Tcloneable dapat menerima parameter tipe ICloneable<T>, bahkan jika tidak semua turunan cloneable Tberbagi nenek moyang yang sama.
supercat
Terima kasih. Itu mirip dengan situasi pemeran yang saya sebutkan di atas (yaitu 'apakah kelas ini mendukung' IBar '). Sayangnya, kami hanya membuat skenario yang sangat terbatas dan terisolasi di mana Anda benar-benar memanfaatkan fakta bahwa T dapat dikloning. Apakah Anda memiliki situasi dalam pikiran?
David Kean
Satu hal yang kadang-kadang canggung di .net adalah mengelola koleksi yang bisa berubah ketika konten koleksi seharusnya dilihat sebagai nilai (yaitu saya ingin tahu nanti set item yang ada dalam koleksi sekarang). Sesuatu seperti 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 Foomengandung beberapa jenis tetapi tidak akan bermutasi atau berharap bahwa itu tidak akan pernah berubah dapat menggunakan IReadableFoo, sementara ...
supercat
... kode yang ingin menampung konten Foodapat menggunakan ImmutableFookode sementara yang ingin dimanipulasi dapat menggunakan a MutableFoo. Kode yang diberikan jenis apa pun IReadableFooharus 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'.
supercat
@supercat Jika Anda ingin mengkloning 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. ;)
CptRobby
12

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(); }
}
TcK
sumber
Setiap metode klon yang bisa diterapkan untuk kelas yang diwariskan harus menggunakan Object.MemberwiseClone sebagai titik awal (atau menggunakan refleksi) karena jika tidak, tidak ada jaminan bahwa klon akan menjadi tipe yang sama dengan objek asli. Saya memiliki pola yang cukup bagus jika Anda tertarik.
supercat
@supercat mengapa tidak menjawabnya?
nawfal
"Ada banyak antarmuka / kelas / dll ... yang sangat berguna, tetapi bukan bagian dari pustaka dasar .NET Frameworku." Bisakah Anda memberi kami contoh?
Krythic
1
@Krythic yaitu: struktur data lanjutan seperti b-tree, red-black tree, circle buffer. Dalam '09 tidak ada tupel, referensi lemah umum, koleksi bersamaan ...
TcKs
10

Cukup mudah untuk menulis antarmuka sendiri jika Anda membutuhkannya:

public interface ICloneable<T> : ICloneable
        where T : ICloneable<T>
{
    new T Clone();
}
Mauricio Scheffer
sumber
Saya memasukkan cuplikan karena demi kehormatan hak cipta, tautannya terputus ...
Shimmy Weitzhandler
2
Dan bagaimana sebenarnya antarmuka itu berfungsi? Untuk hasil operasi klon agar dapat dicetak untuk tipe T, objek yang dikloning harus berasal dari T. Tidak ada cara untuk mengatur batasan tipe seperti itu. Secara realistis, satu-satunya hal yang dapat saya lihat hasil pengembalian iCloneable adalah jenis iCloneable.
supercat
@supercat: ya, itulah yang dikembalikan oleh Clone (): huruf T yang mengimplementasikan ICloneable <T>. MbUnit telah menggunakan antarmuka ini selama bertahun - tahun , jadi ya, itu berfungsi. Sedangkan untuk implementasinya, intip sumber MbUnit.
Mauricio Scheffer
2
@Mauricio Scheffer: Saya mengerti apa yang terjadi. Tidak ada tipe yang mengimplementasikan iCloneable (dari T). Sebaliknya, setiap jenis mengimplementasikan iCloneable (dari dirinya sendiri). Itu akan berhasil, saya kira, meskipun yang dilakukannya hanyalah memindahkan typecast. Sayang sekali tidak ada cara untuk mendeklarasikan bidang sebagai memiliki batasan warisan dan antarmuka; akan sangat membantu untuk memiliki metode kloning mengembalikan objek dari tipe dasar semi-cloneable (kloning terkena hanya metode Protected), tetapi dengan kendala tipe cloneable. Itu akan memungkinkan objek untuk menerima turunan klon dari turunan semi-klon dari jenis dasar.
supercat
@Mauricio Scheffer: BTW, saya menyarankan bahwa ICloneable (dari T) juga mendukung properti Self yang dapat dibaca (dari tipe pengembalian T). Ini akan memungkinkan metode untuk menerima ICloneable (dari Foo) dan menggunakannya (melalui properti Self) sebagai Foo.
supercat
9

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>?

  1. 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 newkata 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 overridedannewmetode 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.

  2. 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 ICloneablejuga 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

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?

Metode ekstensi!

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 classkendala yang ada untuk kecepatan (tidak memerlukan nol memeriksa dll).

Saya harap ini menjelaskan alasan mengapa tidak membuat ICloneable<T>. Namun, disarankan untuk tidak menerapkan ICloneablesama sekali.

IllidanS4 ingin Monica kembali
sumber
Lampiran # 1: Satu-satunya kemungkinan penggunaan generik ICloneableadalah 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).
IllidanS4 ingin Monica kembali
2

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

Sameh Deabes
sumber
1

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
sheamus
sumber
8
Itu tidak tampak seburuk class A : ICloneable { public object Clone() { return 1; } /* I can return whatever I want */ }
Nikola Novak
Saya tidak benar-benar melihat apa masalahnya. Tidak ada antarmuka yang dapat memaksa implementasi untuk berperilaku wajar. Bahkan jika ICloneable<T>bisa membatasi Tuntuk 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 ICloneabledisegel, memiliki antarmuka ICloneable<out T>termasuk Selfproperti yang diharapkan untuk mengembalikan sendiri, dan ...
supercat
... minta konsumen memberikan atau membatasi ICloneable<BaseType>atau ICloneable<ICloneable<BaseType>>. The BaseTypebersangkutan harus memiliki protectedmetode 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 Containeratau yang membutuhkan FancyContainer(tetapi tidak peduli apakah itu dapat diklon).
supercat
Alasan saya mendukung desain seperti itu adalah bahwa masalah apakah suatu tipe dapat dikloning dengan masuk akal agak ortogonal dengan aspek-aspek lain dari itu. Sebagai contoh, seseorang mungkin memiliki FancyListtipe 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 FancyListtetapi tidak perlu untuk mengkloningnya.
supercat
+1 - jawaban ini adalah satu-satunya dari yang saya lihat di sini yang benar-benar menjawab pertanyaan.
IllidanS4 ingin Monica kembali
0

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.

baretta
sumber
"Buruk" tidak berarti usang. Ini hanya berarti "Anda lebih baik tidak menggunakannya". Itu tidak ditinggalkan dalam arti bahwa "kami akan menghapus antarmuka ICloneable di masa depan". Mereka hanya mendorong Anda untuk tidak menggunakannya, dan tidak akan memperbaruinya dengan obat generik atau fitur baru lainnya.
jalf
Dennis: Lihat jawaban Marc. Masalah kovarian / pewarisan membuat antarmuka ini cukup tidak dapat digunakan untuk skenario apa pun yang setidaknya sedikit rumit.
Tamas Czinege
Ya, saya bisa melihat keterbatasan ICloneable, pasti ... Saya jarang perlu menggunakan kloning.
baretta