Adakah yang bisa memberi tahu saya jika ada cara dengan obat generik untuk membatasi argumen tipe generik T
hanya:
Int16
Int32
Int64
UInt16
UInt32
UInt64
Saya mengetahui where
kata kunci, tetapi tidak dapat menemukan antarmuka hanya untuk jenis ini,
Sesuatu seperti:
static bool IntegerFunction<T>(T value) where T : INumeric
c#
generics
constraints
Corin Blaikie
sumber
sumber
Jawaban:
C # tidak mendukung ini. Hejlsberg telah menjelaskan alasan untuk tidak menerapkan fitur ini dalam sebuah wawancara dengan Bruce Eckel :
Namun, ini mengarah pada kode yang cukup berbelit-belit, di mana pengguna harus menyediakan
Calculator<T>
implementasi mereka sendiri , untuk masing-masingT
yang ingin mereka gunakan. Selama itu tidak harus diperluas, yaitu jika Anda hanya ingin mendukung sejumlah jenis tetap, sepertiint
dandouble
, Anda dapat pergi dengan antarmuka yang relatif sederhana:( Implementasi minimal dalam GitHub Gist. )
Namun, segera setelah Anda ingin pengguna dapat menyediakan jenis kustom mereka sendiri, Anda perlu membuka implementasi ini sehingga pengguna dapat menyediakan
Calculator
contoh mereka sendiri . Misalnya, untuk membuat instance matriks yang menggunakan implementasi floating point desimal khususDFP
, Anda harus menulis kode ini:... dan laksanakan semua anggota untuk
DfpCalculator : ICalculator<DFP>
.Alternatif, yang sayangnya memiliki batasan yang sama, adalah bekerja dengan kelas kebijakan, seperti yang dibahas dalam jawaban Sergey Shandar .
sumber
Operator
/Operator<T>
; yoda.arachsys.com/csharp/miscutil/usage/genericoperators.htmlOperator<T>
kode (karena wawancara telah diberikan jauh sebelum adanyaExpressions
kerangka kerja, meskipun orang dapat penggunaan sajaReflection.Emit
) - dan saya akan sangat tertarik dengan solusinya.Mengingat popularitas pertanyaan ini dan minat di balik fungsi seperti itu, saya terkejut melihat bahwa belum ada jawaban yang melibatkan T4.
Dalam kode contoh ini saya akan menunjukkan contoh yang sangat sederhana tentang bagaimana Anda dapat menggunakan mesin templating yang kuat untuk melakukan apa yang dilakukan oleh kompiler di belakang layar dengan obat generik.
Alih-alih melewati rintangan dan mengorbankan kepastian waktu kompilasi, Anda cukup membuat fungsi yang Anda inginkan untuk setiap tipe yang Anda sukai dan menggunakannya sesuai itu (pada waktu kompilasi!).
Untuk melakukan ini:
Itu dia. Anda selesai sekarang.
Menyimpan file ini akan secara otomatis mengompilasinya ke file sumber ini:
Dalam
main
metode Anda, Anda dapat memverifikasi bahwa Anda memiliki kepastian waktu kompilasi:Saya akan mendahului satu komentar: tidak, ini bukan pelanggaran prinsip KERING. Prinsip KERING ada untuk mencegah orang dari menggandakan kode di banyak tempat yang akan menyebabkan aplikasi menjadi sulit untuk dipelihara.
Ini sama sekali tidak terjadi di sini: jika Anda ingin perubahan maka Anda bisa mengubah template (satu sumber untuk semua generasi Anda!) Dan selesai.
Untuk menggunakannya dengan definisi kustom Anda sendiri, tambahkan deklarasi namespace (pastikan itu adalah yang sama dengan yang di mana Anda akan mendefinisikan implementasi Anda sendiri) ke kode yang Anda hasilkan dan tandai kelas sebagai
partial
. Setelah itu, tambahkan baris ini ke file template Anda sehingga akan dimasukkan dalam kompilasi akhirnya:Jujur saja: Ini keren sekali.
Penafian: sampel ini sangat dipengaruhi oleh Metaprogramming dalam .NET oleh Kevin Hazzard dan Jason Bock, Manning Publications .
sumber
T
yang ada atau mewarisi dari berbagaiIntX
kelas? Saya suka solusi ini karena menghemat waktu, tetapi untuk itu untuk menyelesaikan masalah 100% (meskipun tidak sebaik jika # C memiliki dukungan untuk jenis kendala ini, built-in) masing-masing metode yang dihasilkan masih harus generik sehingga mereka dapat mengembalikan objek tipe yang mewarisi dari salah satuIntXX
kelas.IntXX
jenisnya adalah struct yang berarti mereka tidak mendukung warisan sejak awal . Dan bahkan jika itu terjadi maka prinsip substitusi Liskov (yang mungkin Anda ketahui dari idiom SOLID) berlaku: jika metode ini didefinisikan sebagaiX
danY
merupakan anak dariX
setiap definisi maka setiapY
harus dapat diteruskan ke metode itu sebagai pengganti jenis dasarnya.Tidak ada kendala untuk ini. Ini masalah nyata bagi siapa pun yang ingin menggunakan obat generik untuk perhitungan numerik.
Saya akan melangkah lebih jauh dan mengatakan kita perlu
Atau bahkan
Sayangnya Anda hanya memiliki antarmuka, kelas dasar, dan kata kunci
struct
(harus tipe nilai),class
(harus tipe referensi) dannew()
(harus memiliki konstruktor default)Anda bisa membungkus nomor itu dengan sesuatu yang lain (mirip dengan
INullable<T>
) seperti di sini di proyek codep .Anda bisa menerapkan pembatasan pada saat runtime (dengan merefleksikan operator atau memeriksa jenis) tetapi itu memang kehilangan keuntungan memiliki generik di tempat pertama.
sumber
where T : operators( +, -, /, * )
apakah legal C #? Maaf untuk pertanyaan pemula.where T : operators( +, -, /, * )
, tetapi tidak bisa.Penanganan masalah menggunakan kebijakan:
Algoritma:
Pemakaian:
Solusinya adalah waktu kompilasi yang aman. CityLizard Framework menyediakan versi kompilasi untuk .NET 4.0. File tersebut adalah lib / NETFramework4.0 / CityLizard.Policy.dll.
Ini juga tersedia di Nuget: https://www.nuget.org/packages/CityLizard/ . Lihat struktur CityLizard.Policy.I .
sumber
struct
? bagaimana jika saya menggunakan kelas tunggal sebagai gantinya dan mengubah instance kepublic static NumericPolicies Instance = new NumericPolicies();
dan kemudian menambahkan konstruktor iniprivate NumericPolicies() { }
.T Add<T> (T t1, T t2)
, tetapiSum()
hanya berfungsi ketika dapat mengambil sendiri tipe T dari parameternya, yang tidak mungkin ketika itu tertanam dalam fungsi generik lain.Pertanyaan ini sedikit seperti FAQ, jadi saya memposting ini sebagai wiki (karena saya pernah memposting yang serupa sebelumnya, tapi ini yang lebih tua); bagaimanapun...
Versi NET. Apa yang Anda gunakan? Jika Anda menggunakan .NET 3.5, maka saya memiliki implementasi operator generik di MiscUtil (gratis dll).
Ini memiliki metode seperti
T Add<T>(T x, T y)
, dan varian lain untuk aritmatika pada berbagai jenis (sepertiDateTime + TimeSpan
).Selain itu, ini berfungsi untuk semua operator inbuilt, mengangkat dan dipesan lebih dahulu, dan cache delegasi untuk kinerja.
Beberapa latar belakang tambahan tentang mengapa ini rumit ada di sini .
Anda mungkin juga ingin tahu bahwa
dynamic
(4.0) semacam memecahkan masalah ini secara tidak langsung juga - yaitusumber
Sayangnya Anda hanya dapat menentukan struct di klausa where dalam hal ini. Tampaknya aneh Anda tidak dapat menentukan Int16, Int32, dll secara khusus, tetapi saya yakin ada beberapa alasan implementasi mendalam yang mendasari keputusan untuk tidak mengizinkan tipe nilai dalam klausa mana.
Saya kira satu-satunya solusi adalah dengan melakukan pemeriksaan runtime yang sayangnya mencegah masalah diambil pada waktu kompilasi. Itu akan seperti: -
Yang agak jelek, saya tahu, tapi setidaknya memberikan kendala yang diperlukan.
Saya juga melihat kemungkinan implikasi kinerja untuk implementasi ini, mungkin ada cara yang lebih cepat di luar sana.
sumber
// Rest of code...
mungkin tidak dapat dikompilasi jika tergantung pada operasi yang ditentukan oleh kendala.// Rest of code...
sukavalue + value
atauvalue * value
, Anda punya kesalahan kompilasi.Mungkin yang terdekat yang bisa Anda lakukan adalah
Tidak yakin apakah Anda bisa melakukan yang berikut ini
Untuk sesuatu yang sangat spesifik, mengapa tidak hanya memiliki kelebihan untuk masing-masing jenis, daftarnya sangat singkat dan mungkin memiliki lebih sedikit jejak memori.
sumber
Dimulai dengan C # 7.3, Anda dapat menggunakan pendekatan yang lebih dekat - batasan yang tidak dikelola untuk menentukan bahwa parameter tipe adalah tipe tidak dikelola yang bukan penunjuk, tidak dapat dibatalkan .
Kendala yang tidak dikelola menyiratkan kendala struct dan tidak dapat digabungkan dengan kendala struct atau baru ().
Tipe adalah tipe yang tidak dikelola jika salah satu dari tipe berikut:
Untuk membatasi lebih jauh dan menghilangkan pointer dan tipe yang ditentukan pengguna yang tidak mengimplementasikan IComparable, tambahkan IComparable (tetapi enum masih berasal dari IComparable, jadi batasi enum dengan menambahkan IEquatable <T>, Anda dapat melangkah lebih jauh tergantung pada keadaan Anda dan menambahkan antarmuka tambahan. tidak dikelola memungkinkan daftar ini lebih pendek):
sumber
DateTime
berada di bawahunmanaged, IComparable, IEquatable<T>
batasan ..Tidak ada cara untuk membatasi templat ke tipe, tetapi Anda dapat menentukan tindakan yang berbeda berdasarkan jenisnya. Sebagai bagian dari paket numerik umum, saya membutuhkan kelas generik untuk menambahkan dua nilai.
Perhatikan bahwa typeofs dievaluasi pada waktu kompilasi, jadi pernyataan if akan dihapus oleh kompiler. Kompiler juga menghapus gips palsu. Jadi Sesuatu akan menyelesaikan di kompiler untuk
sumber
Saya membuat sedikit fungsi perpustakaan untuk mengatasi masalah ini:
Dari pada:
Anda bisa menulis:
Anda dapat menemukan kode sumber di sini: /codereview/26022/improvement-requested-for-generic-calculator-and-generic-number
sumber
Saya bertanya-tanya sama dengan samjudson, mengapa hanya untuk bilangan bulat? dan jika itu masalahnya, Anda mungkin ingin membuat kelas pembantu atau sesuatu seperti itu untuk menampung semua jenis yang Anda inginkan.
Jika semua yang Anda inginkan adalah bilangan bulat, jangan gunakan generik, itu bukan generik; atau lebih baik lagi, tolak tipe lain dengan memeriksa tipenya.
sumber
Belum ada solusi 'baik' untuk ini. Namun Anda dapat mempersempit argumen tipe secara signifikan untuk mengesampingkan banyak kesalahan untuk kendala 'Inumerik' hipotetis Anda seperti yang ditunjukkan oleh Haacked di atas.
static bool IntegerFunction <T> (nilai T) di mana T: ICmembandingkan, IFormattable, IConvertible, IComparable <T>, IEquatable <T>, struct {...
sumber
Jika Anda menggunakan .NET 4.0 dan yang lebih baru maka Anda bisa menggunakan argumen metode dinamis dan memeriksa runtime bahwa tipe argumen dinamis yang diteruskan adalah tipe numerik / integer.
Jika jenis melewati dinamis adalah tidak numerik jenis / bilangan bulat kemudian melemparkan pengecualian.
Contoh kode pendek yang mengimplementasikan ide adalah sesuatu seperti:
Tentu saja solusi ini hanya bekerja dalam jangka waktu tetapi tidak pernah dalam waktu kompilasi.
Jika Anda menginginkan solusi yang selalu berfungsi dalam waktu kompilasi dan tidak pernah dalam waktu berjalan maka Anda harus membungkus dinamis dengan struct / kelas publik yang konstruktor publiknya yang kelebihan beban hanya menerima argumen dari tipe yang diinginkan saja dan memberikan nama yang sesuai dengan struct / kelas.
Masuk akal bahwa dynamic yang dibungkus selalu anggota privat dari kelas / struct dan itu adalah satu-satunya anggota struct / kelas dan nama satu-satunya anggota struct / kelas adalah "nilai".
Anda juga harus mendefinisikan dan menerapkan metode publik dan / atau operator yang bekerja dengan tipe yang diinginkan untuk anggota dinamis pribadi kelas / struct jika perlu.
Juga masuk akal bahwa struct / kelas memiliki konstruktor khusus / unik yang menerima dinamis sebagai argumen yang menginisialisasi itu hanya anggota dinamis pribadi yang disebut "nilai" tetapi pengubah konstruktor ini bersifat pribadi tentu saja.
Setelah kelas / struct siap mendefinisikan tipe argumen IntegerFunction menjadi kelas / struct yang telah didefinisikan.
Contoh kode panjang yang mengimplementasikan ide adalah sesuatu seperti:
Perhatikan bahwa untuk menggunakan dinamis dalam kode Anda, Anda harus Menambahkan Referensi ke Microsoft.CSharp
Jika versi .NET framework di bawah / di bawah / kurang dari 4.0 dan dinamis tidak terdefinisi dalam versi itu maka Anda harus menggunakan objek dan melakukan casting ke tipe integer, yang merupakan masalah, jadi saya sarankan Anda gunakan di Setidaknya. NET 4.0 atau yang lebih baru jika Anda bisa sehingga Anda dapat menggunakan dinamis, bukan objek .
sumber
Sayangnya. NET tidak menyediakan cara untuk melakukannya secara asli.
Untuk mengatasi masalah ini, saya membuat pustaka OSS Genumerics yang menyediakan sebagian besar operasi numerik standar untuk tipe numerik bawaan berikut dan setara yang dapat dibatalkan dengan kemampuan untuk menambahkan dukungan untuk tipe numerik lainnya.
sbyte
,byte
,short
,ushort
,int
,uint
,long
,ulong
,float
,double
,decimal
, DanBigInteger
Kinerja ini setara dengan solusi spesifik tipe numerik yang memungkinkan Anda membuat algoritma numerik generik yang efisien.
Berikut adalah contoh penggunaan kode.
sumber
Apa gunanya latihan ini?
Seperti yang sudah ditunjukkan orang, Anda bisa memiliki fungsi non-generik mengambil item terbesar, dan kompiler akan secara otomatis mengonversi int lebih kecil untuk Anda.
Jika fungsi Anda berada di jalur kritis kinerja (sangat tidak mungkin, IMO), Anda dapat memberikan kelebihan untuk semua fungsi yang diperlukan.
sumber
Saya akan menggunakan yang generik yang Anda dapat menangani eksternal ...
sumber
Keterbatasan ini memengaruhi saya ketika saya mencoba membebani operator untuk tipe generik; karena tidak ada batasan "Inumerik", dan karena alasan lain orang-orang baik di stackoverflow senang untuk menyediakan, operasi tidak dapat didefinisikan pada tipe generik.
Saya menginginkan sesuatu seperti
Saya telah mengatasi masalah ini menggunakan .net4 dynamic runtime typing.
Dua hal tentang penggunaan
dynamic
adalahsumber
Tipe .NET numeric primitive tidak berbagi antarmuka umum apa pun yang memungkinkan mereka digunakan untuk perhitungan. Dimungkinkan untuk mendefinisikan antarmuka Anda sendiri (mis
ISignedWholeNumber
) yang akan melakukan operasi tersebut, menentukan struktur yang mengandung satuInt16
,Int32
, dll dan mengimplementasikan interface tersebut, dan kemudian memiliki metode yang menerima jenis generik dibatasiISignedWholeNumber
, tetapi harus mengkonversi nilai numerik untuk jenis struktur Anda mungkin akan menjadi gangguan.Sebuah pendekatan alternatif akan mendefinisikan kelas statis
Int64Converter<T>
dengan properti statisbool Available {get;};
dan delegasi statis untukInt64 GetInt64(T value)
,T FromInt64(Int64 value)
,bool TryStoreInt64(Int64 value, ref T dest)
. Konstruktor kelas dapat menggunakan kode-keras untuk memuat delegasi untuk tipe yang diketahui, dan mungkin menggunakan Refleksi untuk menguji apakah tipeT
mengimplementasikan metode dengan nama dan tanda tangan yang sesuai (dalam kasus itu sesuatu seperti struct yang berisiInt64
dan mewakili angka, tetapi memilikiToString()
metode kustom ). Pendekatan ini akan kehilangan keuntungan yang terkait dengan pengecekan tipe waktu kompilasi, tetapi akan tetap berhasil menghindari operasi tinju dan masing-masing jenis hanya perlu "diperiksa" sekali. Setelah itu, operasi yang terkait dengan tipe itu akan diganti dengan pengiriman delegasi.sumber
Int64
hasil, tetapi tidak memberikan sarana yang misalnya bilangan bulat dari jenis sewenang-wenang dapat ditingkatkan untuk menghasilkan bilangan bulat lain dari jenis yang sama .Saya memiliki situasi serupa di mana saya perlu menangani jenis dan string numerik; tampaknya sedikit campuran aneh tapi begitulah.
Sekali lagi, seperti banyak orang, saya melihat kendala dan menghasilkan banyak antarmuka yang harus didukung. Namun, a) bukan 100% kedap air dan b), siapa pun yang baru melihat daftar panjang kendala ini akan segera sangat bingung.
Jadi, pendekatan saya adalah menempatkan semua logika saya ke dalam metode generik tanpa kendala, tetapi menjadikan metode generik itu pribadi. Saya kemudian mengeksposnya dengan metode publik, yang secara eksplisit menangani tipe yang ingin saya tangani - menurut saya, kodenya bersih dan eksplisit, misalnya
sumber
Jika yang Anda inginkan adalah menggunakan satu tipe numerik , Anda dapat mempertimbangkan untuk membuat sesuatu yang mirip dengan alias di C ++ with
using
.Jadi alih-alih memiliki yang sangat generik
kamu bisa saja
Itu mungkin memungkinkan Anda untuk dengan mudah beralih dari
double
keint
atau orang lain jika diperlukan, tetapi Anda tidak akan dapat menggunakannyaComputeSomething
dengandouble
danint
dalam program yang sama.Tapi mengapa tidak mengganti semua
double
untukint
kemudian? Karena metode Anda mungkin ingin menggunakandouble
apakah inputnyadouble
atauint
. Alias memungkinkan Anda untuk mengetahui dengan tepat variabel mana yang menggunakan tipe dinamis .sumber
Topik sudah tua tetapi untuk pembaca masa depan:
Fitur ini sangat terkait dengan
Discriminated Unions
yang tidak diimplementasikan dalam C # sejauh ini. Saya menemukan masalahnya di sini:https://github.com/dotnet/csharplang/issues/113
Masalah ini masih terbuka dan fitur sudah direncanakan
C# 10
Jadi kita masih harus menunggu lebih lama, tetapi setelah melepaskan Anda bisa melakukannya dengan cara ini:
sumber
Saya pikir Anda salah paham obat generik. Jika operasi yang Anda coba lakukan hanya baik untuk tipe data tertentu maka Anda tidak melakukan sesuatu yang "generik".
Juga, karena Anda hanya ingin memungkinkan fungsi untuk bekerja pada tipe data int maka Anda seharusnya tidak memerlukan fungsi terpisah untuk setiap ukuran tertentu. Dengan hanya mengambil parameter dalam tipe spesifik terbesar akan memungkinkan program untuk secara otomatis mengirimkan tipe data yang lebih kecil ke dalamnya. (Yaitu dengan mengirimkan Int16 akan secara otomatis dikonversi ke Int64 saat memanggil).
Jika Anda melakukan operasi yang berbeda berdasarkan ukuran int aktual yang dilewatkan ke dalam fungsi maka saya akan berpikir Anda harus mempertimbangkan kembali dengan serius bahkan mencoba melakukan apa yang Anda lakukan. Jika Anda harus membodohi bahasa itu, Anda harus berpikir lebih banyak tentang apa yang ingin Anda capai daripada bagaimana melakukan apa yang Anda inginkan.
Gagal semua yang lain, parameter tipe objek dapat digunakan dan kemudian Anda harus memeriksa tipe parameter dan mengambil tindakan yang sesuai atau melempar pengecualian.
sumber