4-Bob: Tur Asoslari
Turlar bilan ishlashning asosiy tamoyillari, casting, nomlar fazosi va ish vaqtida turlarning o'zaro bog'lanishi
Ushbu bobda men turlar bilan ishlash va umumiy til ish vaqti muhiti (CLR) uchun asosiy bo'lgan ma'lumotlarni taqdim qilaman. Xususan, men har bir tur ega bo'lishi kutilgan minimal xatti-harakatlar to'plamini muhokama qilaman. Shuningdek, tur xavfsizligini, nomlar fazosini, assemblylarni va obyektlarni bir turdan boshqasiga casting qilishning turli usullarini tasvirlayman. Nihoyat, men ushbu bobni turlar, obyektlar, oqim steklari va boshqariladigan heap (managed heap) ish vaqtida bir-biriga qanday bog'lanishi haqidagi tushuntirish bilan yakunlayman.
Barcha Turlar System.Object dan Hosila Bo'ladi
Ish vaqti muhiti har bir turni oxir-oqibat System.Object turidan hosila bo'lishini talab qiladi. Bu quyidagi ikkita tur ta'rifi bir xil ekanligini anglatadi:
// Bilvosita System.Object dan hosila bo'lgan
class Employee {
...
}
// Aniq System.Object dan hosila bo'lgan
class Employee : System.Object {
...
}
Barcha turlar oxir-oqibat System.Object dan hosila bo'lganligi sababli, har bir tur obyektining minimal metodlar to'plamiga ega bo'lishi kafolatlanadi. Aniqrog'i, System.Object klassi 4-1 jadvalda sanab o'tilgan ochiq instansiya metodlarini taklif qiladi.
System.Object ning Ochiq Metodlari
| Ochiq metod | Tavsif |
|---|---|
Equals |
Agar ikki obyekt bir xil qiymatga ega bo'lsa, true qaytaradi. Bu metod haqida ko'proq ma'lumot uchun 5-bobning "Obyekt Tengligi va Identifikatsiyasi" bo'limiga qarang, "Primitiv, Havola va Qiymat Turlari." |
GetHashCode |
Ushbu obyektning qiymati uchun xesh kodni qaytaradi. Agar obyektlar Dictionary kabi xesh jadval to'plamida kalit sifatida ishlatilishi kerak bo'lsa, tur ushbu metodni qayta yozishi (override) kerak. Metod yaxshi taqsimotni ta'minlashi lozim. Afsuski, bu metod Object da aniqlangan, chunki ko'pchilik turlar hech qachon xesh jadvalda kalit sifatida ishlatilmaydi; bu metod interfeys sifatida aniqlangan bo'lishi kerak edi. Ko'proq ma'lumot uchun 5-bobning "Obyekt Xesh Kodlari" bo'limiga qarang. |
ToString |
Standart holda turning to'liq nomini qaytaradi (this.GetType().FullName). Biroq, bu metodni qanday bo'lsa shunday qoldirmaslik odatiy holdir — u obyektning holatini ifodalovchi String obyektini qaytarishi uchun qayta yoziladi. Masalan, Boolean va Int32 kabi asosiy turlar bu metodni qayta yozib, o'z qiymatlarining matnli ko'rinishini qaytaradi. Shuningdek, bu metodni nosozliklarni tuzatish (debugging) maqsadida qayta yozish ham keng tarqalgan. E'tibor bering, ToString chaqiruvchi oqim (calling thread) bilan bog'langan CultureInfo dan xabardor bo'lishi kutiladi. 14-bob, "Belgilar, Satrlar va Matn bilan Ishlash," ToString haqida batafsilroq gapiradi. |
GetType |
Obyektni chaqirish uchun ishlatilgan turni identifikatsiyalovchi Type dan hosila bo'lgan obyektning instansiyasini qaytaradi. Qaytarilgan Type obyekti reflektion (reflection) klasslari bilan birgalikda obyekt turi haqidagi metadata ma'lumotlarini olish uchun ishlatilishi mumkin. Reflektion 23-bobda, "Assembly Yuklash va Reflektion," batafsil muhokama qilinadi. GetType metodi novirtual bo'lib, bu esa klassning bu metodni qayta yozib, o'z turi haqida yolg'on aytishining oldini oladi, tur xavfsizligini buzadi. |
System.Object ning Himoyalangan (Protected) Metodlari
Bundan tashqari, System.Object dan hosila bo'lgan turlar 4-2 jadvalda sanab o'tilgan himoyalangan metodlarga kirish huquqiga ega.
| Himoyalangan metod | Tavsif |
|---|---|
MemberwiseClone |
Bu novirtual metod turning yangi instansiyasini yaratadi va yangi obyektning instansiya maydonlarini this obyektining instansiya maydonlariga o'xshash qilib o'rnatadi. Yangi instansiyaga havola qaytariladi. |
Finalize |
Bu virtual metod axlat yig'uvchi (garbage collector) obyekt axlat ekanligini aniqlagan va obyekt uchun ajratilgan xotira qaytadan talab qilinishidan oldin chaqiriladi. Yig'ilganda tozalash talab qiladigan turlar bu muhim metodni qayta yozishi kerak. Men bu muhim metod haqida 21-bobda, "Boshqariladigan Heap va Axlat Yig'ish," batafsilroq gaplashaman. |
new Operatori
CLR barcha obyektlarni new operatori yordamida yaratishni talab qiladi. Quyidagi qator Employee obyektini qanday yaratishni ko'rsatadi:
Employee e = new Employee("ConstructorParam1");
new operatori nima qilishini ko'rib chiqaylik:
- Baytlar sonini hisoblaydi. U turda va uning barcha asosiy (base) turlarida, shu jumladan
System.Objectda (o'z instansiya maydonlari yo'q) aniqlangan barcha instansiya maydonlari tomonidan talab qilinadigan baytlar sonini hisoblaydi. Heap dagi har bir obyekt ba'zi qo'shimcha a'zolarni ham talab qiladi — tur obyekti ko'rsatkichi (type object pointer) va sinxronizatsiya bloki indeksi (sync block index) — ular CLR tomonidan obyektni boshqarish uchun ishlatiladi. Bu qo'shimcha a'zolar uchun baytlar obyekt hajmiga qo'shiladi. - Xotirani ajratadi. U boshqariladigan heap dan belgilangan tur uchun kerakli baytlar sonini ajratib xotira ajratadi; barcha bu baytlar nolga (0) o'rnatiladi.
- Ko'rsatkich va indeksni ishga tushiradi. U obyektning tur obyekti ko'rsatkichi va sinxronizatsiya bloki indeksini ishga tushiradi.
- Konstruktorni chaqiradi. Turning instansiya konstruktori chaqiriladi, unga
newchaqiruvida ko'rsatilgan barcha argumentlar (oldingi misolda"ConstructorParam1"qatori) uzatiladi. Ko'pchilik kompilyatorlar avtomatik ravishda konstruktor ichida asosiy klassning konstruktorini chaqirish uchun kod ishlab chiqaradi. Har bir konstruktor o'zi chaqirilayotgan turning aniqlagan instansiya maydonlarini ishga tushirish uchun javobgardir. Oxir-oqibat,System.Objectning konstruktori chaqiriladi va bu konstruktor metodi hech narsa qilmaydi, shunchaki qaytadi.
new barcha bu operatsiyalarni bajargandan so'ng, u yangi yaratilgan obyektga havolani (yoki ko'rsatkichni) qaytaradi. Oldingi kod misolida bu havola e o'zgaruvchisiga saqlanadi va u Employee turiga tegishli.
Aytgancha, new operatorining komplementar (teskari) delete operatori yo'q; ya'ni, obyekt uchun ajratilgan xotirani aniq bo'shatishning iloji yo'q. CLR axlat yig'ish (garbage-collected) muhitidan foydalanadi (21-bobda tasvirlangan), bu muhit obyektlar endi ishlatilmayotgani yoki ularga kirish imkoni yo'qligini avtomatik aniqlaydi va obyekt xotirasini avtomatik bo'shatadi.
Turlar O'rtasida Casting (Tur Almashish)
CLR ning eng muhim xususiyatlaridan biri tur xavfsizligi (type safety) dir. Ish vaqtida CLR har doim obyekt qaysi turga tegishli ekanligini biladi. Siz har doim GetType metodini chaqirib obyektning aniq turini aniqlashingiz mumkin. Bu metod novirtual bo'lganligi sababli, biror tur bu metodni qayta yozib boshqa tur nomini ko'rsatishi mumkin emas. Masalan, Employee turi GetType metodni qayta yozib, uni SuperHero turini qaytaradigan qilib o'zgartirolmaydi.
Dasturchilar tez-tez obyektlarni turli turlarga casting qilish zarurati bilan to'qnash kelishadi. CLR sizga obyektni o'z turiga yoki uning istalgan asosiy (base) turiga casting qilish imkonini beradi. Qaysi dasturlash tili ishlatilishi casting operatsiyalarini dasturchiga qanday ochishni belgilaydi. Masalan, C# asosiy turlarga casting qilish uchun hech qanday maxsus sintaksis talab qilmaydi, chunki asosiy turlarga casting xavfsiz implicit (yashirin) almashtirish hisoblanadi. Biroq, C# dasturchidan obyektni uning hosila (derived) turlarining istalganiga aniq casting qilishni talab qiladi, chunki bunday casting ish vaqtida muvaffaqiyatsiz bo'lishi mumkin. Quyidagi kod asosiy va hosila turlarga casting ni ko'rsatadi:
// Bu tur bilvosita System.Object dan hosila bo'lgan.
internal class Employee {
...
}
public sealed class Program {
public static void Main() {
// Cast kerak emas chunki new Employee obyektini qaytaradi
// va Object Employee ning asosiy turidir.
Object o = new Employee();
// Cast kerak chunki Employee Object dan hosila bo'lgan.
// Boshqa tillar (masalan Visual Basic) bu castni
// talab qilmasligi mumkin.
Employee e = (Employee) o;
}
}
Bu misol kompilyatoringiz kodingizni kompilyatsiya qilishi uchun nima kerakligini ko'rsatadi. Endi ish vaqtida nima sodir bo'lishini tushuntiraman. Ish vaqtida CLR casting operatsiyalarini tekshirib, castlarning har doim obyektning haqiqiy turiga yoki uning istalgan asosiy turiga amalga oshirilishini ta'minlaydi. Masalan, quyidagi kod kompilyatsiya bo'ladi, lekin ish vaqtida InvalidCastException tashlanadi:
internal class Employee {
...
}
internal class Manager : Employee {
...
}
public sealed class Program {
public static void Main() {
// Manager obyektini yaratib, PromoteEmployee ga uzatish.
// Manager IS-A Object: PromoteEmployee muvaffaqiyatli ishlaydi.
Manager m = new Manager();
PromoteEmployee(m);
// DateTime obyektini yaratib, PromoteEmployee ga uzatish.
// DateTime Employee dan hosila bo'lgan EMAS. PromoteEmployee
// System.InvalidCastException istisnosini tashlaydi.
DateTime newYears = new DateTime(2013, 1, 1);
PromoteEmployee(newYears);
}
public static void PromoteEmployee(Object o) {
// Bu nuqtada kompilyator o ning aniq qaysi turga
// tegishli ekanligini bilmaydi. Shuning uchun kompilyator
// kodni kompilyatsiya qilishga ruxsat beradi.
// Biroq, ish vaqtida CLR o ning qaysi turga tegishli
// ekanligini biladi (har safar cast amalga oshirilganda)
// va obyektning turi Employee yoki Employee dan
// hosila bo'lgan tur ekanligini tekshiradi.
Employee e = (Employee) o;
...
}
}
Main metodida Manager obyekti yaratiladi va PromoteEmployee ga uzatiladi. Bu kod kompilyatsiya bo'ladi va muvaffaqiyatli ishlaydi, chunki Manager oxir-oqibat Object dan hosila bo'lgan, bu esa PromoteEmployee kutgan narsadir. PromoteEmployee ichiga kirgandan so'ng, CLR o ning Employee yoki Employee dan hosila bo'lgan turga ishora qilishini tasdiqlaydi. Manager Employee dan hosila bo'lganligi sababli, CLR castni amalga oshiradi va PromoteEmployee ning bajarilishini davom ettirishga ruxsat beradi.
PromoteEmployee qaytgandan so'ng, Main DateTime obyektini yaratadi va uni PromoteEmployee ga uzatadi. DateTime ham Object dan hosila bo'lgan va kompilyator PromoteEmployee ni chaqiradigan kodni muammosiz kompilyatsiya qiladi. Biroq, PromoteEmployee ichida CLR castni tekshiradi va o ning DateTime obyektiga ishora qilayotganini va shuning uchun Employee ham, Employee dan hosila bo'lgan biron tur ham emasligini aniqlaydi. Bu nuqtada CLR castga ruxsat bermaydi va System.InvalidCastException istisnosini tashlaydi.
Agar CLR castga ruxsat berganida, tur xavfsizligi bo'lmas edi va natijalar bashorat qilib bo'lmaydigan bo'lar edi, shu jumladan ilova nosozliklari va turlarning boshqa turlarni taqlid qilish qobiliyati natijasida yuzaga keladigan xavfsizlik buzilishlari. Tur taqlid qilish ko'plab xavfsizlik buzilishlarining sababidir va ilovaning barqarorligi va mustahkamligiga putur yetkazadi. Shuning uchun tur xavfsizligi CLR ning nihoyatda muhim qismidir.
Aytgancha, PromoteEmployee metodini e'lon qilishning to'g'ri usuli parametr sifatida Object turi o'rniga Employee turini belgilash bo'lardi — kompilyator kompilyatsiya vaqtida xatoni aniqlash orqali dasturchini ish vaqtidagi istisno muammosini topgunicha kutishdan saqlardi. Men Object dan foydalandim, chunki C# kompilyatori va CLR casting va tur xavfsizligi bilan qanday ishlashini ko'rsatishni xohlardim.
C# ning is va as Operatorlari Bilan Casting
C# tilida casting qilishning yana bir usuli — is operatoridan foydalanish. is operatori obyektning berilgan turga mos kelishini tekshiradi va baholash natijasi Boolean: true yoki false. is operatori hech qachon istisno tashlamaydi. Quyidagi kod buni ko'rsatadi:
Object o = new Object();
Boolean b1 = (o is Object); // b1 true bo'ladi.
Boolean b2 = (o is Employee); // b2 false bo'ladi.
Agar obyekt havolasi null bo'lsa, is operatori har doim false qaytaradi, chunki turini tekshirish uchun hech qanday obyekt mavjud emas.
is operatori odatda quyidagicha ishlatiladi:
if (o is Employee) {
Employee e = (Employee) o;
// 'if' ifodasining qolgan qismida e dan foydalaning.
}
Bu kodda CLR aslida obyektning turini ikki marta tekshirmoqda: is operatori avval o ning Employee turiga mos kelishini tekshiradi. Agar mos bo'lsa, if ifodasi ichida CLR o ning Employee ga ishora qilishini yana tasdiqlaydi. CLR ning tur tekshiruvi xavfsizlikni oshiradi, lekin u albatta samaradorlik narxiga ega, chunki CLR o o'zgaruvchisi ko'rsatayotgan obyektning haqiqiy turini aniqlashi va keyin CLR meros ierarxiyasini ko'rib chiqib, har bir asosiy turni belgilangan tur (Employee) bilan tekshirishi kerak. Bu dasturlash paradigmasi juda keng tarqalgan bo'lganligi sababli, C# bu kodni soddalashtirish va samaradorligini oshirish uchun as operatorini taklif qiladi.
Employee e = o as Employee;
if (e != null) {
// 'if' ifodasining qolgan qismida e dan foydalaning.
}
Bu kodda CLR o ning Employee turiga mos kelishini tekshiradi va agar mos bo'lsa, as bir xil obyektga null bo'lmagan havolani qaytaradi. Agar o Employee turiga mos bo'lmasa, as operatori null qaytaradi. E'tibor bering, as operatori CLR ga obyektning turini faqat bir marta tekshirishga sabab bo'ladi. if ifodasi shunchaki e ning null yoki null emasligini tekshiradi; bu tekshiruv obyektning turini tekshirishdan ko'ra tezroq amalga oshirilishi mumkin.
as operatori casting bilan bir xil ishlaydi, faqat u hech qachon istisno tashlamaydi. Agar obyektni cast qilib bo'lmasa, natija null bo'ladi. Natijada havolaning null yoki null emasligini tekshirishni xohlaysiz, chunki natijadagi havolani shunchaki ishlatishga urinish System.NullReferenceException tashlanishiga olib keladi. Quyidagi kod buni ko'rsatadi:
Object o = new Object(); // Yangi Object obyekti yaratiladi
Employee e = o as Employee; // o ni Employee ga cast qilish
// Yuqoridagi cast muvaffaqiyatsiz: istisno tashlanmaydi, lekin e null ga o'rnatiladi.
e.ToString(); // e ga kirish NullReferenceException tashlaydi.
Tur Xavfsizligi Viktorinasi
Hozirgacha taqdim etilgan barcha narsalarni tushunganingizga ishonch hosil qilish uchun quyidagi viktorinani oling. Faraz qiling, quyidagi ikkita klass ta'rifi mavjud:
internal class B { // Asosiy klass (Base class)
}
internal class D : B { // Hosila klass (Derived class)
}
Endi quyidagi 4-3 jadvaldagi C# kod qatorlarini ko'rib chiqing. Har bir qator uchun qator muvaffaqiyatli kompilyatsiya bo'lib ishlashini (jadvalda OK deb belgilangan), kompilyatsiya vaqtida xato (CTE) yuzaga kelishini yoki ish vaqtida xato (RTE) bo'lishini aniqlang.
| Ifoda (Statement) | OK | CTE | RTE |
|---|---|---|---|
Object o1 = new Object(); |
✓ | ||
Object o2 = new B(); |
✓ | ||
Object o3 = new D(); |
✓ | ||
Object o4 = o3; |
✓ | ||
B b1 = new B(); |
✓ | ||
B b2 = new D(); |
✓ | ||
D d1 = new D(); |
✓ | ||
B b3 = new Object(); |
✓ | ||
D d2 = new Object(); |
✓ | ||
B b4 = d1; |
✓ | ||
D d3 = b2; |
✓ | ||
D d4 = (D) d1; |
✓ | ||
D d5 = (D) b2; |
✓ | ||
D d6 = (D) b1; |
✓ | ||
B b5 = (B) o1; |
✓ | ||
B b6 = (D) b2; |
✓ |
Bu jadvaldan muhim xulosa shuki: kompilyator har doim casting operatsiyalarini kompilyatsiya vaqtida tekshiradi — agar tur ierarxiyasida cast mumkin bo'lmasa, CTE (kompilyatsiya xatosi) yuzaga keladi. Agar kompilyator casting to'g'ri bo'lishi mumkinligini aniqlasa, lekin kafolatlay olmasa, kod kompilyatsiya bo'ladi va CLR ish vaqtida tekshiradi — agar cast noto'g'ri bo'lsa, RTE (ish vaqtida xato, InvalidCastException) yuzaga keladi.
C# turga konversiya operatori metodlarini aniqlashga ruxsat beradi, bu 8-bobning "Konversiya Operatori Metodlari" bo'limida muhokama qilinadi. Bu metodlar faqat cast ifodasi ishlatilganda chaqiriladi; ular C# ning as yoki is operatori ishlatilganda hech qachon chaqirilmaydi.
Nomlar Fazosi va Assemblylar
Nomlar fazosi (namespace) tegishli turlarni mantiqiy guruhlash imkonini beradi va dasturchilar odatda ulardan ma'lum turni osongina topish uchun foydalanadi. Masalan, System.Text nomlar fazosi satr manipulyatsiyasini amalga oshirish uchun bir qancha turlarni aniqlaydi va System.IO nomlar fazosi kiritish/chiqarish (I/O) operatsiyalarini amalga oshirish uchun bir qancha turlarni aniqlaydi. Quyida System.IO.FileStream obyekti va System.Text.StringBuilder obyektini yaratadigan kod keltirilgan:
public sealed class Program {
public static void Main() {
System.IO.FileStream fs = new System.IO.FileStream(...);
System.Text.StringBuilder sb = new System.Text.StringBuilder();
}
}
using Direktivasi
Ko'rib turganingizdek, kod juda batafsil; yozishni kamaytirish uchun FileStream va StringBuilder turlariga murojaat qilishning biron qisqartirilgan usuli bo'lsa yaxshi bo'lardi. Xayriyatki, ko'plab kompilyatorlar dasturchining yozishini kamaytirish uchun mexanizmlar taklif qiladi. C# kompilyatori bu mexanizmni using direktivasi orqali taqdim qiladi. Quyidagi kod oldingi misolga teng:
using System.IO; // "System.IO." prefiksini qo'shib ko'ring
using System.Text; // "System.Text." prefiksini qo'shib ko'ring
public sealed class Program {
public static void Main() {
FileStream fs = new FileStream(...);
StringBuilder sb = new StringBuilder();
}
}
Kompilyator uchun nomlar fazosi shunchaki turning nomini uzunroq va yagona qilishning oson usuli — nomni nuqtalar bilan ajratilgan ba'zi belgilar bilan oldindan yozish. Shuning uchun kompilyator ushbu misoldagi FileStream havolasini System.IO.FileStream deb tushunadi. Xuddi shunday, kompilyator StringBuilder havolasini System.Text.StringBuilder deb talqin qiladi.
C# ning using direktivasidan foydalanish butunlay ixtiyoriydir; siz har doim turning to'liq kvalifikatsiyalangan (fully qualified) nomini yozishingiz mumkin. C# ning using direktivasi kompilyatorga tur nomi topilmaguncha turli prefikslarni sinashni buyuradi.
CLR nomlar fazosi haqida hech narsa bilmaydi. Siz turga kirishda CLR turning to'liq nomini (bu nuqtalar o'z ichiga olgan juda uzun nom bo'lishi mumkin) va turning ta'rifini o'z ichiga olgan assemblyni bilishi kerak — shunday qilib ish vaqti to'g'ri assemblyni yuklashi, turni topishi va uni boshqarishi mumkin.
Oldingi kod misolida kompilyator havola qilingan har bir tur mavjud ekanligini va kodum ushbu turni to'g'ri ishlatayotganini ta'minlashi kerak: mavjud metodlarni chaqirish, to'g'ri sondagi argumentlarni uzatish, argumentlarning to'g'ri turda ekanligini ta'minlash, metodning qaytarish qiymatini to'g'ri ishlatish va hokazo. Agar kompilyator manba fayllarda yoki havola qilingan biron assemblyda ko'rsatilgan nomli turni topa olmasa, u nom oldiga System.IO. qo'shadi va hosil bo'lgan nom mavjud turga mos kelishini tekshiradi. Agar kompilyator hali ham topilma topa olmasa, u turning nomi oldiga System.Text. qo'shadi. Oldinroq ko'rsatilgan ikkita using direktivasi menga shunchaki FileStream va StringBuilder yozish imkonini berdi — kompilyator avtomatik ravishda havolalarni System.IO.FileStream va System.Text.StringBuilder ga kengaytiradi. Siz bu qanchalik yozishni tejashini va kodingiz qanchalik toza ko'rinishini tasavvur qilishingiz mumkin.
Turning ta'rifini tekshirayotganda, kompilyatorga qaysi assemblylarni ko'rib chiqishini 2-bobda muhokama qilingan /reference kompilyator kaliti va 3-bobda muhokama qilingan "Kuchli Nomlangan Assemblylar" yordamida aytish kerak. Kompilyator turning ta'rifini qidirib havola qilingan barcha assemblylarni skanerlaydi.
Nomlar Fazosi Noaniqligini Hal Qilish
Kompilyatorlar nomlar fazolarini qanday ko'rib chiqishi bilan bog'liq ba'zi potensial muammolar mavjud: bir xil nomdagi turlar turli nomlar fazolarida mavjud bo'lishi mumkin. Microsoft sizga turlar uchun yagona nomlar belgilashni tavsiya qiladi. Biroq, ba'zi hollarda bu mumkin emas. Sizning ilovangiz Microsoft yaratgan komponent va Wintellect yaratgan boshqa bir komponentdan foydalanishi mumkin. Bu ikkala kompaniya ham Widget nomli turni taklif qilishi mumkin — Microsoft'ning Widget i bir narsa qilsa, Wintellect'ning Widget i boshqa narsa qiladi. Bu stsenariyda siz ikkala widget o'rtasida ularning to'liq kvalifikatsiyalangan nomlarini ishlatib farqlashingiz mumkin. Quyidagi kodda C# kompilyatori xato xabar beradi: error CS0104: 'Widget' is an ambiguous reference between 'Microsoft.Widget' and 'Wintellect.Widget'.
using Microsoft; // "Microsoft." prefiksini qo'shib ko'ring
using Wintellect; // "Wintellect." prefiksini qo'shib ko'ring
public sealed class Program {
public static void Main() {
Widget w = new Widget();// Noaniq (ambiguous) havola
}
}
Noaniqlikni bartaraf etish uchun, kompilyatorga qaysi Widget ni yaratishni xohlayotganingizni aniq aytishingiz kerak:
using Microsoft; // "Microsoft." prefiksini qo'shib ko'ring
using Wintellect; // "Wintellect." prefiksini qo'shib ko'ring
public sealed class Program {
public static void Main() {
Wintellect.Widget w = new Wintellect.Widget(); // Noaniq emas
}
}
C# ning using direktivasining yana bir shakli sizga bitta tur yoki nomlar fazosi uchun taxallus (alias) yaratish imkonini beradi. Bu, agar siz nomlar fazosidan faqat bir nechtata turlarni ishlatsangiz va global nomlar fazosini uning barcha turlari bilan to'ldirmoqchi bo'lmasangiz qulaydir. Quyidagi kod oldingi koddagi noaniqlik muammosini hal qilishning yana bir usulini ko'rsatadi:
// WintellectWidget belgisini Wintellect.Widget turining taxallusi sifatida aniqlang
using WintellectWidget = Wintellect.Widget;
public sealed class Program {
public static void Main() {
WintellectWidget w = new WintellectWidget(); // Endi xato yo'q
}
}
Bu noaniqlikni hal qilish usullari foydali, lekin ba'zi stsenariylarda yanada chuqurroq yondashish kerak. Tasavvur qiling, Australian Boomerang Company (ABC) va Alaskan Boat Corporation (ABC) har biri o'z assemblylarida yetkazishni rejalashtirgan BuyProduct nomli turni yaratmoqda. Ikkala kompaniya ham ehtimol ABC nomli nomlar fazosi yaratishi mumkin va u BuyProduct turini o'z ichiga olishi mumkin. Dasturlash tili nafaqat nomlar fazolari, balki assemblylar o'rtasida ham farqlash uchun yo'l taqdim qilmasa, ikkala bumerang va qayiqlarni sotib olishga urinadigan ilova dasturchi uchun muammoli bo'ladi. Xayriyatki, C# kompilyatori extern alias (tashqi taxallus) deb ataladigan xususiyatni taklif qiladi, bu sizga bu kamdan-kam uchraydigan muammo atrofida ishlash usulini beradi. Tashqi taxalluslar shuningdek sizga bir xil assemblyning ikki (yoki undan ko'p) turli versiyalaridan bitta turga kirish usulini ham beradi.
Nomlar Fazosi Yaratish
Kutubxonangizda uchinchi tomonlar foydalanishini kutadigan turlarni loyihalayotganingizda, bu turlarni nomlar fazosida aniqlashingiz kerak — shunday qilib kompilyatorlar ularni osongina ajrata oladi. Aslida, nomlarning to'qnashuvini kamaytirish uchun to'liq kompaniya nomingizni (qisqartma yoki abbreviatura emas) yuqori darajadagi nomlar fazosi nomi sifatida ishlatishingiz kerak. Microsoft .NET Framework SDK hujjatlariga murojaat qilsangiz, Microsoft Microsoft-ga xos turlar uchun "Microsoft" nomlar fazosidan foydalanganini ko'rishingiz mumkin. (Microsoft.CSharp, Microsoft.VisualBasic va Microsoft.Win32 nomlar fazolarini misol sifatida ko'ring.)
Nomlar fazosi yaratish shunchaki kodingizga nomlar fazosi deklaratsiyasini yozish masalasidir (C# da):
namespace CompanyName {
public sealed class A { // TypeDef: CompanyName.A
}
namespace X {
public sealed class B { ... } // TypeDef: CompanyName.X.B
}
}
Oldingi klass ta'riflarining o'ng tomonidagi izoh turning haqiqiy nomini ko'rsatadi — kompilyator tur ta'rifi metadata jadvaliga chiqaradi; bu CLR nuqtai nazaridan turning haqiqiy nomidir.
Ba'zi kompilyatorlar umuman nomlar fazolarini qo'llab-quvvatlamaydi va boshqa kompilyatorlar "nomlar fazosi" tushunchasi ma'lum bir til uchun nimani anglatishini belgilash huquqiga ega. C# da namespace direktivasi shunchaki kompilyatorga manba kodida paydo bo'ladigan har bir tur nomining oldiga nomlar fazosi nomini qo'shishni aytadi — shunday qilib dasturchilar kamroq yozishi mumkin.
Nomlar Fazosi va Assemblylar Qanday Bog'lanadi
Shuni bilish kerakki, nomlar fazosi va turni amalga oshiruvchi assembly (fayl) bir-biriga bog'liq bo'lishi shart emas. Xususan, bitta nomlar fazosiga tegishli turli turlar bir nechta assemblylarda amalga oshirilishi mumkin. Masalan, System.IO.FileStream turi MSCorLib.dll assemblyda, System.IO.FileSystemWatcher turi esa System.dll assemblyda amalga oshirilgan.
Bitta assembly turli nomlar fazolaridagi turlarni o'z ichiga olishi mumkin. Masalan, System.Int32 va System.Text.StringBuilder turlari ikkalasi ham MSCorLib.dll assemblyda joylashgan.
.NET Framework SDK hujjatlarida turni qidirsangiz, hujjatlar turning tegishli nomlar fazosi va turni amalga oshirgan assemblyni aniq ko'rsatadi. Masalan, ResXFileRef turi System.Resources nomlar fazosining bir qismi va u System.Windows.Forms.dll assemblysida amalga oshirilgan. Bu turga havola qiluvchi kod kompilyatsiya qilish uchun using System.Resources; direktivasini manba kodingizga qo'shishingiz va /r:System.Windows.Forms.dll kompilyator kalitini ishlatishingiz kerak.
Ish Vaqtida Narsalar Qanday Bog'lanadi
Ushbu bo'limda men ish vaqtida turlar, obyektlar, oqim steki (thread's stack) va boshqariladigan heap (managed heap) o'rtasidagi munosabatni tushuntiraman. Bundan tashqari, statik metodlarni, instansiya metodlarini va virtual metodlarni chaqirish o'rtasidagi farqni ham tushuntiraman. Keling, kompyuterlarning ba'zi asosiy tushunchalaridan boshlaylik.
Oqim Steki (Thread Stack)
Men tasvirlashga kirishayotgan narsa CLR ga xos emas umuman, lekin men uni shunday tasvirlayman ki, biz asosiy poydevorga ega bo'laylik va keyin muhokamani CLR-ga xos ma'lumotlarni kiritish uchun o'zgartiraylik.
4-2 rasmda CLR yuklangan bitta Windows jarayoni ko'rsatilgan. Bu jarayonda ko'plab oqimlar bo'lishi mumkin. Oqim yaratilganda, unga 1 MB stek ajratiladi. Bu stek metod uchun argumentlarni uzatish va metod ichida aniqlangan mahalliy o'zgaruvchilar uchun xotira ajratish maqsadida ishlatiladi. 4-2 rasmda bitta oqimning steki uchun ajratilgan xotira ko'rsatilgan (o'ng tomonda). Steklar yuqori xotira manzillaridan pastga qarab quriladi. Rasmda ushbu oqim ba'zi kodni bajargan va uning stekida allaqachon ba'zi ma'lumotlar mavjud (rasmning yuqori qismidagi to'ldirilgan maydon sifatida ko'rsatilgan). Endi tasavvur qiling, oqim M1 metodini chaqiradigan biron kodni bajardi.
Eng oddiy metodlarning barchasi metodni ishga tushirishdan oldin ishga tushiruvchi prolog kodi va metod o'z ishini bajargandan so'ng metodni chaqiruvchisiga qaytishga imkon beruvchi epilog kodi o'z ichiga oladi. M1 metodi bajarila boshlaganda, uning prolog kodi mahalliy name o'zgaruvchisi uchun oqimning stekidan xotira ajratadi (4-3 rasmga qarang).
void M1() {
String name = "Joe";
M2(name);
...
return;
}
Keyin M1 M2 metodini chaqiradi, name mahalliy o'zgaruvchisini argument sifatida uzatadi. Bu name mahalliy o'zgaruvchisining manzilini stekga surishga (push) sabab bo'ladi (4-4 rasmga qarang). M2 metodi ichida stek joylashuvi s nomli parametr o'zgaruvchisi yordamida identifikatsiya qilinadi. (E'tibor bering, ba'zi arxitekturalar argumentlarni samaradorlikni oshirish uchun registrlar orqali uzatadi, lekin bu muhokama uchun bu farq muhim emas.) Shuningdek, metod chaqirilganda, chaqirilgan metod qaytishi kerak bo'lgan manzilni ko'rsatuvchi qaytish manzili (return address) stekga suriladi (bu ham 4-4 rasmda ko'rsatilgan).
void M2(String s) {
Int32 length = s.Length;
Int32 tally;
...
return;
}
M2 metodi bajarila boshlaganda, uning prolog kodi mahalliy length va tally o'zgaruvchilari uchun oqim stekidan xotira ajratadi (4-5 rasmga qarang). Keyin M2 metodi ichidagi kod bajariladi. Oxir-oqibat, M2 o'zining return ifodasiga yetib keladi, bu CPU ning ko'rsatma ko'rsatkichini stekdagi qaytish manziliga o'rnatadi va M2 ning stek freymi ochiladi (unwound) — u 4-3 rasmdagidek holatga qaytadi. Bu nuqtada M1 M2 chaqiruvidan keyin darhol keladigan kodni bajarishda davom etadi va uning stek freymi M1 uchun zarur holatni aniq aks ettiradi.
Oxir-oqibat, M1 o'z chaqiruvchisiga qaytadi — CPU ning ko'rsatma ko'rsatkichini qaytish manziliga (rasmlarda ko'rsatilmagan, lekin u stekdagi name argumentidan biroz yuqorida bo'lishi kerak) o'rnatadi va M1 ning stek freymi ochiladi — u 4-2 rasmdagidek holatga qaytadi. Bu nuqtada M1 ni chaqirgan metod kodni bajarishda davom etadi va uning stek freymi o'sha metod uchun zarur holatni aniq aks ettiradi.
CLR va Tur Obyektlari
Endi muhokamani CLR yo'nalishiga buraylik. Faraz qilaylik, bizda quyidagi ikkita klass ta'rifi bor:
internal class Employee {
public Int32 GetYearsEmployed() { ... }
public virtual String GetProgressReport() { ... }
public static Employee Lookup(String name) { ... }
}
internal sealed class Manager : Employee {
public override String GetProgressReport() { ... }
}
Windows jarayonimiz ishga tushdi, CLR unga yuklandi, boshqariladigan heap ishga tushirildi va oqim yaratildi (1 MB stek maydoniga ega). Bu oqim allaqachon ba'zi kodlarni bajardi va endi M3 metodini chaqirishga qaror qildi. M3 metodi CLR qanday ishlashini ko'rsatadigan kodni o'z ichiga oladi; bu siz odatda yozadigan kod emas, chunki u amalda foydali hech narsa qilmaydi.
void M3() {
Employee e;
Int32 year;
e = new Manager();
e = Employee.Lookup("Joe");
year = e.GetYearsEmployed();
e.GetProgressReport();
}
Just-in-time (JIT) kompilyator M3 ning Oraliq Til (IL) kodini mahalliy CPU ko'rsatmalariga aylantirganida, u M3 ichida havola qilingan barcha turlarni aniqlaydi: Employee, Int32, Manager va String ("Joe" tufayli). Bu paytda CLR bu turlarni aniqlagan assemblylarning yuklangan ekanligini ta'minlaydi. Keyin, assembly metadatasidan foydalanib, CLR bu turlar haqidagi ma'lumotlarni chiqaradi va turlarning o'zlarini ifodalash uchun ba'zi ma'lumotlar tuzilmalarini yaratadi.
Employee va Manager turlari uchun ma'lumotlar tuzilmalari 4-7 rasmda ko'rsatilgan. Bu oqim M3 chaqirilishidan oldin allaqachon ba'zi kodlarni bajarganligi sababli, Int32 va String tur obyektlari allaqachon yaratilganligini faraz qilaylik (chunki bular tez-tez ishlatiladigan turlar) va shuning uchun ularni rasmda ko'rsatmayman.
Bu tur obyektlarini bir oz ko'rib chiqaylik. Ushbu bobda avvalroq muhokama qilganidek, heap dagi barcha obyektlar ikkita qo'shimcha a'zoni o'z ichiga oladi: tur obyekti ko'rsatkichi (type object pointer) va sinxronizatsiya bloki indeksi (sync block index). Ko'rib turganingizdek, Employee va Manager tur obyektlarining ikkalasida ham bu a'zolar mavjud. Tur aniqlanganida, siz uning ichida statik ma'lumotlar maydonlarini aniqlashingiz mumkin. Bu statik ma'lumotlar maydonlarini qo'llab-quvvatlovchi baytlar tur obyektlarining o'zlarida joylashgan. Nihoyat, har bir tur obyekti ichida metod jadvali mavjud — turda aniqlangan har bir metod uchun bitta yozuv. Bu 1-bobda muhokama qilingan metod jadvali. Employee turi uchta metodni aniqlayotganligi sababli (GetYearsEmployed, GetProgressReport va Lookup), Employee metod jadvalida uchta yozuv mavjud. Manager turi bitta metodni aniqlayotganligi sababli (GetProgressReport ning override'i), Manager metod jadvalida faqat bitta yozuv mavjud.
Statik Metodlarni Chaqirish
CLR barcha tur obyektlari yaratilganini va M3 uchun kod kompilyatsiya qilinganini ta'minlagandan so'ng, CLR oqimga M3 ning mahalliy kodini bajarishga ruxsat beradi. M3 ning prolog kodi bajarganda, mahalliy o'zgaruvchilar uchun xotira oqimning stekidan ajratilishi kerak — 4-8 rasmda ko'rsatilgan. Aytgancha, CLR avtomatik ravishda barcha mahalliy o'zgaruvchilarni null yoki 0 (nol) ga ishga tushiradi — bu metod prolog kodining bir qismi sifatida. Biroq, C# kompilyatori manba kodingizda aniq ishga tushirilmagan mahalliy o'zgaruvchidan o'qishga uringaningizda Use of unassigned local variable xato xabarini beradi.
Keyin, M3 o'z kodini Manager obyektini yaratish uchun bajaradi. Bu Manager turining instansiyasini boshqariladigan heap da yaratishga sabab bo'ladi — 4-9 rasmda ko'rsatilganidek. Ko'rib turganingizdek, Manager obyekti — barcha obyektlar singari — tur obyekti ko'rsatkichi va sinxronizatsiya bloki indeksiga ega. Bu obyekt shuningdek Manager turi va uning barcha asosiy klasslari (Employee va Object) tomonidan aniqlangan barcha instansiya ma'lumotlar maydonlarini saqlash uchun zarur baytlarni ham o'z ichiga oladi. Yangi obyekt heap da yaratilganda, CLR avtomatik ravishda ichki tur obyekti ko'rsatkichini obyektning mos tur obyektiga (bu holda Manager tur obyektiga) ishora qiladigan qilib ishga tushiradi. Bundan tashqari, CLR sinxronizatsiya bloki indeksini ishga tushiradi va obyektning barcha instansiya maydonlarini null yoki 0 (nol) ga o'rnatadi — bu turning konstruktorini chaqirishdan oldin amalga oshiriladi, konstruktor esa ehtimol ba'zi instansiya ma'lumotlar maydonlarini o'zgartiradi. new operatori Manager obyektining xotira manzilini qaytaradi, bu manzil oqimning stekidagi e o'zgaruvchisiga saqlanadi.
M3 dagi kodning keyingi qatori Employee ning statik Lookup metodini chaqiradi. Statik metodni chaqirganda, JIT kompilyator statik metodni aniqlagan turga mos tur obyektini topadi. Keyin, JIT kompilyator tur obyektining metod jadvalidagi chaqirilayotgan metodga tegishli yozuvni topadi, metodni JIT qiladi (agar kerak bo'lsa) va JIT qilingan kodni chaqiradi. Bizning muhokamamiz uchun, Employee ning Lookup metodi ma'lumotlar bazasidan Joe ni topadi deylik. Ma'lumotlar bazasi Joe kompaniyada menejer ekanligini ko'rsatadi deylik va shuning uchun Lookup metodi ichki ravishda heap da yangi Manager obyektini yaratadi, uni Joe uchun ishga tushiradi va bu obyektning manzilini qaytaradi. Bu manzil e mahalliy o'zgaruvchisiga saqlanadi. Bu operatsiyaning natijasi 4-10 rasmda ko'rsatilgan.
E'tibor bering, e endi avval yaratilgan birinchi Manager obyektiga endi ishora qilmaydi. Aslida, biron o'zgaruvchi ham bu obyektga ishora qilmaganligi sababli, u kelajakda axlat yig'ilganida (garbage collected) qaytarib olinadigan asosiy nomzoddir — shu o'zgaruvchining ishlatgan xotirasini bo'shatib beradi.
Novirtual Instansiya Metodlarini Chaqirish
M3 dagi kodning keyingi qatori Employee ning novirtual instansiya GetYearsEmployed metodini chaqiradi. Novirtual instansiya metodini chaqirganda, JIT kompilyator chaqiruv uchun ishlatiladigan o'zgaruvchining turiga mos tur obyektini topadi. Bu holda, e o'zgaruvchisi Employee turi sifatida aniqlangan. (Agar Employee turi chaqirilayotgan metodni aniqlamagan bo'lsa, JIT kompilyator Object ga qarab klass ierarxiyasini pastga yuradi va bu metodni qidiradi. U buni qila oladi, chunki har bir tur obyektida uning asosiy turiga ishora qiluvchi maydon mavjud; bu ma'lumot rasmlarda ko'rsatilmagan.) Keyin, JIT kompilyator tur obyektining metod jadvalida chaqirilayotgan metodga tegishli yozuvni topadi, metodni JIT qiladi (agar kerak bo'lsa) va JIT qilingan kodni chaqiradi. Bizning muhokamamiz uchun, Employee ning GetYearsEmployed metodi Joe kompaniyada 5 yildan beri ishlayotganini qaytaradi deylik. Bu butun son year mahalliy o'zgaruvchisiga saqlanadi. Bu operatsiyaning natijasi 4-11 rasmda ko'rsatilgan.
Virtual Instansiya Metodlarini Chaqirish
M3 dagi kodning keyingi qatori Employee ning virtual instansiya GetProgressReport metodini chaqiradi. Virtual instansiya metodini chaqirganda, JIT kompilyator metodda ba'zi qo'shimcha kodni ishlab chiqaradi, bu kod har safar metod chaqirilganda bajariladi. Bu kod avval chaqiruv uchun ishlatiladigan o'zgaruvchiga qarashi va keyin chaqiruvchi obyektning manziliga boradi. Bu holda, e o'zgaruvchisi "Joe" ni ifodalovchi Manager obyektiga ishora qiladi. Keyin, kod obyektning ichki tur obyekti ko'rsatkichiga qaraydi; bu a'zo obyektning haqiqiy turiga ishora qiladi. Kod keyin tur obyektining metod jadvalida chaqirilayotgan metodga tegishli yozuvni topadi, metodni JIT qiladi (agar kerak bo'lsa) va JIT qilingan kodni chaqiradi. Bizning muhokamamiz uchun, Manager ning GetProgressReport ni chaqiriladi chunki e Manager obyektiga ishora qiladi. Bu operatsiyaning natijasi 4-12 rasmda ko'rsatilgan.
E'tibor bering, agar Employee ning Lookup metodi Joe shunchaki Employee va Manager emasligini aniqlagan bo'lsa, Lookup ichki ravishda tur obyekti ko'rsatkichi Employee tur obyektiga ishora qiladigan Employee obyektini yaratgan bo'lardi va bu Manager ning implementatsiyasi o'rniga Employee ning GetProgressReport implementatsiyasining bajarilishiga sabab bo'lardi.
Shu nuqtada biz manba kodi, IL va JIT qilingan kod o'rtasidagi munosabatni muhokama qildik. Shuningdek, biz oqimning steki, argumentlari, mahalliy o'zgaruvchilari va bu argumentlar va o'zgaruvchilar boshqariladigan heap dagi obyektlarga qanday ishora qilishini muhokama qildik. Siz shuningdek obyektlar o'z tur obyektlariga ko'rsatkich o'z ichiga olganini ko'rdingiz (statik maydonlar va metod jadvali bilan). Biz JIT kompilyatori statik metodlarni, novirtual instansiya metodlarini va virtual instansiya metodlarini chaqirishni qanday aniqlashini ham muhokama qildik.
System.Type Tur Obyekti
Ushbu bobni yakunlashdan oldin, men sizga CLR ichida nima sodir bo'layotgani haqida yana biroz tushuncha bermoqchiman.
Siz Employee va Manager tur obyektlarining ikkalasi ham tur obyekti ko'rsatkichiga ega ekanligini ko'rasiz. Bu tur obyektlari aslida o'zlari ham obyektlar bo'lganligi tufaylidir. CLR tur obyektlarini yaratganda, u bu a'zolarni ishga tushirishi kerak. "Nimaga?" deb so'rashingiz mumkin. CLR jarayonda ishga tusha boshlaganda, u darhol MSCorLib.dll da aniqlangan System.Type turi uchun maxsus tur obyektini yaratadi. Employee va Manager tur obyektlari bu turning "instansiyalari" dir va shuning uchun ularning tur obyekti ko'rsatkichlari System.Type tur obyektiga ishora qilish uchun ishga tushiriladi — 4-13 rasmda ko'rsatilganidek.
Albatta, System.Type tur obyektining o'zi ham obyekt va shuning uchun u ham tur obyekti ko'rsatkichiga ega. Bu a'zo nimaga ishora qilishini so'rash mantiqan to'g'ri. U o'ziga ishora qiladi, chunki System.Type tur obyektining o'zi tur obyektining "instansiyasi" dir. Va endi siz CLR ning butun turdagi tizimini va u qanday ishlashini tushunishingiz kerak. Aytgancha, System.Object ning GetType metodi shunchaki ko'rsatilgan obyektning ichki tur obyekti ko'rsatkichiga saqlangan manzilni qaytaradi. Boshqacha aytganda, GetType metodi obyektning tur obyektiga ko'rsatkich qaytaradi va aynan shu tarzda siz tizimdagi istalgan obyektning (shu jumladan tur obyektlarining) haqiqiy turini aniqlashingiz mumkin.
Ushbu bobda siz turlar bilan ishlashning asosiy tamoyillarini o'rgandingiz: barcha turlar System.Object dan hosila bo'ladi, casting mexanizmi va tur xavfsizligi qanday ishlashi, nomlar fazolari turlarni mantiqiy guruhlash usuli va nihoyat ish vaqtida turlar, obyektlar, oqim steklari va boshqariladigan heap qanday o'zaro bog'lanishi. Bu tushunchalar .NET Framework dagi har qanday ishni tushunish uchun muhim poydevordir.