Apakah ada cara untuk mendapatkan deklarasi fungsi berikut?
public bool Foo<T>() where T : interface;
yaitu. di mana T adalah tipe antarmuka (mirip dengan where T : class
, dan struct
).
Saat ini saya sudah puas dengan:
public bool Foo<T>() where T : IBase;
Di mana IBase didefinisikan sebagai antarmuka kosong yang diwarisi oleh semua antarmuka khusus saya ... Tidak ideal, tetapi harus berfungsi ... Mengapa Anda tidak dapat menentukan bahwa tipe generik harus berupa antarmuka?
Untuk apa nilainya, saya ingin ini karena Foo
melakukan refleksi di mana ia membutuhkan jenis antarmuka ... Saya bisa meneruskannya sebagai parameter normal dan melakukan pemeriksaan yang diperlukan dalam fungsi itu sendiri, tetapi ini tampak jauh lebih aman untuk mengetik (dan saya misalkan sedikit lebih banyak pemain, karena semua pemeriksaan dilakukan pada waktu yang bersamaan).
sumber
IBase
- digunakan dengan cara ini - disebut antarmuka penanda . Mereka memungkinkan perilaku khusus untuk tipe 'ditandai'.Jawaban:
Yang paling dekat yang dapat Anda lakukan (kecuali untuk pendekatan antarmuka-dasar Anda) adalah "
where T : class
", yang berarti tipe referensi. Tidak ada sintaks yang berarti "antarmuka apa pun".Ini ("
where T : class
") digunakan, misalnya, di WCF untuk membatasi klien untuk kontrak layanan (antarmuka).sumber
interface
kendala padaT
harus memungkinkan perbandingan referensi antaraT
dan semua jenis referensi lainnya, karena perbandingan referensi diperbolehkan antara setiap antarmuka dan hampir semua jenis referensi lainnya, dan memungkinkan perbandingan bahkan kasus itu tidak menimbulkan masalah.Saya tahu ini agak terlambat tetapi bagi mereka yang tertarik Anda dapat menggunakan pemeriksaan runtime.
sumber
Foo(Type type)
.if (new T() is IMyInterface) { }
untuk memeriksa apakah antarmuka diimplementasikan oleh kelas T. Mungkin bukan yang paling efisien, tetapi berhasil.Tidak, sebenarnya, jika Anda berpikir
class
danstruct
berarticlass
es danstruct
s, Anda sedang salah.class
berarti semua jenis referensi (mis. termasuk antarmuka juga) danstruct
sarana semua jenis nilai (misalnyastruct
,enum
).sumber
where T : struct
batasan pencocokan .class
, tetapi menyatakan lokasi penyimpanan jenis antarmuka benar-benar menyatakan lokasi penyimpanan sebagai referensi kelas yang menerapkan jenis itu.where T : struct
bersesuaian denganNotNullableValueTypeConstraint
, jadi itu berarti ia harus menjadi jenis nilai lainnya selainNullable<>
. (JadiNullable<>
adalah tipe struct yang tidak memenuhiwhere T : struct
kendala.)Untuk menindaklanjuti jawaban Robert, ini bahkan lebih lambat, tetapi Anda dapat menggunakan kelas pembantu statis untuk melakukan pemeriksaan runtime hanya sekali per jenis:
Saya juga mencatat bahwa solusi "seharusnya bekerja" Anda ternyata tidak berhasil. Mempertimbangkan:
Sekarang tidak ada yang menghentikan Anda dari memanggil Foo demikian:
The
Actual
kelas, setelah semua, memenuhi yangIBase
kendala.sumber
static
konstruktor tidak bisapublic
, jadi ini harus memberikan kesalahan saat kompilasi.static
Kelas Anda juga berisi metode instance, itu juga kesalahan waktu kompilasi.Untuk beberapa waktu sekarang saya telah memikirkan kendala waktu dekat, jadi ini adalah kesempatan yang sempurna untuk meluncurkan konsep.
Ide dasarnya adalah bahwa jika Anda tidak dapat melakukan waktu kompilasi cek, Anda harus melakukannya sedini mungkin, yang pada dasarnya adalah saat aplikasi dimulai. Jika semua cek baik-baik saja, aplikasi akan berjalan; jika cek gagal, aplikasi akan gagal secara instan.
Tingkah laku
Hasil terbaik yang mungkin adalah bahwa program kami tidak dapat dikompilasi jika kendala tidak terpenuhi. Sayangnya itu tidak mungkin dalam implementasi C # saat ini.
Hal terbaik berikutnya adalah bahwa program macet saat dimulai.
Opsi terakhir adalah bahwa program akan macet saat kode dipukul. Ini adalah perilaku default .NET. Bagi saya, ini benar-benar tidak dapat diterima.
Prasyarat
Kita perlu memiliki mekanisme kendala, jadi karena tidak ada yang lebih baik ... mari kita gunakan atribut. Atribut akan hadir di atas batasan umum untuk memeriksa apakah itu cocok dengan kondisi kita. Jika tidak, kami memberikan kesalahan yang buruk.
Ini memungkinkan kami melakukan hal-hal seperti ini dalam kode kami:
(Saya telah menyimpannya di
where T:class
sini, karena saya selalu lebih suka pemeriksaan waktu kompilasi daripada pemeriksaan waktu berjalan)Jadi, itu hanya menyisakan 1 masalah bagi kami, yaitu memeriksa apakah semua tipe yang kami gunakan cocok dengan batasan tersebut. Seberapa sulitkah itu?
Mari kita hancurkan
Tipe generik selalu baik pada kelas (/ struct / antarmuka) atau pada suatu metode.
Memicu kendala mengharuskan Anda melakukan salah satu dari hal-hal berikut:
Pada titik ini, saya ingin menyatakan bahwa Anda harus selalu menghindari melakukan (4) dalam program IMO. Apapun, pemeriksaan ini tidak akan mendukungnya, karena itu secara efektif berarti menyelesaikan masalah penghentian.
Kasus 1: menggunakan tipe
Contoh:
Contoh 2:
Pada dasarnya ini melibatkan pemindaian semua jenis, warisan, anggota, parameter, dll, dll, dll. Jika suatu jenis adalah tipe generik dan memiliki batasan, kami memeriksa batasannya; jika array, kami memeriksa jenis elemen.
Pada titik ini saya harus menambahkan bahwa ini akan mematahkan fakta bahwa secara default .NET memuat jenis 'malas'. Dengan memindai semua jenis, kami memaksa runtime .NET untuk memuat semuanya. Untuk sebagian besar program ini seharusnya tidak menjadi masalah; tetap saja, jika Anda menggunakan inisialisasi statis dalam kode Anda, Anda mungkin mengalami masalah dengan pendekatan ini ... Yang mengatakan, saya tidak akan menyarankan siapa pun untuk melakukan ini anyways (kecuali untuk hal-hal seperti ini :-), jadi itu seharusnya tidak memberikan Anda banyak masalah.
Kasus 2: menggunakan tipe dalam suatu metode
Contoh:
Untuk memeriksa ini kita hanya memiliki 1 opsi: mendekompilasi kelas, periksa semua token anggota yang digunakan dan jika salah satunya adalah tipe generik - periksa argumen.
Kasus 3: Refleksi, konstruksi generik runtime
Contoh:
Saya kira secara teori dimungkinkan untuk memeriksa ini dengan trik yang sama seperti kasus (2), tetapi pelaksanaannya jauh lebih sulit (Anda perlu memeriksa jika
MakeGenericType
dipanggil dalam beberapa jalur kode). Saya tidak akan membahas detail di sini ...Kasus 4: Refleksi, runtime RTTI
Contoh:
Ini adalah skenario terburuk dan seperti yang saya jelaskan sebelumnya umumnya ide yang buruk IMHO. Either way, tidak ada cara praktis untuk mencari tahu ini menggunakan cek.
Menguji banyak
Membuat program yang menguji kasus (1) dan (2) akan menghasilkan sesuatu seperti ini:
Menggunakan kodenya
Nah, itu bagian yang mudah :-)
sumber
Anda tidak dapat melakukan ini dalam versi C # dirilis, atau di C # 4.0 mendatang. Ini bukan batasan C #, baik - tidak ada batasan "antarmuka" di CLR itu sendiri.
sumber
Jika memungkinkan, saya pergi dengan solusi seperti ini. Ini hanya berfungsi jika Anda ingin beberapa antarmuka tertentu (mis. Yang memiliki akses sumber) dilewatkan sebagai parameter umum, bukan apa pun.
IInterface
.IInterface
Dalam sumber, terlihat seperti ini:
Antarmuka apa pun yang Anda inginkan untuk dilewatkan sebagai parameter umum:
Iinterface:
Kelas tempat Anda ingin meletakkan batasan tipe:
sumber
T
tidak dibatasi untuk antarmuka, itu dibatasi pada apa pun yang mengimplementasikanIInterface
- yang jenis apa pun dapat lakukan jika ingin, misalnyastruct Foo : IInterface
karena AndaIInterface
kemungkinan besar publik (jika tidak semua yang menerimanya harus internal).Apa yang telah Anda setujui adalah yang terbaik yang dapat Anda lakukan:
sumber
Saya mencoba melakukan sesuatu yang serupa dan menggunakan solusi penyelesaian: Saya berpikir tentang operator implisit dan eksplisit pada struktur: Idenya adalah untuk membungkus Type dalam struktur yang dapat dikonversi menjadi Type secara implisit.
Berikut adalah struktur seperti itu:
public struct InterfaceType {private Type _type;
}
penggunaan dasar:
Anda harus membayangkan mekanisme Anda sendiri dalam hal ini, tetapi sebuah contoh dapat berupa metode yang mengambil InterfaceType dalam parameter, bukan tipe
Metode untuk mengesampingkan yang seharusnya mengembalikan tipe antarmuka:
Mungkin ada hubungannya dengan obat generik juga, tetapi saya tidak mencoba
Semoga ini bisa membantu atau memberikan ide :-)
sumber
Solusi A: Kombinasi kendala ini harus menjamin bahwa itu
TInterface
adalah antarmuka:Dibutuhkan satu struct
TStruct
sebagai Saksi untuk membuktikan bahwa ituTInterface
adalah struct.Anda dapat menggunakan struct tunggal sebagai saksi untuk semua jenis non-generik Anda:
Solusi B: Jika Anda tidak ingin menjadikan struct sebagai saksi, Anda dapat membuat antarmuka
dan gunakan batasan:
Implementasi untuk antarmuka:
Ini memecahkan beberapa masalah, tetapi membutuhkan kepercayaan bahwa tidak ada yang mengimplementasikan
ISInterface<T>
untuk jenis non-antarmuka, tetapi itu cukup sulit dilakukan secara tidak sengaja.sumber
Gunakan kelas abstrak sebagai gantinya. Jadi, Anda akan memiliki sesuatu seperti:
sumber