IEnumerable<T>
adalah co-varian tetapi tidak mendukung tipe nilai, hanya tipe referensi saja. Kode sederhana di bawah ini berhasil dikompilasi:
IEnumerable<string> strList = new List<string>();
IEnumerable<object> objList = strList;
Tetapi mengubah dari string
menjadi int
akan mendapatkan kesalahan yang dikompilasi:
IEnumerable<int> intList = new List<int>();
IEnumerable<object> objList = intList;
Alasannya dijelaskan dalam MSDN :
Varians hanya berlaku untuk tipe referensi; jika Anda menentukan tipe nilai untuk parameter tipe varian, parameter tipe itu tidak tetap untuk tipe konstruksi yang dihasilkan.
Saya telah mencari dan menemukan bahwa beberapa pertanyaan menyebutkan alasannya adalah tinju antara tipe nilai dan tipe referensi . Tapi itu masih belum menjernihkan pikiran saya mengapa tinju adalah alasannya?
Bisakah seseorang tolong berikan penjelasan yang sederhana dan terperinci mengapa kovarians dan contravariance tidak mendukung tipe nilai dan bagaimana tinju mempengaruhi ini?
sumber
Jawaban:
Pada dasarnya, varians berlaku ketika CLR dapat memastikan bahwa CLR tidak perlu melakukan perubahan representasional terhadap nilai-nilai tersebut. Referensi semua terlihat sama - sehingga Anda dapat menggunakan
IEnumerable<string>
sebagaiIEnumerable<object>
tanpa perubahan representasi; kode asli itu sendiri tidak perlu tahu apa yang Anda lakukan dengan nilai-nilai sama sekali, asalkan infrastruktur telah menjamin bahwa itu pasti akan valid.Untuk tipe nilai, itu tidak berfungsi - untuk menganggapnya
IEnumerable<int>
sebagaiIEnumerable<object>
, kode yang menggunakan urutan harus tahu apakah akan melakukan konversi tinju atau tidak.Anda mungkin ingin membaca posting blog Eric Lippert tentang representasi dan identitas untuk lebih lanjut tentang topik ini secara umum.
EDIT: Memiliki membaca ulang posting blog Eric sendiri, setidaknya tentang identitas sebagai representasi, meskipun keduanya terkait. Khususnya:
sumber
int
bukan merupakan subtipe dariobject
. Fakta bahwa perubahan representasional diperlukan hanyalah konsekuensi dari ini.Int32
memiliki dua bentuk representasional, "kotak" dan "tanpa kotak". Compiler harus memasukkan kode untuk mengkonversi dari satu formulir ke yang lain, meskipun ini biasanya tidak terlihat pada tingkat kode sumber. Akibatnya, hanya bentuk "kotak" yang dianggap oleh sistem yang mendasari sebagai subtipeobject
, tetapi kompilator secara otomatis berurusan dengan ini setiap kali suatu tipe nilai ditugaskan ke antarmuka yang kompatibel atau ke sesuatu yang bertipeobject
.Mungkin lebih mudah untuk dipahami jika Anda memikirkan representasi yang mendasarinya (meskipun ini benar-benar detail implementasi). Berikut ini adalah kumpulan string:
Anda dapat menganggapnya
strings
sebagai memiliki representasi berikut:Ini adalah kumpulan dari tiga elemen, masing-masing menjadi referensi ke string. Anda bisa melemparkan ini ke koleksi objek:
Pada dasarnya itu adalah representasi yang sama kecuali sekarang referensi adalah referensi objek:
Representasinya sama. Referensi hanya diperlakukan secara berbeda; Anda tidak lagi dapat mengakses
string.Length
properti tetapi Anda masih dapat meneleponobject.GetHashCode()
. Bandingkan ini dengan koleksi int:Untuk mengonversikan ini ke
IEnumerable<object>
data, harus dikonversi dengan memasukkan int:Konversi ini membutuhkan lebih dari sekadar pemeran.
sumber
this
merujuk ke struct yang bidangnya overlay mereka dari objek tumpukan yang menyimpannya, daripada merujuk ke objek yang menyimpannya. Tidak ada cara bersih untuk instance tipe nilai kotak untuk mendapatkan referensi ke objek tumpukan melampirkan.Saya pikir semuanya dimulai dari definisi
LSP
(Prinsip Pergantian Liskov), yang meliputi:Namun tipe nilai, misalnya
int
tidak bisa menjadi penggantiobject
inC#
. Buktikan sangat sederhana:Ini mengembalikan
false
bahkan jika kita menetapkan "referensi" yang sama ke objek.sumber
int
bukan subtipeobject
sehingga prinsip tersebut tidak berlaku. "Bukti" Anda bergantung pada representasi perantaraInteger
, yang merupakan subtipe dariobject
dan untuk mana bahasa tersebut memiliki konversi tersirat (object obj1=myInt;
sebenarnya diperluas keobject obj1=new Integer(myInt)
;).int
bukan subtipe dariobject
. Selain itu, LSP tidak berlaku karenamyInt
,obj1
danobj2
merujuk ke tiga objek berbeda: satuint
dan dua (tersembunyi)Integer
s.int
adalah alias untuk BCLSystem.Int32
, yang sebenarnya merupakan subtipe dariobject
(alias dariSystem.Object
). Sebenarnya,int
kelas dasar adalahSystem.ValueType
siapa kelas dasar ituSystem.Object
. Cobalah mengevaluasi ekspresi berikut dan lihat:typeof(int).BaseType.BaseType
. AlasanReferenceEquals
mengembalikan false di sini adalah bahwaint
kotak itu menjadi dua kotak yang terpisah, dan identitas masing-masing kotak berbeda untuk kotak lainnya. Jadi dua operasi tinju selalu menghasilkan dua objek yang tidak pernah identik, terlepas dari nilai kotaknya.System.Int32
atauList<String>.Enumerator
) sebenarnya mewakili dua jenis hal: tipe lokasi penyimpanan, dan tipe objek tumpukan (kadang-kadang disebut "tipe nilai kotak"). Lokasi penyimpanan yang jenisnya berasalSystem.ValueType
akan menampung bekas; tumpukan objek yang tipenya juga akan memegang yang terakhir. Dalam sebagian besar bahasa, pemeran pelebaran ada dari yang pertama, dan yang lebih kecil dari yang terakhir ke yang terakhir. Perhatikan bahwa sementara tipe nilai kotak memiliki deskriptor tipe yang sama dengan lokasi penyimpanan tipe nilai, ...Itu datang ke detail implementasi: Jenis nilai diimplementasikan secara berbeda untuk jenis referensi.
Jika Anda memaksakan tipe nilai untuk diperlakukan sebagai tipe referensi (yaitu kotak mereka, misalnya dengan merujuk mereka melalui antarmuka) Anda bisa mendapatkan varians.
Cara termudah untuk melihat perbedaannya adalah dengan mempertimbangkan
Array
: sebuah array tipe Nilai disatukan dalam memori secara bersamaan (langsung), di mana sebagai array tipe Referensi hanya memiliki referensi (pointer) yang berdekatan dalam memori; objek yang ditunjuk dialokasikan secara terpisah.Masalah (terkait) lainnya (*) adalah bahwa (hampir) semua tipe Referensi memiliki representasi yang sama untuk tujuan varians dan banyak kode tidak perlu mengetahui perbedaan antara jenis, sehingga co-dan kontra-varians dimungkinkan (dan mudah diimplementasikan - seringkali hanya dengan menghilangkan pemeriksaan tipe tambahan).
(*) Ini mungkin terlihat masalah yang sama ...
sumber