Pengecualian terbaik untuk argumen tipe umum yang tidak valid

106

Saat ini saya sedang menulis beberapa kode untuk UnconstrainedMelody yang memiliki metode umum untuk dilakukan dengan enum.

Sekarang, saya memiliki kelas statis dengan banyak metode yang hanya dimaksudkan untuk digunakan dengan enum "flags". Saya tidak dapat menambahkan ini sebagai pembatas ... jadi mungkin saja mereka akan dipanggil dengan jenis enum lain juga. Dalam hal ini, saya ingin memberikan pengecualian, tetapi saya tidak yakin mana yang harus dibuang.

Hanya untuk membuat ini menjadi konkret, jika saya memiliki sesuatu seperti ini:

// Returns a value with all bits set by any values
public static T GetBitMask<T>() where T : struct, IEnumConstraint
{
    if (!IsFlags<T>()) // This method doesn't throw
    {
        throw new ???
    }
    // Normal work here
}

Apa pengecualian terbaik untuk dilempar? ArgumentExceptionterdengar logis, tapi ini adalah tipe argumen daripada argumen normal, yang dapat dengan mudah membingungkan banyak hal. Haruskah saya memperkenalkan TypeArgumentExceptionkelas saya sendiri ? Gunakan InvalidOperationException? NotSupportedException? Ada yang lain?

Saya lebih suka tidak membuat pengecualian saya sendiri untuk ini kecuali hal ini jelas merupakan hal yang benar untuk dilakukan.

Jon Skeet
sumber
Saya menemukan ini hari ini dalam menulis metode umum di mana persyaratan tambahan ditempatkan pada jenis yang digunakan yang tidak dapat dijelaskan dengan batasan. Saya terkejut tidak menemukan jenis pengecualian yang sudah ada di BCL. Tetapi dilema yang tepat ini adalah yang juga saya hadapi beberapa hari yang lalu dalam proyek yang sama untuk generik yang hanya akan bekerja dengan atribut Flags. Menyeramkan!
Andras Zoltan

Jawaban:

46

NotSupportedException kedengarannya cocok, tetapi dokumentasinya dengan jelas menyatakan bahwa itu harus digunakan untuk tujuan yang berbeda. Dari catatan kelas MSDN:

Ada metode yang tidak didukung di kelas dasar, dengan harapan bahwa metode ini akan diterapkan di kelas turunan. Kelas turunan mungkin hanya mengimplementasikan subset metode dari kelas dasar, dan melontarkan NotSupportedException untuk metode yang tidak didukung.

Tentu saja, ada cara yang NotSupportedExceptionjelas-jelas cukup bagus, terutama mengingat arti yang masuk akal. Karena itu, saya tidak yakin apakah itu tepat.

Mengingat tujuan Melodi Tanpa Batas ...

Ada berbagai hal berguna yang bisa dilakukan dengan metode / kelas umum di mana ada batasan tipe "T: enum" atau "T: delegate" - tapi sayangnya, hal itu dilarang di C #.

Pustaka utilitas ini bekerja di sekitar larangan menggunakan ildasm / ilasm ...

... sepertinya yang baru Exceptionmungkin beres meskipun beban pembuktiannya tinggi yang harus kita penuhi sebelum membuat kebiasaan Exceptions. Sesuatu seperti InvalidTypeParameterExceptionmungkin berguna di seluruh perpustakaan (atau mungkin tidak - ini pasti kasus tepi, kan?).

Apakah klien harus dapat membedakan ini dari Pengecualian BCL? Kapan klien tidak sengaja memanggil ini menggunakan vanilla enum? Bagaimana Anda akan menjawab pertanyaan yang diajukan oleh jawaban yang diterima untuk Faktor apa yang harus dipertimbangkan saat menulis kelas pengecualian khusus?

Jeff Sternal
sumber
Nyatanya, sangat menggoda untuk membuat pengecualian khusus internal di tempat pertama, dengan cara yang sama seperti yang dilakukan Kontrak Kode ... Saya tidak percaya siapa pun harus menangkapnya.
Jon Skeet
Sayang sekali itu tidak bisa mengembalikan nol!
Jeff Sternal
25
Saya menggunakan TypeArgumentException.
Jon Skeet
Menambahkan pengecualian ke Kerangka kerja mungkin memiliki "beban pembuktian" yang tinggi, tetapi tidak seharusnya menentukan pengecualian khusus. Hal-hal seperti InvalidOperationExceptionmenjijikkan, karena "Foo meminta Bilah Pengumpul untuk menambahkan sesuatu yang sudah ada, jadi Bilah melempar IOE" dan "Foo meminta Bilah Pengumpul untuk menambahkan sesuatu, jadi Bar memanggil Boz yang melempar IOE meskipun Bar tidak mengharapkannya" keduanya akan menampilkan tipe pengecualian yang sama; kode yang mengharapkan untuk menangkap yang pertama tidak akan mengharapkan yang terakhir. Yang telah dikatakan ...
supercat
... Saya pikir argumen yang mendukung pengecualian Kerangka di sini lebih menarik daripada pengecualian khusus. Sifat umum NSE adalah ketika referensi ke suatu objek sebagai tipe umum, dan beberapa tetapi tidak semua tipe objek tertentu yang titik referensi akan mendukung kemampuan, mencoba menggunakan kemampuan pada tipe tertentu yang tidak Tidak mendukung itu harus membuang NSE. Saya akan menganggap a Foo<T>sebagai "tipe umum", dan Foo<Bar>menjadi "tipe khusus" dalam konteks itu, meskipun tidak ada hubungan "warisan" di antara mereka.
supercat
24

Saya akan menghindari NotSupportedException. Pengecualian ini digunakan dalam kerangka kerja di mana metode tidak diimplementasikan dan terdapat properti yang menunjukkan bahwa jenis operasi ini tidak didukung. Tidak muat di sini

Menurut saya, InvalidOperationException adalah pengecualian paling tepat yang dapat Anda berikan di sini.

JaredPar
sumber
Terima kasih atas informasi sebelumnya tentang NSE. Akan menerima masukan dari kolega Anda juga, btw ...
Jon Skeet
Intinya adalah, fungsionalitas yang dibutuhkan Jon tidak ada yang serupa di BCL. Kompiler seharusnya menangkapnya. Jika Anda menghapus persyaratan "properti" dari NotSupportedException, hal-hal yang Anda sebutkan (seperti koleksi ReadOnly) adalah hal yang paling mendekati masalah Jon.
Mehrdad Afshari
Satu titik - Saya memiliki metode IsFlags (itu harus menjadi metode untuk menjadi generik) yang merupakan semacam dari yang menunjukkan bahwa jenis operasi tidak didukung ... sehingga dalam arti bahwa NSE akan sesuai. yaitu penelepon dapat memeriksa terlebih dahulu.
Jon Skeet
@ Jon: Saya pikir bahkan jika Anda tidak memiliki properti seperti tetapi semua anggota dari jenis Anda inheren bergantung pada kenyataan bahwa Tadalah enumdihiasi dengan Flags, itu akan berlaku untuk membuang NSE.
Mehrdad Afshari
1
@ Jon: StupidClrExceptionmembuat nama yang menyenangkan;)
Mehrdad Afshari
13

Pemrograman generik tidak boleh melempar pada waktu proses untuk parameter tipe yang tidak valid. Seharusnya tidak dikompilasi, Anda harus memiliki penegakan waktu kompilasi. Saya tidak tahu IsFlag<T>()isinya, tapi mungkin Anda bisa mengubahnya menjadi penegakan waktu kompilasi, seperti mencoba membuat tipe yang hanya mungkin dibuat dengan 'flags'. Mungkin traitskelas bisa membantu.

Memperbarui

Jika Anda harus melempar, saya akan memilih InvalidOperationException. Alasannya adalah bahwa tipe generik memiliki parameter dan kesalahan yang terkait dengan parameter (metode) yang berpusat di sekitar hierarki ArgumentException. Namun, rekomendasi pada ArgumentException menyatakan itu

jika kegagalan tidak melibatkan argumen itu sendiri, maka InvalidOperationException harus digunakan.

Setidaknya ada satu lompatan keyakinan di sana, bahwa rekomendasi parameter metode juga akan diterapkan ke parameter umum , tetapi tidak ada yang lebih baik dalam hierachy imho SystemException.

Remus Rusanu
sumber
1
Tidak, ini tidak mungkin dibatasi pada waktu kompilasi. IsFlag<T>menentukan apakah enum telah [FlagsAttribute]diterapkan padanya, dan CLR tidak memiliki batasan berdasarkan atribut. Itu akan terjadi di dunia yang sempurna - atau akan ada cara lain untuk membatasinya - tetapi dalam kasus ini itu tidak berhasil :(
Jon Skeet
(+1 untuk prinsip umum - Saya ingin sekali bisa membatasinya.)
Jon Skeet
9

Saya akan menggunakan NotSupportedException karena itulah yang Anda katakan. Enum selain yang spesifik tidak didukung . Ini tentu saja akan dinyatakan dengan lebih jelas dalam pesan pengecualian.

Robban
sumber
2
NotSupportedException digunakan untuk tujuan yang sangat berbeda di BCL. Tidak muat di sini. blogs.msdn.com/jaredpar/archive/2008/12/12/…
JaredPar
8

Saya akan pergi dengan NotSupportedException. Meskipun ArgumentExceptionterlihat bagus, itu benar-benar diharapkan ketika argumen yang diteruskan ke suatu metode tidak dapat diterima. Argumen tipe adalah karakteristik yang menentukan untuk metode sebenarnya yang ingin Anda panggil, bukan "argumen" yang sebenarnya. InvalidOperationExceptionharus dibuang ketika operasi yang Anda lakukan bisa valid dalam beberapa kasus, tetapi untuk situasi tertentu, itu tidak dapat diterima.

NotSupportedExceptiondilempar saat operasi tidak didukung secara inheren. Misalnya, saat mengimplementasikan antarmuka di mana anggota tertentu tidak masuk akal untuk sebuah kelas. Ini terlihat seperti situasi yang serupa.

Mehrdad Afshari
sumber
Mmm. Masih tidak cukup merasa benar, tapi saya pikir itu akan menjadi hal yang paling dekat untuk itu.
Jon Skeet
Jon: Rasanya tidak enak karena secara alami kami mengharapkannya ditangkap oleh kompilator.
Mehrdad Afshari
Ya. Ini adalah jenis kendala aneh yang ingin saya terapkan tetapi tidak bisa :)
Jon Skeet
6

Rupanya, Microsoft menggunakan ArgumentExceptionuntuk itu, seperti yang ditunjukkan pada contoh Expression.Lambda <> , Enum.TryParse <> atau Marshal.GetDelegateForFunctionPointer <> di bagian Pengecualian. Saya tidak dapat menemukan contoh yang menunjukkan sebaliknya, baik (meskipun mencari sumber referensi lokal untuk TDelegatedan TEnum).

Jadi, saya pikir aman untuk mengasumsikan bahwa setidaknya dalam kode Microsoft itu adalah praktik umum yang digunakan ArgumentExceptionuntuk argumen tipe generik yang tidak valid selain dari yang variabel dasar. Mengingat bahwa deskripsi pengecualian di dokumen tidak membedakan keduanya, itu juga tidak terlalu berlebihan.

Mudah-mudahan ini memutuskan pertanyaan itu sekali dan untuk selamanya.

Alice
sumber
Sebuah contoh tunggal dalam rangka tidak cukup bagi saya, tidak ada - mengingat jumlah tempat di mana saya pikir MS telah membuat pilihan yang buruk dalam kasus lain :) Saya tidak akan berasal TypeArgumentExceptiondari ArgumentException, hanya karena jenis argumen tidak biasa argumen.
Jon Skeet
1
Itu pasti lebih menarik dalam hal "itulah yang dilakukan MS secara konsisten". Tidak membuatnya lebih menarik dalam hal pencocokan dokumentasi ... dan saya tahu ada banyak orang di tim C # yang sangat peduli tentang perbedaan antara argumen biasa dan argumen tipe :) Tapi terima kasih atas contohnya - mereka sangat membantu.
Jon Skeet
@ Jon Skeet: Mengedit; sekarang ini termasuk 3 contoh dari perpustakaan MS yang berbeda, semua dengan ArgumentException didokumentasikan sebagai yang dilempar; jadi jika itu adalah pilihan yang buruk, setidaknya itu adalah pilihan buruk yang konsisten. ;) Saya kira Microsoft mengasumsikan bahwa argumen biasa dan argumen tipe keduanya adalah argumen; dan secara pribadi, menurut saya asumsi tersebut cukup masuk akal. ^^ '
Alice
Ah, sudahlah, sepertinya Anda sudah menyadarinya. Senang saya bisa membantu. ^^
Alice
Saya pikir kita harus setuju untuk tidak setuju apakah masuk akal untuk memperlakukan mereka sama. Mereka tentunya tidak sama dalam hal refleksi, atau aturan bahasa, dll ... mereka ditangani dengan sangat berbeda.
Jon Skeet
3

Saya akan menggunakan NotSupportedExpcetion.

Carl Bergquist
sumber
2

Melempar pengecualian yang dibuat khusus harus selalu dilakukan dalam kasus apa pun yang meragukan. Pengecualian khusus akan selalu berfungsi, apa pun kebutuhan pengguna API. Pengembang dapat menangkap salah satu tipe pengecualian jika dia tidak peduli, tetapi jika pengembang membutuhkan penanganan khusus dia akan menjadi SOL.

Eric Schneider
sumber
Juga pengembang harus mendokumentasikan semua pengecualian yang dilemparkan dalam komentar XML.
Eric Schneider
1

Bagaimana jika mewarisi dari NotSupportedException. Meskipun saya setuju dengan @Mehrdad bahwa ini paling masuk akal, saya dengar pendapat Anda bahwa ini sepertinya tidak sesuai dengan sempurna. Jadi, mewarisi dari NotSupportedException, dan dengan begitu orang yang melakukan pengkodean terhadap API Anda masih bisa menangkap NotSupportedException.

BFree
sumber
1

Saya selalu berhati-hati dalam menulis pengecualian khusus, semata-mata karena pengecualian tersebut tidak selalu didokumentasikan dengan jelas dan menyebabkan kebingungan jika tidak dinamai dengan benar.

Dalam hal ini saya akan melempar ArgumentException untuk kegagalan pemeriksaan bendera. Semuanya tergantung pada preferensi. Beberapa standar pengkodean yang telah saya lihat sejauh untuk menentukan jenis pengecualian mana yang harus dilemparkan dalam skenario seperti ini.

Jika pengguna mencoba untuk mengirimkan sesuatu yang bukan enum maka saya akan melempar InvalidOperationException.

Edit:

Yang lain mengangkat poin menarik bahwa ini tidak didukung. Satu-satunya perhatian saya dengan NotSupportedException adalah bahwa secara umum itu adalah pengecualian yang dilemparkan ketika "materi gelap" telah dimasukkan ke sistem, atau dengan kata lain, "Metode ini harus masuk ke sistem pada antarmuka ini, tetapi kami menang hidupkan sampai versi 2.4 "

Saya juga melihat NotSupportedExceptions dilemparkan sebagai pengecualian lisensi "Anda menjalankan versi gratis dari perangkat lunak ini, fungsi ini tidak didukung".

Edit 2:

Kemungkinan lain:

System.ComponentModel.InvalidEnumArgumentException  

Pengecualian dilontarkan saat menggunakan argumen tidak valid yang merupakan enumerator.

Peter
sumber
Saya akan membuatnya dibatasi menjadi enum (setelah beberapa tipu muslihat jiggery) - hanya bendera yang saya khawatirkan.
Jon Skeet
Saya pikir orang-orang yang memberi lisensi itu harus melempar contoh dari LicensingExceptionkelas yang diwarisi InvalidOperationException.
Mehrdad Afshari
Saya setuju Mehrdad, Sayangnya, Pengecualian adalah salah satu area di mana terdapat banyak abu-abu dalam kerangka kerja. Tapi saya yakin ini sama untuk banyak bahasa. (tidak mengatakan saya akan kembali ke error runtime vb6 13 hehe)
Peter