Apa sebenarnya “Kelas Khusus” itu?

114

Setelah gagal mendapatkan sesuatu seperti berikut untuk dikompilasi:

public class Gen<T> where T : System.Array
{
}

dengan kesalahan

Batasan tidak dapat berupa kelas khusus 'System.Array'

Aku mulai bertanya-tanya, apa sebenarnya adalah sebuah "kelas khusus"?

Orang-orang sering kali mendapatkan jenis kesalahan yang sama saat mereka menentukan System.Enumdalam batasan umum. Saya mendapat hasil yang sama dengan System.Object, System.Delegate, System.MulticastDelegatedan System.ValueTypejuga.

Apakah masih ada lagi? Saya tidak dapat menemukan info tentang "kelas khusus" di C #.

Juga, apa yang istimewa dari kelas-kelas itu sehingga kita tidak dapat menggunakannya sebagai batasan tipe umum?

Mints97
sumber
14
Menurut saya ini bukan duplikat langsung. Pertanyaannya bukanlah "mengapa saya tidak bisa menggunakan ini sebagai batasan", ini adalah "apa kelas-kelas khusus ini". Saya telah melihat pertanyaan-pertanyaan itu dan mereka hanya menyatakan mengapa tidak ada gunanya digunakan sebagai batasan, tidak menjelaskan apa sebenarnya "kelas khusus" dan mengapa itu dianggap istimewa.
Adam Houldsworth
2
Dalam pengalaman saya, kelas yang digunakan tetapi Anda tidak dapat menggunakannya secara langsung, hanya secara implisit melalui sintaks lain, adalah kelas khusus. Enum termasuk dalam kategori yang sama. Apa sebenarnya yang membuat mereka istimewa, saya tidak tahu.
Lasse V. Karlsen
@AndyKorneyev: pertanyaan itu agak berbeda. Saya meminta definisi "kelas khusus" dan / atau daftar lengkapnya. Pertanyaan itu hanya menanyakan alasan System.Array tidak bisa menjadi batasan tipe generik.
Mints97
Dari dokumentasi itu menyatakan "[...] hanya sistem dan kompiler yang dapat memperoleh secara eksplisit dari kelas Array.". Kemungkinan inilah yang membuatnya menjadi kelas khusus - ini diperlakukan secara khusus oleh kompiler.
RB.
1
@RB .: salah. Logika ini berarti System.Objectadalah bukan "kelas khusus", karena ini adalah valid: public class X : System.Object { }, tapi System.Objectmasih "kelas khusus".
Mints97

Jawaban:

106

Dari kode sumber Roslyn, ini terlihat seperti daftar tipe hardcode:

switch (type.SpecialType)
{
    case SpecialType.System_Object:
    case SpecialType.System_ValueType:
    case SpecialType.System_Enum:
    case SpecialType.System_Delegate:
    case SpecialType.System_MulticastDelegate:
    case SpecialType.System_Array:
        // "Constraint cannot be special class '{0}'"
        Error(diagnostics, ErrorCode.ERR_SpecialTypeAsBound, syntax, type);
        return false;
}

Sumber: Binder_Constraints.cs IsValidConstraintType
Saya telah menemukannya menggunakan pencarian GitHub: "Batasan tidak bisa menjadi kelas khusus"

Kobi
sumber
1
@kobi 702 menjadi kesalahan kompilator CS0702, seperti yang terlihat pada keluaran kompilator (yang pertanyaan ini diabaikan kutipannya) dan jawaban lainnya.
AakashM
1
@AakashM - Terima kasih! Saya mencoba mengkompilasi dan tidak mendapatkan nomor kesalahan, karena alasan tertentu. Kemudian saya membutuhkan waktu hampir 5 menit untuk mengetahuinya, dan tidak memiliki cukup waktu untuk mengedit komentar saya. Cerita sedih.
Kobi
1
@Kobi: Anda harus melihat output -window, di sana Anda menemukan nomor kode kesalahan compiler yang tepat CS0702.
Tim Schmelter
9
Jadi sekarang pertanyaan sebenarnya adalah mengapa kelas khusus ini?
David mengatakan Reinstate Monica
@DavidGrinberg Mungkin alasannya adalah Anda tidak dapat mewarisi dari tipe ini secara langsung (kecuali untuk object), atau setidaknya ada hubungannya dengan itu. Juga where T : Arrayakan memungkinkan lulus Assay sebagai T, yang mungkin bukan yang diinginkan kebanyakan orang.
IllidanS4 ingin Monica kembali
42

Saya menemukan komentar Jon Skeet dari tahun 2008 tentang pertanyaan serupa: Mengapa System.Enumbatasan tidak didukung.

Saya tahu ini sedikit keluar dari topik , tetapi dia bertanya kepada Eric Lippert (tim C #) tentang hal itu dan mereka memberikan jawaban ini:

Pertama, dugaan Anda benar; pembatasan pada batasan pada umumnya artefak bahasa, bukan CLR. (Jika kami melakukan fitur-fitur ini, akan ada beberapa hal kecil yang ingin kami ubah di CLR mengenai bagaimana jenis enumerable ditentukan, tetapi sebagian besar ini akan menjadi pekerjaan bahasa.)

Kedua, secara pribadi saya ingin memiliki batasan delegasi, batasan enum, dan kemampuan untuk menentukan batasan yang ilegal saat ini karena kompilator mencoba menyelamatkan Anda dari diri Anda sendiri. (Artinya, membuat jenis tersegel legal sebagai kendala, dan seterusnya.)

Namun, karena pembatasan penjadwalan, kami mungkin tidak dapat memasukkan fitur-fitur ini ke versi bahasa berikutnya.

Amir Popovich
sumber
10
@YuvalItzchakov - Apakah mengutip Github \ MSDN lebih baik? Tim C # telah memberikan jawaban konkret terkait masalah tersebut atau yang serupa..Tidak bisa menyakiti siapa pun. Jon Skeet baru saja mengutipnya dan cukup dapat diandalkan dalam hal C # ..
Amir Popovich
5
Tidak perlu kesal. Saya tidak bermaksud ini bukan jawaban yang valid :) Hanya berbagi pemikiran saya tentang yayasan yang jonskeet; p
Yuval Itzchakov
40
FYI BTW Saya pikir itu saya yang Anda kutip di sana. :-)
Eric Lippert
2
@EricLippert - Itu membuat kutipan itu semakin dapat diandalkan.
Amir Popovich
Domain link di jawaban sudah mati.
Pang
25

Menurut MSDN , ini adalah daftar kelas statis:

Kesalahan Penyusun CS0702

Batasan tidak dapat menjadi 'pengenal' kelas khusus. Jenis berikut tidak dapat digunakan sebagai pembatas:

  • System.Object
  • System.Array
  • System.Delegasikan
  • System.Enum
  • System.ValueType.
Tim Schmelter
sumber
4
Keren, sepertinya jawaban yang benar, temuan bagus! Tapi System.MulticastDelegatedi manakah daftarnya?
Mints97
8
@ Mints97: tidak tahu, mungkin kurangnya dokumentasi?
Tim Schmelter
Sepertinya Anda juga tidak bisa mewarisi dari kelas-kelas ini.
David Klempfner
14

Sesuai C # 4.0 Spesifikasi Bahasa (Berkode: [10.1.5] Batasan parameter tipe) memberi tahu dua hal:

1] Jenisnya tidak boleh berupa objek. Karena semua tipe berasal dari objek, batasan seperti itu tidak akan berpengaruh jika diizinkan.

2] Jika T tidak memiliki batasan primer atau batasan parameter tipe, kelas dasar efektifnya adalah objek.

Saat Anda mendefinisikan kelas generik, Anda bisa menerapkan batasan pada jenis tipe yang dapat digunakan kode klien untuk argumen tipe saat membuat instance kelas Anda. Jika kode klien mencoba untuk membuat instance kelas Anda dengan menggunakan tipe yang tidak diperbolehkan oleh batasan, hasilnya adalah kesalahan waktu kompilasi. Pembatasan ini disebut kendala. Batasan ditentukan dengan menggunakan kata kunci kontekstual where. Jika Anda ingin membatasi tipe generik menjadi tipe referensi, gunakan: class.

public class Gen<T> where T : class
{
}

Ini akan melarang tipe generik menjadi tipe nilai, seperti int atau struct, dll.

Selain itu, Batasan tidak dapat menjadi 'pengenal' kelas khusus. Jenis berikut tidak dapat digunakan sebagai pembatas:

  • System.Object
  • System.Array
  • System.Delegasikan
  • System.Enum
  • System.ValueType.
Rahul Nikate
sumber
12

Ada kelas-kelas tertentu dalam Framework yang secara efektif meneruskan karakteristik khusus ke semua tipe yang diturunkan darinya tetapi tidak memiliki karakteristik itu sendiri . CLR sendiri tidak memberlakukan larangan untuk menggunakan kelas-kelas tersebut sebagai batasan, tetapi tipe generik yang dibatasi padanya tidak akan memperoleh karakteristik yang tidak diturunkan seperti tipe konkret. Pencipta C # memutuskan bahwa karena perilaku seperti itu mungkin membingungkan sebagian orang, dan mereka gagal melihat kegunaannya, mereka harus melarang batasan tersebut daripada membiarkan mereka berperilaku seperti yang mereka lakukan di CLR.

Jika, misalnya, seseorang diizinkan untuk menulis void CopyArray<T>(T dest, T source, int start, int count):; seseorang akan dapat melewatkan destdan sourceke metode yang mengharapkan sebuah argumen bertipe System.Array; selanjutnya, seseorang akan mendapatkan validasi waktu kompilasi itu destdan sourcemerupakan tipe array yang kompatibel, tetapi seseorang tidak akan bisa mengakses elemen dari array menggunakan []operator.

Ketidakmampuan untuk menggunakan Arraysebagai kendala sebagian besar cukup mudah untuk dikerjakan, karena void CopyArray<T>(T[] dest, T[] source, int start, int count)akan bekerja di hampir semua situasi di mana metode sebelumnya akan bekerja. Namun, ia memiliki kelemahan: metode sebelumnya akan bekerja dalam skenario bahwa salah satu atau kedua argumen bertipe System.Arraysementara menolak kasus di mana argumen merupakan jenis array yang tidak kompatibel; menambahkan overload di mana kedua argumen memiliki tipe System.Arrayakan membuat kode menerima kasus tambahan yang harus diterimanya, tetapi juga membuatnya salah menerima kasus yang seharusnya tidak diterima.

Menurut saya keputusan untuk melarang sebagian besar batasan khusus menjengkelkan. Satu-satunya yang memiliki makna semantik nol adalah System.Object[karena jika itu legal sebagai batasan, apa pun akan memuaskannya]. System.ValueTypemungkin tidak akan terlalu berguna, karena referensi tipe ValueTypetidak memiliki banyak kesamaan dengan tipe nilai, tetapi mungkin masuk akal memiliki beberapa nilai dalam kasus yang melibatkan Refleksi. Keduanya System.Enumdan System.Delegateakan memiliki kegunaan nyata, tetapi karena pencipta C # tidak memikirkannya, mereka dilarang tanpa alasan yang jelas.

supercat
sumber
10

Berikut ini dapat ditemukan di CLR melalui C # 4th Edition:

Kendala Utama

Parameter tipe dapat menentukan batasan utama nol atau satu batasan utama. Batasan utama bisa menjadi tipe referensi yang mengidentifikasi kelas yang tidak disegel. Anda tidak dapat menentukan salah satu dari jenis referensi khusus berikut: System.Object , System.Array , System.Delegate , System.MulticastDelegate , System.ValueType , System.Enum , atau System.Void . Saat menentukan batasan tipe referensi, Anda menjanjikan compiler bahwa argumen tipe yang ditentukan akan berjenis sama atau tipe turunan dari tipe batasan.

Claudio P
sumber
Lihat juga: C # LS Bagian 10.1.4.1: Kelas dasar langsung dari jenis kelas tidak harus salah satu dari jenis berikut: System.Array, System.Delegate, System.MulticastDelegate, System.Enum, atau System.ValueType. Selanjutnya, deklarasi kelas generik tidak dapat digunakan System.Attributesebagai kelas dasar langsung atau tidak langsung.
Jeroen Vannevel
5

Saya tidak berpikir, bahwa ada definisi resmi dari "kelas khusus" / "tipe khusus".

Anda mungkin menganggapnya sebagai jenis, yang tidak dapat digunakan dengan semantik jenis "biasa":

  • Anda tidak dapat membuat instance secara langsung;
  • Anda tidak dapat mewarisi tipe kustom dari mereka secara langsung;
  • ada beberapa kompiler ajaib untuk bekerja dengannya (opsional);
  • penggunaan langsung dari contoh mereka setidaknya tidak berguna (opsional; bayangkan, bahwa Anda telah membuat generik di atas, kode generik apa yang akan Anda tulis?)

PS Saya akan menambahkan System.Voidke daftar.

Dennis
sumber
2
System.Voidmemberikan kesalahan yang sama sekali berbeda bila digunakan sebagai batasan umum =)
Mints97
@ Mints97: benar. Tetapi jika pertanyaannya tentang "khusus", maka ya, voidsangat istimewa. :)
Dennis
@ Dennis: Kode yang memiliki beberapa parameter dari tipe yang dibatasi untuk System.Arraydapat menggunakan metode seperti Array.Copymemindahkan data dari satu ke yang lain; kode dengan parameter dari tipe yang dibatasi System.Delegateakan dapat digunakan Delegate.Combinepadanya dan menampilkan hasilnya ke tipe yang tepat . Memanfaatkan jenis yang diketahui umum secara efektif Enumakan menggunakan Refleksi satu kali untuk setiap jenis tersebut, tetapi HasAnyFlagmetode umum bisa 10x lebih cepat daripada metode non-umum.
supercat