20-Bob: Istisnolar va State Boshqarish

Exception handling mexanizmi, istisnolarni to'g'ri boshqarish, xatolarni tiklash va dastur holatini himoya qilish usullari

~60 daqiqaO'qish vaqti
14 ta mavzuShu bobda
O'rta-YuqoriDaraja
451-501 betAsl kitobda

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.

Muhim

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:

C# private void SomeMethod() { try { // Ehtiyotkorlik bilan tiklash va/yoki tozalash talab qiladigan kodni // bu yerga joylashtiring... } catch (InvalidOperationException) { // InvalidOperationException dan tiklanadigan kod... } catch (IOException) { // IOException dan tiklanadigan kod... } catch { // Yuqoridagilardan boshqa har qanday istisno turidan tiklanadigan kod... // Istisnoni ushlanganda, odatda uni qayta tashlaysiz. throw; } finally { // try bloki ichida boshlangan operatsiyalarni tozalovchi kod... // Bu yerdagi kod DOIMO bajariladi, istisno tashlanganmi yoki yo'qmi. } // finally blokidan keyingi kod: agar try ichida istisno tashlanmasa, // yoki catch bloki istisnoni ushlab, qayta tashlamasa bajariladi. }

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.

Muhim

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.

Exception: Call Stack bo'ylab Ko'tarilishi
Main() — try/catch bor
MethodA() — catch yo'q
MethodB() — catch yo'q
↑ Exception!
MethodC() — throw new Exception()
Exception tashlanganida, CLR call stack bo'ylab yuqoriga ko'tariladi va birinchi catch blokni qidiradi

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 catch bloki oxiridan chiqib ketishiga ruxsat berish
Eslatma

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.

C# private void ReadData(String pathname) { FileStream fs = null; try { fs = new FileStream(pathname, FileMode.Open); // Fayldagi ma'lumotlarni qayta ishlash... } catch (IOException) { // IOException dan tiklanadigan kod... } finally { // Fayl yopilganligiga ishonch hosil qilish. if (fs != null) fs.Close(); } }

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.

Diqqat!

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.

C# private void SomeMethod() { try { // Ehtiyotkorlik bilan tiklash va/yoki tozalash talab qiladigan kod... } catch (Exception e) { // C# 2.0 dan boshlab, bu blok CLS-mos va non-CLS istisnolarni tutadi throw; // Tutilgan narsani qayta tashlash } catch { // C# ning barcha versiyalarida, bu blok CLS-mos // va non-CLS istisnolarni tutadi throw; // Tutilgan narsani qayta tashlash } }

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:

C# using System.Runtime.CompilerServices; [assembly:RuntimeCompatibility(WrapNonExceptionThrows = false)]

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:

XususiyatKirishTurTavsif
MessageFaqat o'qishStringIstisno nima uchun tashlanganini ko'rsatuvchi matn. Bu xabar texnik bo'lishi kerak, chunki foydalanuvchi emas, balki dasturchi uni ko'radi.
InnerExceptionFaqat o'qishExceptionJoriy istisnoni qayta ishlash jarayonida avvalgi istisno yuzaga kelgan bo'lsa, bu xususiyat o'sha avvalgi istisnoga ishora qiladi. Odatda null bo'ladi.
StackTraceFaqat o'qishStringIstisnoga olib kelgan metodlar nomlari va signaturalarini o'z ichiga oladi. Debugging uchun juda qimmatli.
HResultO'qish/yozishInt32Boshqariladigan va native kod chegaralarini kesishda ishlatiladigan 32-bitli qiymat. COM API lari HRESULT qaytarganda, CLR Exception dan hosil bo'lgan ob'yekt tashlaydi.
SourceO'qish/yozishStringIstisnoni yaratgan assembly nomini o'z ichiga oladi.
DataFaqat o'qishIDictionaryKalit-qiymat juftliklari to'plami. Istisno tashlovchi kod bu to'plamga yozuvlar qo'shishi mumkin, tutuvchi kod esa ulardan foydalanishi mumkin.
TargetSiteFaqat o'qishMethodBaseIstisnoni tashlagan metodga ishora qiladi.
HelpLinkO'qish/yozishStringFoydalanuvchiga 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.

Muhim

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:

C# private void SomeMethod() { try { ... } catch (Exception e) { ... throw e; // CLR bu yerda istisno kelib chiqqan deb hisoblaydi. // FxCop buni xato deb belgilaydi! } }

Aksincha, agar istisnoni throw kalit so'zi yordamida ob'yektsiz qayta tashlasangiz, CLR stekning boshlang'ich nuqtasini qayta tiklamaydi:

C# private void SomeMethod() { try { ... } catch (Exception e) { ... throw; // Bu CLR ning istisno kelib chiqqan deb hisoblagan joyiga // hech qanday ta'sir ko'rsatmaydi. FxCop buni xato deb belgilamaydi. } }

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:

C# private void SomeMethod() { Boolean trySucceeds = false; try { ... trySucceeds = true; } finally { if (!trySucceeds) { /* catch kodi shu yerga */ } } }

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:

Istisno Ierarxiyasi System.Exception System.AggregateException System.ApplicationException System.Reflection.TargetInvocationException System.Threading.WaitHandleCannotBeOpenedException System.SystemException System.AccessViolationException System.ArgumentException System.ArgumentNullException System.ArgumentOutOfRangeException System.ArithmeticException System.DivideByZeroException System.OverflowException System.ArrayTypeMismatchException System.FormatException System.IndexOutOfRangeException System.InvalidCastException System.InvalidOperationException System.ObjectDisposedException System.IO.IOException System.IO.DirectoryNotFoundException System.IO.FileNotFoundException System.IO.FileLoadException System.MemberAccessException System.FieldAccessException System.MethodAccessException System.NotImplementedException System.NotSupportedException System.NullReferenceException System.OutOfMemoryException System.StackOverflowException System.TypeInitializationException System.UnauthorizedAccessException

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.

Muhim: Versiyalash ta'siri

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:

C# [Serializable] public sealed class Exception<TExceptionArgs> : Exception, ISerializable where TExceptionArgs : ExceptionArgs { private const String c_args = "Args"; // (De)serializatsiya uchun private readonly TExceptionArgs m_args; public TExceptionArgs Args { get { return m_args; } } public Exception(String message = null, Exception innerException = null) : this(null, message, innerException) { } public Exception(TExceptionArgs args, String message = null, Exception innerException = null) : base(message, innerException) { m_args = args; } // Deserializatsiya uchun konstruktor [SecurityPermission(SecurityAction.LinkDemand, Flags=SecurityPermissionFlag.SerializationFormatter)] private Exception(SerializationInfo info, StreamingContext context) : base(info, context) { m_args = (TExceptionArgs)info.GetValue(c_args, typeof(TExceptionArgs)); } // Serializatsiya uchun metod [SecurityPermission(SecurityAction.LinkDemand, Flags=SecurityPermissionFlag.SerializationFormatter)] public override void GetObjectData(SerializationInfo info, StreamingContext context) { info.AddValue(c_args, m_args); base.GetObjectData(info, context); } public override String Message { get { String baseMsg = base.Message; return (m_args == null) ? baseMsg : baseMsg + " (" + m_args.Message + ")"; } } public override Boolean Equals(Object obj) { Exception<TExceptionArgs> other = obj as Exception<TExceptionArgs>; if (other == null) return false; return Object.Equals(m_args, other.m_args) && base.Equals(obj); } public override int GetHashCode() { return base.GetHashCode(); } }

ExceptionArgs bazaviy sinfi juda oddiy:

C# [Serializable] public abstract class ExceptionArgs { public virtual String Message { get { return String.Empty; } } }

Endi disk to'laganligini bildiradigan istisno turini aniqlash juda oson:

C# [Serializable] public sealed class DiskFullExceptionArgs : ExceptionArgs { private readonly String m_diskpath; public DiskFullExceptionArgs(String diskpath) { m_diskpath = diskpath; } public String DiskPath { get { return m_diskpath; } } public override String Message { get { return (m_diskpath == null) ? base.Message : "DiskPath=" + m_diskpath; } } }

Agar qo'shimcha ma'lumot kerak bo'lmasa, yanada soddaroq:

C# [Serializable] public sealed class DiskFullExceptionArgs : ExceptionArgs { }

Va endi istisnoni tashlash va tutish quyidagicha bo'ladi:

C# public static void TestException() { try { throw new Exception<DiskFullExceptionArgs>( new DiskFullExceptionArgs(@"C:\"), "The disk is full"); } catch (Exception<DiskFullExceptionArgs> e) { Console.WriteLine(e.Message); } }

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:

C# Boolean f = "Jeff".Substring(1, 1).ToUpper().EndsWith("E");

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:

  • finally blokida muhim kodni yozing: CLR oqimni catch yoki finally blok ichida bajarilayotganda to'xtatishga ruxsat bermaydi. Shunday qilib, Transfer metodini mustahkamroq qilish mumkin:
C# public static void Transfer(Account from, Account to, Decimal amount) { try { /* bu yerda hech narsa qilmang */ } finally { from -= amount; // Endi thread abort (Thread.Abort/AppDomain.Unload tufayli) // bu yerda sodir bo'la olmaydi to += amount; } }
Diqqat!

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.Contract sinfini ishlatib argumentlar va o'zgaruvchilarni tekshirish. Agar shartnoma bajarilmasa, holat o'zgartirilmasdan oldin istisno tashlanadi.
  • Cheklangan bajarilish hududlari (CER): try blokiga kirishdan oldin barcha bog'liq assemblylarni yuklab, catch va finally bloklaridagi 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:

C# public static void FailFast(String message); public static void FailFast(String message, Exception exception);

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.

Muhim

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.

Muhim: Sinf kutubxonasi dasturchilari uchun

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.

Ilova dasturchilari uchun

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# using System; using System.IO; public sealed class SomeType { private void SomeMethod() { FileStream fs = new FileStream(@"C:\Data.bin", FileMode.Open); try { // Fayldagi birinchi baytga 100 ni bo'lib ko'rsatish. Console.WriteLine(100 / fs.ReadByte()); } finally { // Tozalash kodini finally blokga joylashtiring // istisno yuzaga kelganmi-yo'qmi, fayl yopilishini kafolatlash uchun. if (fs != null) fs.Dispose(); } } }

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:

C# using System; using System.IO; internal sealed class SomeType { private void SomeMethod() { using (FileStream fs = new FileStream(@"C:\Data.bin", FileMode.Open)) { // Fayldagi birinchi baytga 100 ni bo'lib ko'rsatish. Console.WriteLine(100 / fs.ReadByte()); } } }

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.

C# — YOMON NAMUNA try { // Dasturchi muvaffaqiyatsiz bo'lishi mumkinligini bilgan kod... } catch (Exception) { ... }

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.

Eslatma

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:

C# public String CalculateSpreadsheetCell(Int32 row, Int32 column) { String result; try { result = /* elektron jadval katakchasini hisoblash kodi */ } catch (DivideByZeroException) { result = "Can't show value: Divide by zero"; } catch (OverflowException) { result = "Can't show value: Too big"; } return result; }

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:

C# public void SerializeObjectGraph(FileStream fs, IFormatter formatter, Object rootObj) { // Faylning joriy pozitsiyasini saqlash. Int64 beforeSerialization = fs.Position; try { // Ob'yekt grafini faylga serializatsiya qilishga harakat qilish. formatter.Serialize(fs, rootObj); } catch { // Barcha istisnolarni tutish. // HAMMA narsa noto'g'ri bo'lsa, faylni yaxshi holatga qaytarish. fs.Position = beforeSerialization; // Faylni qisqartirish. fs.SetLength(fs.Position); // Chaqiruvchiga nima bo'lganini bildirib, AYNI istisnoni qayta tashlash. throw; } }

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.

C# internal sealed class PhoneBook { private String m_pathname; // manzillar kitobini o'z ichiga olgan fayl yo'li public String GetPhoneNumber(String name) { String phone; FileStream fs = null; try { fs = new FileStream(m_pathname, FileMode.Open); // fs dan nomni topguncha o'qish kodi phone = /* topilgan raqam */ } catch (FileNotFoundException e) { // Boshqa istisno tashlash, asl istisnoni ichki istisno sifatida saqlash. throw new NameNotFoundException(name, e); } catch (IOException e) { // Boshqa istisno tashlash, asl istisnoni ichki istisno sifatida saqlash. throw new NameNotFoundException(name, e); } finally { if (fs != null) fs.Close(); } return phone; } }

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.

Muhim

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:

C# private static void SomeMethod(String filename) { try { // Bu yerda istalgan narsa qiling... } catch (IOException e) { // IOException ob'yektiga fayl nomini qo'shish e.Data.Add("Filename", filename); throw; // Endi qo'shimcha ma'lumotga ega istisno ob'yektini qayta tashlash } }

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.AppDomain ning UnhandledException hodisasi
  • Windows Store ilovalari: Windows.UI.Xaml.Application ning UnhandledException hodisasi
  • Windows Forms: System.Windows.Forms.NativeWindow ning OnThreadException virtual metodi, Application.ThreadException hodisasi
  • WPF: Application.DispatcherUnhandledException va Dispatcher.UnhandledException hodisalari
  • ASP.NET: System.Web.UI.TemplateControl ning Error hodisasi
Eslatma: Corrupted State Exceptions (CSEs)

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:

C# public static Boolean TryParse(String s, out Int32 result); public static Boolean TryParse(String s, NumberStyles styles, IFormatProvider provider, out Int32 result);

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.

Eslatma

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:

C# private static void Demo1() { try { Console.WriteLine("In try"); } finally { // Type1 ning statik konstruktori bu yerda bilvosita chaqiriladi Type1.M(); } } private sealed class Type1 { static Type1() { // Agar bu istisno tashlasa, M hech qachon chaqirilmaydi Console.WriteLine("Type1's static ctor called"); } public static void M() { } }

Natija:

Natija In try Type1's static ctor called

Biz xohlagan narsa shuki, try blokidagi kodni bog'liq catch va finally bloklaridagi kod kafolatlangan bajarilishini bilmasdan bajarilmasin. Buni RuntimeHelpers.PrepareConstrainedRegions() yordamida amalga oshiramiz:

C# private static void Demo2() { // finally dagi kodni imkon qadar erta tayyorlash RuntimeHelpers.PrepareConstrainedRegions(); // System.Runtime.CompilerServices try { Console.WriteLine("In try"); } finally { // Type2 ning statik konstruktori bu yerda bilvosita chaqiriladi Type2.M(); } } public class Type2 { static Type2() { Console.WriteLine("Type2's static ctor called"); } // System.Runtime.ConstrainedExecution nomlar fazosidagi atribut [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] public static void M() { } }

Natija:

Natija Type2's static ctor called In try

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:

C# public sealed class ReliabilityContractAttribute : Attribute { public ReliabilityContractAttribute(Consistency consistencyGuarantee, Cer cer); public Cer Cer { get; } public Consistency ConsistencyGuarantee { get; } } enum Consistency { MayCorruptProcess, MayCorruptAppDomain, MayCorruptInstance, WillNotCorruptState } enum Cer { None, MayFail, Success }

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:

C# public static void ExecuteCodeWithGuaranteedCleanup( TryCode code, CleanupCode backoutCode, Object userData); public delegate void TryCode(Object userData); public delegate void CleanupCode(Object userData, Boolean exceptionThrown);

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:

C# public static class Contract { // Old shart metodlari: [Conditional("CONTRACTS_FULL")] public static void Requires(Boolean condition); public static void EndContractBlock(); // Old shartlar: doimo chaqiriladi public static void Requires<TException>(Boolean condition) where TException : Exception; // Keyingi shart metodlari: [Conditional("CONTRACTS_FULL")] public static void Ensures(Boolean condition); public static void EnsuresOnThrow<TException>(Boolean condition) where TException : Exception; // Maxsus keyingi shart metodlari: doimo chaqiriladi public static T Result<T>(); public static T OldValue<T>(T value); public static T ValueAtReturn<T>(out T value); // Ob'yekt invariant metodlari: [Conditional("CONTRACTS_FULL")] public static void Invariant(Boolean condition); // Quantifier metodlari: doimo chaqiriladi public static Boolean Exists<T>(IEnumerable<T> collection, Predicate<T> predicate); public static Boolean ForAll<T>(IEnumerable<T> collection, Predicate<T> predicate); // Yordamchi metodlar: public static void Assert(Boolean condition); public static void Assume(Boolean condition); // Infratuzilma hodisasi: public static event EventHandler<ContractFailedEventArgs> ContractFailed; }

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:

C# public sealed class Item { /* ... */ } public sealed class ShoppingCart { private List<Item> m_cart = new List<Item>(); private Decimal m_totalCost = 0; public ShoppingCart() { } public void AddItem(Item item) { AddItemHelper(m_cart, item, ref m_totalCost); } private static void AddItemHelper(List<Item> m_cart, Item newItem, ref Decimal totalCost) { // Old shartlar: Contract.Requires(newItem != null); Contract.Requires(Contract.ForAll(m_cart, s => s != newItem)); // Keyingi shartlar: Contract.Ensures(Contract.Exists(m_cart, s => s == newItem)); Contract.Ensures(totalCost >= Contract.OldValue(totalCost)); Contract.EnsuresOnThrow<IOException>( totalCost == Contract.OldValue(totalCost)); // Ba'zi ishlarni bajarish (IOException tashlashi mumkin)... m_cart.Add(newItem); totalCost += 1.00M; } // Ob'yekt invarianti [ContractInvariantMethod] private void ObjectInvariant() { Contract.Invariant(m_totalCost >= 0); } }

AddItemHelper metodi bir nechta shartnomani belgilaydi:

  • Old shartlar: newItem null bo'lmasligi va savatda hali bo'lmasligi kerak
  • Keyingi shartlar: Yangi mahsulot savatda bo'lishi kerak, umumiy narx kamida avvalgidek bo'lishi kerak, va agar IOException tashlanilsa, totalCost o'zgarmagan bo'lishi kerak
  • Ob'yekt invarianti: m_totalCost hech qachon manfiy bo'lmasligi kerak
Muhim: Old shartlar va merosxo'rlik

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 qoldiriladi
  • SetUnwind() chaqirsa — ContractException tashlanadi
  • Hech kim SetHandled, SetUnwind chaqirmasa va istisno tashlamasa — standart qayta ishlash amalga oshiriladi