Saya mengalami sedikit kesulitan memahami bagaimana saya akan menggunakan kovarians dan contravariance di dunia nyata.
Sejauh ini, satu-satunya contoh yang saya lihat adalah contoh array lama yang sama.
object[] objectArray = new string[] { "string 1", "string 2" };
Akan menyenangkan untuk melihat contoh yang memungkinkan saya untuk menggunakannya selama pengembangan saya jika saya bisa melihatnya digunakan di tempat lain.
c#
c#-4.0
covariance
Pisau cukur
sumber
sumber
Jawaban:
Katakanlah Anda memiliki Pribadi kelas dan kelas yang berasal darinya, Guru. Anda memiliki beberapa operasi yang menganggap
IEnumerable<Person>
sebagai argumen. Di kelas Sekolah Anda, Anda memiliki metode yang mengembalikanIEnumerable<Teacher>
. Kovarian memungkinkan Anda untuk langsung menggunakan hasil itu untuk metode yang mengambilIEnumerable<Person>
, menggantikan jenis yang lebih diturunkan untuk jenis yang lebih turunan (lebih generik). Kontravarians, kontra-intuitif, memungkinkan Anda untuk menggunakan jenis yang lebih umum, di mana jenis yang lebih diturunkan ditentukan.Lihat juga Kovarian dan Kontravarian dalam Generik pada MSDN .
Kelas :
Penggunaan :
sumber
Untuk kelengkapan ...
sumber
void feed(IGobbler<Donkey> dg)
. Jika Anda menggunakan IGobbler <Quadruped> sebagai parameter, Anda tidak bisa meneruskan naga yang hanya memakan keledai.Inilah yang saya kumpulkan untuk membantu saya memahami perbedaannya
tldr
sumber
Contravariance
contoh) kapanFruit
induknyaApple
?Kata kunci masuk dan keluar mengontrol aturan casting kompiler untuk antarmuka dan delegasi dengan parameter umum:
sumber
Berikut adalah contoh sederhana menggunakan hierarki warisan.
Dengan hierarki kelas yang sederhana:
Dan dalam kode:
Invarian (mis. Parameter tipe umum * tidak * dihiasi
in
atauout
kata kunci)Tampaknya, metode seperti ini
... harus menerima koleksi yang heterogen: (yang memang ada)
Namun, melewati koleksi tipe yang lebih diturunkan gagal!
Mengapa? Karena parameter generik
IList<LifeForm>
bukan kovarian -IList<T>
invarian, jadiIList<LifeForm>
hanya menerima koleksi (yang mengimplementasikan IList) di mana tipe parameternyaT
harusLifeForm
.Jika metode implementasi
PrintLifeForms
berbahaya (tetapi memiliki tanda tangan metode yang sama), alasan mengapa kompiler mencegah lewatList<Giraffe>
menjadi jelas:Karena
IList
mengizinkan penambahan atau penghapusan elemen, maka setiap subkelasLifeForm
dapat ditambahkan ke parameterlifeForms
, dan akan melanggar tipe kumpulan dari setiap tipe turunan yang diteruskan ke metode. (Di sini, metode jahat akan berusaha menambahkanZebra
kevar myGiraffes
). Untungnya, kompiler melindungi kita dari bahaya ini.Kovarian (Generik dengan tipe parameterisasi yang didekorasi
out
)Kovarian secara luas digunakan dengan koleksi yang tidak dapat diubah (yaitu ketika elemen baru tidak dapat ditambahkan atau dihapus dari koleksi)
Solusi untuk contoh di atas adalah untuk memastikan bahwa tipe pengumpulan generik kovarian digunakan, misalnya
IEnumerable
(didefinisikan sebagaiIEnumerable<out T>
).IEnumerable
tidak memiliki metode untuk mengubah ke koleksi, dan sebagai hasil dariout
kovarian, koleksi dengan subtipeLifeForm
sekarang dapat diteruskan ke metode:PrintLifeForms
sekarang dapat dipanggil denganZebras
,Giraffes
danIEnumerable<>
sembarang subkelas dariLifeForm
Contravariance (Generic dengan tipe parameterized yang didekorasi dengan
in
)Kontravarians sering digunakan ketika fungsi dilewatkan sebagai parameter.
Berikut adalah contoh fungsi, yang menggunakan
Action<Zebra>
parameter, dan menjalankannya pada turunan Zebra yang dikenal:Seperti yang diharapkan, ini berfungsi dengan baik:
Secara intuitif, ini akan gagal:
Namun, ini berhasil
dan bahkan ini juga berhasil:
Mengapa? Karena
Action
didefinisikan sebagaiAction<in T>
, yaitucontravariant
, artinya untukAction<Zebra> myAction
, yangmyAction
dapat berupa "sebagian besar"Action<Zebra>
, tetapi superclasses yang kurang turunan dariZebra
juga dapat diterima.Meskipun ini mungkin tidak intuitif pada awalnya (misalnya bagaimana bisa
Action<object>
dilewatkan sebagai parameter yang memerlukanAction<Zebra>
?), Jika Anda membongkar langkah-langkahnya, Anda akan mencatat bahwa fungsi yang dipanggil (PerformZebraAction
) itu sendiri bertanggung jawab untuk mengirimkan data (dalam hal iniZebra
contoh ) ke fungsi - data tidak berasal dari kode panggilan.Karena pendekatan terbalik menggunakan fungsi urutan yang lebih tinggi dengan cara ini, pada saat
Action
dipanggil, itu adalah turunan yang lebih banyakZebra
yang dipanggil terhadapzebraAction
fungsi (dilewatkan sebagai parameter), meskipun fungsi itu sendiri menggunakan tipe yang kurang diturunkan.sumber
in
kata kunci yang digunakan untuk contravariance ?Action<in T>
danFunc<in T, out TResult>
bersifat contravarian dalam jenis input. (Contoh saya menggunakan jenis invarian (Daftar), kovarian (IEnumerable) dan contravariant (Action, Func) lainnyaC#
jadi tidak akan tahu itu.Pada dasarnya setiap kali Anda memiliki fungsi yang mengambil Enumerable dari satu jenis, Anda tidak bisa meneruskan Enumerable dari tipe turunan tanpa secara eksplisit menuangnya.
Hanya untuk memperingatkan Anda tentang jebakan:
Itu adalah kode yang mengerikan, tetapi itu memang ada dan perilaku yang berubah di C # 4 mungkin memperkenalkan bug yang sulit ditemukan jika Anda menggunakan konstruksi seperti ini.
sumber
Dari MSDN
sumber
Contravariance
Di dunia nyata, Anda selalu dapat menggunakan tempat berlindung untuk hewan daripada tempat berlindung untuk kelinci karena setiap kali tempat penampungan hewan menampung kelinci, ia adalah hewan. Namun, jika Anda menggunakan tempat perlindungan kelinci alih-alih tempat perlindungan hewan, stafnya dapat dimakan oleh seekor harimau.
Dalam kode, ini berarti bahwa jika Anda memiliki
IShelter<Animal> animals
Anda dapat hanya menulisIShelter<Rabbit> rabbits = animals
jika Anda berjanji dan penggunaanT
dalamIShelter<T>
hanya sebagai metode parameter suka begitu:dan ganti item dengan yang lebih umum, yaitu kurangi varians atau perkenalkan contra variance.
Kovarian
Di dunia nyata, Anda selalu dapat menggunakan pemasok kelinci alih-alih pemasok hewan karena setiap kali pemasok kelinci memberi Anda kelinci itu adalah hewan. Namun, jika Anda menggunakan pemasok hewan alih-alih pemasok kelinci, Anda bisa dimakan oleh harimau.
Dalam kode, ini berarti bahwa jika Anda memiliki
ISupply<Rabbit> rabbits
Anda dapat hanya menulisISupply<Animal> animals = rabbits
jika Anda berjanji dan penggunaanT
dalamISupply<T>
hanya sebagai metode kembali nilai-nilai seperti itu:dan ganti item dengan item yang lebih diturunkan, yaitu menambah varians atau memperkenalkan varian bersama .
Semua dalam semua, ini hanya janji waktu kompilasi yang dapat diperiksa dari Anda bahwa Anda akan memperlakukan jenis generik dengan cara tertentu untuk menjaga keamanan jenis dan tidak membuat siapa pun dimakan.
Anda mungkin ingin memberikan ini sebuah baca dua kali lipat-membungkus kepala Anda sekitar ini.
sumber
contravariance
ini menarik. Saya membacanya sebagai indikasi persyaratan operasional : bahwa jenis yang lebih umum harus mendukung kasus penggunaan dari semua jenis yang berasal darinya. Jadi dalam hal ini tempat perlindungan hewan harus dapat mendukung perlindungan setiap jenis hewan. Jika demikian, menambahkan subkelas baru dapat merusak superclass! Yaitu - jika kita menambahkan subtipe Tyrannosaurus Rex maka itu dapat menghancurkan tempat berlindung hewan yang ada .Delegasi konverter membantu saya memvisualisasikan kedua konsep yang bekerja bersama:
TOutput
mewakili kovarians di mana metode mengembalikan tipe yang lebih spesifik .TInput
mewakili contravariance di mana metode dilewatkan jenis yang kurang spesifik .sumber