20-Bob: Istisnolar va State Boshqarish
Exception handling mexanizmi, istisnolarni to'g'ri boshqarish, xatolarni tiklash va dastur holatini himoya qilish usullari
Ushbu bob CLR ning istisnolarni qayta ishlash (exception handling) mexanizmini chuqur o'rganadi. Istisnolar — bu dastur ishlashi davomida yuzaga keladigan xatolarni boshqarish uchun mo'ljallangan kuchli mexanizmdir. Ob'yektga yo'naltirilgan dasturlashda xatolarni xabar qilishning boshqa usullari (masalan, xato kodlari qaytarish) o'rniga istisnolar ishlatiladi, chunki konstruktorlar, property lar, operatorlar va boshqa tuzilmalar xato kodlarini qaytara olmaydi.
Ko'plab dasturchilar istisno nima ekanligini noto'g'ri tushunadi — ular istisnoni "tez-tez sodir bo'ladigan" hodisalar bilan bog'laydi. Masalan, fayl o'qish metodini loyihalashda "fayl oxiriga doim yetiladi, shuning uchun istisno tashlamayman" deb o'ylashadi. Ammo asl muammo shundaki, bu qarorni metodni loyihalashchi emas, balki metodni chaqiruvchi qabul qilishi kerak. Metodni loyihalashda uning barcha mumkin bo'lgan chaqiruvchilarini oldindan bilish imkonsiz.
Istisnolarni Qayta Ishlash Mexanizmi (Exception-Handling Mechanics)
Ushbu bo'limda istisnolarni qayta ishlash uchun zarur bo'lgan C# konstruktsiyalarini tanishtiramiz. .NET Framework ning istisnolarni qayta ishlash mexanizmi Windows tomonidan taqdim etiladigan Structured Exception Handling (SEH) mexanizmi ustiga qurilgan.
Quyidagi C# kodi istisnolarni qayta ishlash bloklarining standart ishlatilishini ko'rsatadi:
Bu kod istisnolarni qayta ishlash bloklaridan foydalanishning bir mumkin yo'lini ko'rsatadi. Ko'pchilik metodlar oddiygina bitta try bloki bilan bitta finally yoki bitta catch blokdan iborat bo'ladi.
try Bloki
try bloki umumiy tozalash operatsiyalarini, istisnolardan tiklash operatsiyalarini yoki ikkalasini ham talab qiladigan kodni o'z ichiga oladi. Tozalash kodi bitta finally blokiga joylashtirilishi kerak. try bloki istisno tashlashi mumkin bo'lgan kodni ham o'z ichiga olishi mumkin. Istisnodan tiklanish kodi bir yoki bir nechta catch bloklariga joylashtirilishi kerak.
try bloki kamida bitta catch yoki finally bloki bilan bog'langan bo'lishi shart; yolg'iz try blokining ma'nosi yo'q va C# buni oldini oladi.
Ba'zida dasturchilar bitta try blokiga qancha kod joylashtirishni so'rashadi. Buning javobi holatni boshqarish (state management) ga bog'liq. Agar try bloki ichida bir xil istisno turini tashlaydigan bir nechta operatsiya bajarilsa, lekin tiklash usuli har bir operatsiya uchun farq qilsa, har bir operatsiyani alohida try blokiga joylashtiring.
catch Bloki
catch bloki istisnoga javob sifatida bajariladigan kodni o'z ichiga oladi. try bloki nol yoki undan ortiq catch bloklarga ega bo'lishi mumkin. Agar try blokidagi kod istisno tashlamasa, CLR hech qachon catch bloklaridagi kodni bajarmaydi — oqim to'g'ridan-to'g'ri finally blokiga (agar mavjud bo'lsa) o'tadi.
catch kalit so'zidan keyin qavslar ichidagi ifoda catch turi deb ataladi. C# da siz System.Exception yoki undan hosil bo'lgan turni ko'rsatishingiz kerak. CLR yuqoridan pastga qarab mos catch turini qidiradi, shuning uchun eng aniq istisno turlarini yuqoriga, System.Exception ni esa eng pastga joylashtirishingiz kerak.
Agar istisno try blokidagi kod tomonidan (yoki try blokidan chaqirilgan metod tomonidan) tashlanilsa, CLR tashlangan istisno turiga mos catch blokini qidira boshlaydi. Agar hech qanday catch turi mos kelmasa, CLR chaqiruvlar stekida yuqoriga qarab mos catch blokini qidiradi. Agar steklar tepasiga yetib ham topilmasa, tutilmagan istisno (unhandled exception) sodir bo'ladi.
catch bloki oxirida siz uchta tanlovga egasiz:
- Istisnoni qayta tashlash — chaqiruvlar stekida yuqoriroqdagi kodni xabardor qilish
- Boshqa istisnoni tashlash — boyroq ma'lumot bilan yuqoridagi kodni xabardor qilish
- Oqimni
catchbloki oxiridan chiqib ketishiga ruxsat berish
Visual Studio da catch blokini debug qilayotganda, maxsus $exception o'zgaruvchi nomini watch oynasiga qo'shib, joriy tashlangan istisno ob'yektini ko'rishingiz mumkin.
finally Bloki
finally bloki bajarilishi kafolatlangan kodni o'z ichiga oladi. Odatda, finally blokidagi kod try bloki tomonidan amalga oshirilgan amallarning tozalanishini bajaradi. Masalan, agar try blokida fayl ochilsa, faylni yopish kodi finally blokiga joylashtiriladi.
Agar try blokidagi kod istisno tashlamasdan bajarilsa, fayl yopilishi kafolatlangan. Agar try blokidagi kod istisno tashlasa ham, finally blokidagi kod bajariladi va fayl yopilishi kafolatlangan. Faylni yopish ifodrasini finally blokidan keyin qo'yish noto'g'ri bo'lardi, chunki istisno tutilmasdan tashlanilsa, u ifoda bajarilmay qolardi.
try bloki finally blokini talab qilmaydi. Lekin agar finally bloki mavjud bo'lsa, u barcha catch bloklardan keyin kelishi kerak. try bloki bittadan ortiq finally blokiga ega bo'la olmaydi.
catch yoki finally bloki ichida tasodifan istisno tashlanishi mumkin. Bu holda CLR birinchi istisnoni va uning barcha ma'lumotlarini (stack trace kabi) yo'qotadi. Bu holat juda kam uchraydi, ammo sodir bo'lsa, ko'pincha holatning buzilganligini ko'rsatadi.
CLS va Non-CLS Istisnolar
CLR ning barcha dasturlash tillari Exception dan hosil bo'lgan ob'yektlarni tashlashni qo'llab-quvvatlashi kerak, chunki Common Language Specification (CLS) buni talab qiladi. Ammo CLR aslida istalgan turdagi ob'yektni istisno sifatida tashlashga ruxsat beradi — String, Int32, DateTime va hokazo.
C# kompilyatori faqat Exception dan hosil bo'lgan ob'yektlarni tashlashga ruxsat beradi. Boshqa tillarda yozilgan kod Exception dan hosil bo'lmagan ob'yektlarni tashlashi mumkin.
CLR 2.0 da Microsoft RuntimeWrappedException sinfini kiritdi (System.Runtime.CompilerServices nomlar fazosida). Non-CLS istisno tashlanilganda, CLR avtomatik ravishda RuntimeWrappedException ob'yektini yaratadi va uning WrappedException xususiyatiga asl ob'yektni saqlaydi. Shu tariqa CLR barcha non-CLS istisnolarni CLS-mos istisnolarga aylantiradi.
Agar kodni CLR 2.0 yoki undan keyingi versiya uchun qayta kompilyatsiya qilsangiz, ikkinchi catch bloki hech qachon bajarilmaydi va C# kompilyatori ogohlantirish beradi: "CS1058: A previous catch clause already catches all exceptions."
Agar eski xatti-harakatni saqlab qolishni xohlasangiz, assemblyga RuntimeCompatibilityAttribute ni qo'llashingiz mumkin:
System.Exception Sinfi
CLR Int32 dan String gacha har qanday turdagi ob'yektni istisno sifatida tashlashga ruxsat beradi. Ammo Microsoft barcha CLS-mos dasturlash tillarini System.Exception dan hosil bo'lgan turlarni tashlash va tutishga majbur qildi. System.Exception dan hosil bo'lgan istisno turlari CLS-mos deb hisoblanadi.
System.Exception turi quyidagi muhim xususiyatlarni o'z ichiga oladi:
| Xususiyat | Kirish | Tur | Tavsif |
|---|---|---|---|
Message | Faqat o'qish | String | Istisno nima uchun tashlanganini ko'rsatuvchi matn. Bu xabar texnik bo'lishi kerak, chunki foydalanuvchi emas, balki dasturchi uni ko'radi. |
InnerException | Faqat o'qish | Exception | Joriy istisnoni qayta ishlash jarayonida avvalgi istisno yuzaga kelgan bo'lsa, bu xususiyat o'sha avvalgi istisnoga ishora qiladi. Odatda null bo'ladi. |
StackTrace | Faqat o'qish | String | Istisnoga olib kelgan metodlar nomlari va signaturalarini o'z ichiga oladi. Debugging uchun juda qimmatli. |
HResult | O'qish/yozish | Int32 | Boshqariladigan va native kod chegaralarini kesishda ishlatiladigan 32-bitli qiymat. COM API lari HRESULT qaytarganda, CLR Exception dan hosil bo'lgan ob'yekt tashlaydi. |
Source | O'qish/yozish | String | Istisnoni yaratgan assembly nomini o'z ichiga oladi. |
Data | Faqat o'qish | IDictionary | Kalit-qiymat juftliklari to'plami. Istisno tashlovchi kod bu to'plamga yozuvlar qo'shishi mumkin, tutuvchi kod esa ulardan foydalanishi mumkin. |
TargetSite | Faqat o'qish | MethodBase | Istisnoni tashlagan metodga ishora qiladi. |
HelpLink | O'qish/yozish | String | Foydalanuvchiga istisnoni tushunishda yordam beradigan hujjatga URL. |
throw va throw ex Farqi
System.Exception ning StackTrace xususiyati haqida muhim gap bor. catch bloki bu xususiyatni o'qib, istisnoga olib kelgan metodlarning stek izini olishi mumkin. Istisno tashlanilganda, CLR throw ko'rsatmasi joylashgan joyni ichki ravishda qayd etadi. catch bloki istisnoni qabul qilganda esa, CLR istisnoning tutilgan joyini qayd etadi.
Istisno tashlanilganda, CLR istisnoning boshlang'ich nuqtasini tiklaydi (reset qiladi); ya'ni, CLR faqat eng so'nggi istisno ob'yekti tashlangan joyni eslab qoladi.
Quyidagi kod tutilgan istisno ob'yektini qayta tashlaydi va CLR ning boshlang'ich nuqtasini qayta tiklashiga sabab bo'ladi:
Aksincha, agar istisnoni throw kalit so'zi yordamida ob'yektsiz qayta tashlasangiz, CLR stekning boshlang'ich nuqtasini qayta tiklamaydi:
Bu ikki kod fragmenti orasidagi yagona farq — CLR ning istisnoning asl kelib chiqish joyini qanday aniqlashidir. Afsuski, istisno tashlanilganda yoki qayta tashlanilganda, Windows stekning boshlang'ich nuqtasini qayta tiklaydi. Agar istisno tutilmagan bo'lsa, Windows Error Reporting ga xabar qilinadigan stek joylashuvi asl istisno joylashuvi emas, balki oxirgi throw yoki qayta tashlash joylashuvi bo'ladi.
Ba'zi dasturchilar bu muammoni hal qilish uchun stek izini asl istisno tashlangan joyni aniq ko'rsatadigan tarzda amalga oshirishga harakat qilishgan:
FCL da Aniqlangan Exception Sinflari
Framework Class Library (FCL) ko'plab istisno turlarini aniqlaydi (hammasi oxir-oqibat System.Exception dan hosil bo'lgan). Quyida MSCorLib.dll assemblyda aniqlangan istisno turlari ierarxiyasining bir qismi ko'rsatilgan:
Asl g'oya System.Exception barcha istisnolarning asosiy turi bo'lishi va System.SystemException hamda System.ApplicationException uning bevosita ikki hosila turi bo'lishi edi. CLR tomonidan tashlanadigan istisnolar SystemException dan, ilova tomonidan tashlanadigan istisnolar esa ApplicationException dan hosil bo'lishi rejalashtirilgandi.
Ammo bu qoida yaxshi amal qilinmadi: ba'zi istisno turlari to'g'ridan-to'g'ri Exception dan hosil bo'lgan (IsolatedStorageException), ba'zi CLR istisnolari ApplicationException dan hosil bo'lgan (TargetInvocationException). Natijada, SystemException va ApplicationException turlarining hech qanday maxsus ahamiyati yo'q.
Istisno Tashlash (Throwing an Exception)
O'z metodlaringizni amalga oshirayotganda, metod o'z nomi bilan ko'rsatilgan vazifani bajara olmasa, istisno tashlashingiz kerak. Istisno tashlashda ikki masalani o'ylab ko'rish kerak:
1. Qaysi Exception turini tashlash kerak? Siz mazmunli turni tanlashingiz kerak. Chaqiruvlar stekida yuqoriroqdagi kod qaysi metod muvaffaqiyatsiz bo'lganini aniqlash va ehtiyotkorlik bilan tiklanishni xohlashi mumkin. FCL da allaqachon aniqlangan turdan foydalanishingiz yoki o'zingiz System.Exception dan hosil bo'lgan yangi tur yaratishingiz mumkin.
Agar istisno turlari ierarxiyasini aniqlashni xohlasangiz, ierarxiyani past va keng (shallow and wide) qilish tavsiya etiladi — kam bazaviy sinflar bilan. Sababi shuki, bazaviy sinflar ko'p turli xatolarni bitta xato sifatida ko'rish vositasi bo'lib, bu odatda xavfli. Hech qachon System.Exception ob'yektini tashlamang va boshqa bazaviy sinflarni tashlashda ham ehtiyot bo'ling.
Agar mavjud istisno turidan hosil bo'lgan yangi istisno turini aniqlasangiz, bazaviy turni tutadigan barcha mavjud kod endi yangi turingizni ham tutadi. Bu ba'zi holatlarda istalmagan natijaga olib kelishi mumkin. Bazaviy turga javob beradigan kod hech qachon kutilmagan yangi istisnolar bilan to'g'ri ishlamasligi mumkin.
2. Qanday xabar berish kerak? Istisno tashlaganingizda, metod nima uchun o'z vazifasini bajara olmaganini tushuntiradigan batafsil ma'lumotli string xabar qo'shishingiz kerak. Bu xabar foydalanuvchiga ko'rsatilmasligi kerak — u texnik bo'lishi kerak, chunki logga yoziladi va dasturchilar kodlarini tuzatish uchun ishlatadi.
O'z Exception Sinfini Yaratish
O'z istisno sinfingizni loyihalash og'riqli va xatolarga moyil jarayon. Buning asosiy sababi shuki, barcha Exception dan hosil turlar AppDomain chegarasini kesib o'tishi uchun serializatsiyalanishi kerak. Jeffrey Richter bu jarayonni soddalashtirish uchun generik Exception<TExceptionArgs> sinfini taklif qiladi:
ExceptionArgs bazaviy sinfi juda oddiy:
Endi disk to'laganligini bildiradigan istisno turini aniqlash juda oson:
Agar qo'shimcha ma'lumot kerak bo'lmasa, yanada soddaroq:
Va endi istisnoni tashlash va tutish quyidagicha bo'ladi:
Ishonchlilik va Samaradorlik O'rtasidagi Muvozanat
Ob'yektga yo'naltirilgan dasturlash dasturchilarni juda samarali qiladi. Buning katta qismi kodning kompozitsiya qobiliyati (composability) bo'lib, u kodni yozish, o'qish va saqlashni osonlashtiradi. Masalan, bitta qator kod ichida bir nechta operatsiya zanjirini bajarish mumkin:
Bu kodda katta taxmin bor: hech qanday xato sodir bo'lmaydi. Ammo xatolar doimo mumkin va biz ularni qayta ishlash uchun istisnolardan foydalanamiz. C# kompilyatori va CLR o'zi ham ko'plab operatsiyalarni bajaradi: virtual metodlarni chaqiradi, assemblylarni yuklaydi, ob'yektlarni yaratadi, statik konstruktorlarni chaqiradi, turlarni boxing qiladi va hokazo — bularning hammasi potentsial istisno tashlanishi mumkin bo'lgan nuqtalardir.
Haqiqatan ham mustahkam va ishonchli kod yozish juda og'riqli va amalda deyarli imkonsiz. Xatolar kam uchraganligi sababli, jamiyat ishonchli kodni dasturchining samaradorligi evaziga almashtirishga qaror qilgan.
Holatning buzilishini kamaytirish uchun bir nechta choralar ko'rish mumkin:
finallyblokida muhim kodni yozing: CLR oqimnicatchyokifinallyblok ichida bajarilayotganda to'xtatishga ruxsat bermaydi. Shunday qilib,Transfermetodini mustahkamroq qilish mumkin:
Barcha kodingizni finally bloklarga yozish mutlaqo tavsiya etilmaydi! Bu usulni faqat juda muhim holatni o'zgartirish uchun ishlating.
- Kod shartnomalar (Code Contracts):
System.Diagnostics.Contracts.Contractsinfini ishlatib argumentlar va o'zgaruvchilarni tekshirish. Agar shartnoma bajarilmasa, holat o'zgartirilmasdan oldin istisno tashlanadi. - Cheklangan bajarilish hududlari (CER):
tryblokiga kirishdan oldin barcha bog'liq assemblylarni yuklab,catchvafinallybloklaridagi kodni kompilyatsiya qilish mumkin. Bu bir nechta potentsial istisnolarni yo'q qiladi. - Tranzaksiyalar: Ma'lumotlar bazasi tranzaksiyalari barcha holat o'zgartirilishini yoki hech birini kafolatlaydi.
Agar holatning buzilganligini aniqlab, uni tiklash imkonsiz bo'lsa, buzilgan holatni yo'q qiling va ilovani qayta ishga tushiring. Boshqariladigan holat AppDomain dan tashqariga chiqmasligi sababli, AppDomain ni AppDomain.Unload metodi bilan yuklab tushirish (unload qilish) mumkin.
Agar butun jarayonni tugatish kerak bo'lsa, Environment.FailFast statik metodini chaqiring:
Bu metod jarayonni try/finally bloklari yoki Finalize metodlarini bajarmasdan tugatadi. Bu yaxshi, chunki buzilgan holat bilan yana kod bajarish vaziyatni yomonlashtirishi mumkin. Ammo FailFast CriticalFinalizerObject dan hosil ob'yektlarga tozalanish imkonini beradi.
FCL kodining ko'pchiligi kutilmagan istisno yuzaga kelganda holatini yaxshi saqlamaydi. Agar kodingiz istisnoni ushlab, FCL ob'yektlaridan foydalanishni davom ettirsa, bu ob'yektlar oldindan aytib bo'lmaydigan tarzda xatti-harakat ko'rsatishi mumkin. FCL ob'yektlarining ko'pchiligi FailFast ni chaqirmaydi yoki o'z holatlarini tiklashga harakat qilmaydi.
Qo'llanmalar va Eng Yaxshi Amaliyotlar
Istisno mexanizmini tushunish muhim, lekin istisnolardan to'g'ri foydalanishni bilish ham shunchalik muhimdir. Ko'pincha kutubxona dasturchilari har xil istisnolarni ushlab, ilova dasturchisiga muammoning yuzaga kelganini bilishiga yo'l bermaydi.
Agar siz boshqa dasturchilar ishlatadigan sinf kutubxonasini yaratsangiz, quyidagilarga rioya qiling: holatni sinchkovlik bilan kuzating, buzmaslikka harakat qiling. Agar holatni o'zgartirsangiz, xatoga tayyor bo'ling va holatni tiklashga harakat qiling. Kodingiz qaysi sharoitda ishlatilishini oldindan bilolmaysiz, shuning uchun siyosat qabul qilmang — buni chaqiruvchiga qoldiring.
Agar siz ilova dasturchisi bo'lsangiz, o'zingiz mos deb hisoblagan siyosatni belgilang. Bob qo'llanmalariga amal qilish kodingizdagi muammolarni topishga va ilovangizni mustahkamroq qilishga yordam beradi. Ammo sinf kutubxonasi kodiga qaraganda istisnolarni tutishda ko'proq tajovuzkor bo'lishingiz mumkin.
finally Bloklarini Ko'p Ishlating
finally bloklari ajoyib! Ular oqim qaysi turdagi istisnoni tashlashidan qat'i nazar bajarilishi kafolatlangan kod blokini belgilash imkonini beradi. finally bloklaridan muvaffaqiyatli boshlangan operatsiyalarni tozalash, chaqiruvchiga qaytish yoki finally blokdan keyingi kodga ruxsat berishdan oldin foydalaning. Resurs oqishining oldini olish uchun ham finally bloklarini tez-tez ishlating.
C# tili tozalash kodini yozishni osonlashtiruvchi konstruktsiyalarni taqdim etadi. Kompilyator lock, using va foreach bayonotlarini ishlatganingizda avtomatik ravishda try/finally bloklarini yaratadi. Masalan, quyidagi using bayonoti yuqoridagi kod bilan bir xil natija beradi:
Hammasini Tutmang
Dasturchillar tomonidan keng tarqalgan xato — catch bloklarini juda ko'p va noto'g'ri ishlatish. Istisnoni tutish demak, siz bu istisnoni kutganingiz, nima uchun sodir bo'lganini tushunganingiz va u bilan qanday kurashishni bilganingiz degani.
Bu kod barcha istisnolarni kutganini va barchasi bilan qanday kurashishni bilganini ko'rsatadi. Bu sinf kutubxonasi turida hech qachon sodir bo'lmasligi kerak, chunki turning barcha istisnolarni tutishi va yutishiga (swallow) sabab yo'q.
System.Exception ni tutib (qayta tashlamasdan) yutish hech qachon qilinmasligi kerak, chunki bu xatolarni yashiradi va oldindan aytib bo'lmaydigan natijalarga olib keladi. Visual Studio ning kod tahlil vositasi (FxCopCmd.exe) catch (Exception) blokida throw bayonoti mavjud bo'lmagan har qanday kodni belgilaydi.
System.Exception ni tutib, catch bloki oxirida qayta tashlash (throw;) muammo emas. Bu holda siz holatni tiklash uchun ba'zi operatsiyalarni bajarasiz, keyin xatoni yuqoriga uzatasiz.
Istisnolardan Chiroyli Tiklash
Ba'zida metod tashlaydigan istisnolarning ba'zilarini oldindan bilib, undan chiroyli tiklanishingiz mumkin:
Bu pseudokod elektron jadval katakchasining qiymatini hisoblaydi. Agar katak qiymati boshqa katakka bo'lish natijasi bo'lsa va bo'luvchi 0 bo'lsa, DivideByZeroException tutiladi. Xuddi shunday, agar ko'paytirish natijasi juda katta bo'lsa, OverflowException tutiladi.
Aniq istisnolarni tutganingizda, istisnoni keltirib chiqaradigan sharoitlarni to'liq tushunganingizga, tashlanadigan istisno turini va undan hosil bo'ladigan turlarni bilganingizga ishonch hosil qiling. System.Exception ni (qayta tashlamasdan) tutmang, chunki try bloking ichida tashlanishi mumkin bo'lgan barcha istisnolarni bilish juda qiyin.
Qisman Bajarilgan Operatsiyadan Chiqish — Holatni Saqlash
Ko'pincha metodlar bitta mavhum operatsiyani bajarish uchun bir nechta boshqa metodlarni chaqiradi. Ba'zi metod chaqiruvlari muvaffaqiyatli bo'lishi, ba'zilari esa muvaffaqiyatsiz bo'lishi mumkin. Masalan, ob'yektlar to'plamini diskka serializatsiya qilish. 10 ta ob'yektni serializatsiya qilgandan keyin istisno tashlanadi. Disk faylining holati nima bo'ladi? Qisman serializatsiya qilingan ob'yekt grafi bilan buzilgan fayl qoladi.
Yaxshi yondashuv: faylni avvalgi holatiga qaytarish:
Diqqat qilingan nuqta: catch bloki hech qanday istisno turini ko'rsatmaydi, chunki biz barcha istisnolarni tutmoqchimiz — qanday xato sodir bo'lgani muhim emas, faqat biror narsa noto'g'ri ketgani muhim. Holatni tiklangandan keyin C# ning throw kalit so'zi yordamida istisnoni qayta tashlaymiz.
Amalga Oshirish Tafsilotini Yashirish — "Shartnoma"ni Saqlash
Ba'zi holatlarda bitta istisnoni tutib, boshqa istisnoni qayta tashlash foydali bo'lishi mumkin. Bu metod shartnomasi (contract) ning ma'nosini saqlash uchun qilinadi. Yangi tashlangan istisno turi maxsus bo'lishi kerak — boshqa istisno turining bazaviy turi sifatida ishlatilmaydigan tur.
Telefon kitobchasi ma'lumotlari fayldan olinadi. Ammo PhoneBook turidan foydalanuvchi buni bilmaydi — bu amalga oshirish tafsiloti bo'lib, kelajakda o'zgarishi mumkin. Agar fayl topilmasa yoki o'qib bo'lmasa, foydalanuvchi FileNotFoundException yoki IOException ni ko'rishni kutmaydi. Shuning uchun GetPhoneNumber metodi bu ikki istisno turini tutib, yangi NameNotFoundException ni tashlaydi.
Bu usulni ishlatsangiz, chaqiruvchilarga ikki narsa haqida "yolg'on" gapirayapsiz: birinchidan, aslida nima noto'g'ri ketgani haqida (fayl topilmadi, lekin nom topilmadi deyapsiz), ikkinchidan, xato qaerda sodir bo'lgani haqida (stack trace catch blokiga ishora qiladi). Bu debugging ni qiyinlashtirishi mumkin, shuning uchun bu usulni ehtiyotkorlik bilan ishlating.
Istisnoga qo'shimcha ma'lumot qo'shish uchun istisnoning Data xususiyatidan foydalanishingiz va ayni ob'yektni qayta tashlashingiz mumkin:
Tutilmagan Istisnolar (Unhandled Exceptions)
Istisno tashlanilganda, CLR chaqiruvlar stekida tashlangan istisno turiga mos catch bloklarini yuqoriga qarab qidiradi. Agar hech qanday catch bloki mos kelmasa, tutilmagan istisno yuzaga keladi. CLR jarayondagi har qanday oqimda tutilmagan istisno sodir bo'lganini aniqlasa, jarayonni tugatadi.
Tutilmagan istisno dastur kutmagan va haqiqiy xato deb hisoblanadigan vaziyatni ko'rsatadi. Bu nuqtada xato uni nashr etgan kompaniyaga xabar qilinishi, tuzatilishi va yangi versiya tarqatilishi kerak.
Sinf kutubxonasi dasturchilari tutilmagan istisnolar haqida umuman o'ylamasligi kerak. Faqat ilova dasturchilari tutilmagan istisnolar bilan shug'ullanishi va ular uchun siyosat o'rnatishi kerak. Microsoft ilova dasturchilari CLR ning standart siyosatini qabul qilishni tavsiya etadi: tutilmagan istisno sodir bo'lganda, Windows tizim hodisalar jurnaliga yozuv yozadi.
Turli ilova turlari uchun tutilmagan istisnolarni boshqarish uchun turli hodisalar mavjud:
- Ko'p ilovalar uchun:
System.AppDomainningUnhandledExceptionhodisasi - Windows Store ilovalari:
Windows.UI.Xaml.ApplicationningUnhandledExceptionhodisasi - Windows Forms:
System.Windows.Forms.NativeWindowningOnThreadExceptionvirtual metodi,Application.ThreadExceptionhodisasi - WPF:
Application.DispatcherUnhandledExceptionvaDispatcher.UnhandledExceptionhodisalari - ASP.NET:
System.Web.UI.TemplateControlningErrorhodisasi
CLR native kod tomonidan tashlanadigan ba'zi istisnolarni buzilgan holat istisnolari (corrupted state exceptions) deb hisoblaydi: EXCEPTION_ACCESS_VIOLATION, EXCEPTION_STACK_OVERFLOW va boshqalar. Standart holda boshqariladigan kod bu istisnolarni tuta olmaydi va finally bloklari bajarilmaydi. Buni o'zgartirish uchun metodga HandleProcessCorruptedStateExceptionsAttribute ni qo'llashingiz mumkin.
Istisnolarni Debug Qilish (Debugging Exceptions)
Visual Studio debuggeri istisnolar uchun maxsus qo'llab-quvvatlash taklif etadi. Solution ochiq holatda Debug menyusidan Exceptions ni tanlasangiz, turli istisno kategoriyalarini ko'rasiz: C++ Exceptions, CLR Exceptions, JavaScript Exceptions, Native Run-Time Checks va Win32 Exceptions.
Har qanday istisno turi uchun, agar uning Thrown belgilagichi tanlangan bo'lsa, debugger istisno tashlanishi bilanoq to'xtaydi. Bu vaqtda CLR hali mos catch bloklarini qidirmagan. Bu istisno yuzaga kelgan aniq joyni topish uchun juda foydali bo'lib, ayniqsa biror komponent yoki kutubxona istisnolarni yutayotgan deb gumon qilsangiz ishlatiladi.
Agar istisno turining Thrown belgilagichi tanlanmagan bo'lsa, debugger faqat istisno tutilmagan bo'lsa to'xtaydi. Odatda dasturchilar Thrown belgisini o'chirib qo'yishadi, chunki bu ilovaning kutilgan va to'g'ri qayta ishlangan istisnosini ko'rsatadi.
O'z istisno turlaringizni aniqlasangiz, ularni Exceptions dialog oynasiga Add tugmasini bosib qo'shishingiz mumkin. Bu yerda turning to'liq nomlangan ismini kiritasiz (Common Language Runtime Exceptions turini tanlagan holda).
Istisnolarni Qayta Ishlash va Unumdorlik
Dasturchilar jamoatchiligida istisnolarni qayta ishlash unumdorligi haqida ko'p muhokamalar bo'ladi. Ba'zilar istisnolarni qayta ishlash shunchalik sekinki, ulardan umuman foydalanishni rad etadi. Ammo ob'yektga yo'naltirilgan platformada istisnolarni qayta ishlash ixtiyoriy emas — u majburiydir.
Istisnolarni qayta ishlashni an'anaviy xato xabar qilish usullari (HRESULT, xato kodlari) bilan solishtirish qiyin. Har bir metod chaqiruvining qaytarish qiymatini tekshirish uchun kod yozsangiz, ilovangiz unumdorligi jiddiy pasayadi. Istisnolarni qayta ishlash ancha yaxshi alternativdir.
Ammo istisnolarni qayta ishlashning ham narxi bor: boshqarilmagan C++ kompilyatorlari qaysi ob'yektlar muvaffaqiyatli yaratilganini kuzatadigan va istisno tutilganda har birining destruktorini chaqiradigan kod yaratishi kerak. Boshqariladigan kodda esa bu muammo yo'q: boshqariladigan ob'yektlar boshqariladigan heapda joylashadi va garbage collector tomonidan boshqariladi.
Agar ko'p muvaffaqiyatsiz bo'ladigan metodingiz bo'lsa va istisnolar unumdorlikka ta'sir qilsa, TryXxx namunasini (pattern) qo'llang. Masalan, Int32.TryParse:
Bu metodlar Boolean qaytaradi: agar String ni Int32 ga o'girish mumkin bo'lsa true, aks holda false. TryXxx metodining Boolean qaytarish qiymati faqat bitta turdagi muvaffaqiyatsizlikni ko'rsatishi kerak. Boshqa turdagi muvaffaqiyatsizliklar uchun metod hali ham istisno tashlashi kerak.
Turlar va ularning a'zolarini aniqlaganda, ular turingiz ishlatiladigan umumiy stsenariylarda muvaffaqiyatsiz bo'lishi ehtimoli kam bo'ladigan tarzda loyihalang. Shundan so'ng, agar foydalanuvchilardan istisno unumdorligi haqida shikoyatlar kelsa, shu vaqtgina TryXxx metodlarini qo'shishni ko'rib chiqing.
Cheklangan Bajarilish Hududlari (Constrained Execution Regions — CERs)
CER — bu xatolarga chidamli bo'lishi kerak bo'lgan kod bloki. AppDomain lar yuklab tushirilishi mumkin bo'lganligi sababli, CER lar odatda bir nechta AppDomain yoki jarayonlar o'rtasida umumiy holatni boshqarish uchun ishlatiladi. CER lar kutilmaganda tashlanadigan asinxron istisnolar (asynchronous exceptions) sharoitida holatni saqlashda foydali.
Muammo: catch yoki finally bloki ichidagi operatsiyalar muvaffaqiyatsiz bo'lishi mumkin (masalan, assembly yuklanishi, JIT kompilyatsiya, statik konstruktor chaqirilishi). Mana muammoni ko'rsatadigan misol:
Natija:
Biz xohlagan narsa shuki, try blokidagi kodni bog'liq catch va finally bloklaridagi kod kafolatlangan bajarilishini bilmasdan bajarilmasin. Buni RuntimeHelpers.PrepareConstrainedRegions() yordamida amalga oshiramiz:
Natija:
PrepareConstrainedRegions metodi — juda maxsus metod. JIT kompilyatori bu metodni try blokidan darhol oldin chaqirilganini ko'rganda, try blokining catch va finally bloklaridagi kodni oldindan kompilyatsiya qiladi: barcha assemblylarni yuklaydi, tur ob'yektlarini yaratadi, statik konstruktorlarni chaqiradi va JIT kompilyatsiya qiladi. Agar bu operatsiyalarning birortasi muvaffaqiyatsiz bo'lsa, istisno oqim try blokiga kirmasdan oldin tashlanadi.
ReliabilityContractAttribute atributi metodning ishonchliligi haqida ma'lumot beradi:
JIT kompilyatori faqat Consistency.WillNotCorruptState yoki Consistency.MayCorruptInstance bilan belgilangan metodlarni oldindan tayyorlaydi. CLR MayCorruptAppDomain yoki MayCorruptProcess ni buzishi mumkin bo'lgan metodlar haqida hech qanday kafolat bera olmaydi.
Ishonchli metod yozmoqchi bo'lsangiz, uni kichik va cheklab qo'ying: ob'yektlarni ajratmang (boxing yo'q), virtual yoki interfeys metodlarini chaqirmang, delegatlardan foydalanmang yoki reflection ishlatmang.
RuntimeHelper sinfining yana bir foydali metodi:
Kafolatlangan kod bajarilishining yana bir usuli — CriticalFinalizerObject sinfidan foydalanish (21-Bobda batafsil tushuntirilgan).
Kod Shartnomalar (Code Contracts)
Kod shartnomalar kodingiz haqida qabul qilgan dizayn qarorlarini deklarativ ravishda hujjatlashtirish usulini taqdim etadi. Shartnomalar quyidagi shakllarni oladi:
- Preconditions (Old shartlar): Odatda argumentlarni tekshirish uchun ishlatiladi
- Postconditions (Keyingi shartlar): Metod tugagandan keyin holatni tekshirish uchun ishlatiladi — oddiy qaytish yoki istisno tufayli
- Object Invariants (Ob'yekt o'zgarmaslar): Ob'yektning maydonlari butun hayoti davomida yaxshi holatda qolishini ta'minlash uchun ishlatiladi
Kod shartnomalar kod ishlatilishini, tushunilishini, evolyutsiyasini, testlanishini, hujjatlanishini va xatolarni erta aniqlashni osonlashtiradi. Old shartlar va keyingi shartlarni metod signaturesining bir qismi deb hisoblashingiz mumkin. Shartnomani yangi versiyada yumshatish mumkin, lekin qattiqlashtirib bo'lmaydi (orqaga mos kelishni buzmasdan).
Kod shartnomalarning markazi System.Diagnostics.Contracts.Contract statik sinfi:
Ko'pchilik statik metodlarga [Conditional("CONTRACTS_FULL")] atributi qo'llanilgan. Bu shuni anglatadiki, agar loyihangizda CONTRACTS_FULL belgisi (symbol) aniqlanmagan bo'lsa, kompilyator bu metodlar chaqiruvlarini e'tiborsiz qoldiradi. Standart holda shartnomalar faqat hujjat sifatida xizmat qiladi.
Mana kod shartnomalardan foydalanadigan namuna sinf:
AddItemHelper metodi bir nechta shartnomani belgilaydi:
- Old shartlar:
newItemnullbo'lmasligi va savatda hali bo'lmasligi kerak - Keyingi shartlar: Yangi mahsulot savatda bo'lishi kerak, umumiy narx kamida avvalgidek bo'lishi kerak, va agar
IOExceptiontashlanilsa,totalCosto'zgarmagan bo'lishi kerak - Ob'yekt invarianti:
m_totalCosthech qachon manfiy bo'lmasligi kerak
Merosxo'rlikka kelsak, hosila tur bazaviy turda aniqlangan virtual a'zoning old shartlarini bekor qila olmaydi va o'zgartira olmaydi. Xuddi shunday, interfeys a'zosini amalga oshiruvchi tur interfeys a'zosi tomonidan belgilangan old shartlarni o'zgartira olmaydi. Agar a'zoning aniq shartnomasi bo'lmasa, bu mantiqan Contract.Requires(true) ga teng bo'ladi. Shartnomani qattiqlashtirib bo'lmaganligi sababli, yangi virtual yoki interfeys a'zo kiritganingizda old shartlarni diqqat bilan ko'rib chiqishingiz kerak.
Shartnomalar ish vaqtida qanday ishlaydi: C# kompilyatori tomonidan yaratilgan assemblyni Code Contract Rewriter vositasi (CCRewrite.exe) qayta ishlashi kerak. Bu vosita IL ni tahlil qiladi va keyingi shart shartnomalarini har bir metod oxirida bajarilishi uchun kodga qayta yozadi. [ContractInvariantMethod] atributi bilan belgilangan metodni esa har bir public metod oxirida chaqirilishi uchun IL ni o'zgartiradi.
Shartnoma buzilganda Contract.ContractFailed hodisasi ko'tariladi. Hodisa boshqaruvchilari buzilishni turlicha qayta ishlashi mumkin:
SetHandled()chaqirsa — buzilish e'tiborsiz qoldiriladiSetUnwind()chaqirsa —ContractExceptiontashlanadi- Hech kim
SetHandled,SetUnwindchaqirmasa va istisno tashlamasa — standart qayta ishlash amalga oshiriladi