برخورد با استثناها (Exception Handling)
در
اين درس با چگونگی برخورد با
استثناها (يا خطاهاي غير قابل پيشبيني) در زبان برنامهسازي C# آشنا
ميشويم. اهداف ما در اين درس بشرح زير ميباشد :
1) درک و فهم صحيح يک استثناء
يا Exception
2) پيادهسازي يک روتين براي برخورد با استثناها بوسيله بلوک try/catch
3) آزادسازي منابع تخصيص داده شده به يک برنامه در يک بلوک finally
استثناها، در حقيقت خطاهاي غير منتظره در برنامههاي ما هستند. اکثراً، ميتوان و بايد روشهايي را جهت برخورد با خطاهای موجود در
برنامه در نظر گرفت و آنها را پيادهسازی
کرد. بعنوان مثال، بررسي و تاييد دادههای ورودی کاربران،
بررسی اشياء تهی يا Null و يا
بررسی نوع بازگشتی متد ها، ميتوانند از جمله
مواردی باشند که بايد مورد بررسی قرار گيرند. اين خطاها، خطاهايی معمول و رايجی هستند که اکثر برنامهنويسان از آنها مطلع بوده و راههايی را
برای بررسی آنها در نظر ميگيرند تا از
وقوع آنها جلوگيری نمايند.
اما زمانهايي وجود دارند که از
اتفاق افتادن يک خطا در برنامه بی اطلاع هستيد و انتظار وقوع خطا در برنامه را نداريد. بعنوان مثال، هرگز نميتوان وقوع يک خطای I/O را پيشبينی نمود و يا کمبود
حافظه برای اجرای برنامه و از کار افتادن برنامه به اين دليل. اين موارد بسيار غير منتظره و ناخواسته هستند، اما در صورت وقوع بهتر است
بتوان راهی برای مقابله و برخورد با آنها
پيدا کرده و با آنها برخورد نمود. در اين جاست که مسئله برخورد
با استثناها (Exception Handling) مطرح
ميشود.
هنگاميکه استثنايی رخ ميدهد،
در اصطلاح ميگوئيم که اين استثناء، thrown شده
است. در حقيقت thrown، شیءای
است مشتق شده از کلاس System.Exception که
اطلاعاتی در مورد خطا يا استثناء رخ
داده را نشان ميدهد. در قسمتهای مختلف اين درس با روش مقابله با استثناها با استفاده از بلوک های try/catch آشنا
خواهيد شد.
کلاس System.Exception حاوی تعداد بسيار زيادی متد و property است
که اطلاعات مهمی در مورد استثناء و خطای رخ داده
را در اختيار ما قرار ميدهد. برای مثال، Message يکی
از property های
موجود در اين کلاس است که
اطلاعاتی درباره نوع استثناء رخ داده در اختيار ما قرار ميدهد. StackTrace نيز، اطلاعاتی در مورد Stack (پشته) و محل وقوع خطا در Stack در اختيار ما
قرار خواهد داد.
تشخيص چنين استثناهايی، دقيقاً با روتينهای نوشته شده توسط برنامهنويس در ارتباط هستند و بستگی کامل به الگوريتمی دارد
که وی برای چنين شرايطی در نظر گرفته
است. برای مثال، در صورتيکه با استفاده از متد
System.IO.File.OpenRead()، اقدام به باز کردن فايلی نماييم، احتمال
وقوع (Thrown) يکی از
استثناهای زير وجود دارد :
|
كد: |
|
SecurityException
|
با نگاهی بر مستندات .Net Framework SDK، به سادگی ميتوان از خطاها و استثناهايی که ممکن است يک متد ايجاد
کند، مطلع شد. تنها کافيست
به قسمت Reference/Class Library رفته
و مستندات مربوط به Namespace/Class/Method را
مطالعه نماييد. در اين مستندات هر خطا دارای لينکی به کلاس
تعريف کننده خود است که با استفاده از آن ميتوان متوجه شد که اين استثناء به چه موضوعی مربوط است. پس از اينکه از امکان وقوع خطايي در قسمتی از
برنامه مطلع شديد، لازم است تا با
استفاده از مکانيزمی صحيح به مقابله با آن بپردازيد.
هنگاميکه يک استثناء در اصطلاح thrown ميشود
(يا اتفاق ميافتد) بايد بتوان
به طريقی با آن مقابله نمود. با استفاده از بلوکهای try/catch ميتوان
چنين عملی را انجام داد.
پيادهسازی اين بلوکها بدين شکل هستند که، کدی را که احتمال توليد
استثناء در آن وجود دارد را در بلوک try، و کد مربوط به مقابله
با اين استثناء رخ داده را در
بلوک catch قرار
ميدهيم. در مثال 1-15 چگونگی پيادهسازی يک بلوک try/catch نشان داده شده است. بدليل اينکه
متد OpenRead() احتمال
ايجاد يکی از استثناهای گفته شده در
بالا را دارد، آنرا در بلوک try قرار داده ايم. در
صورتيکه اين خطا رخ دهد، با آن
در بلوک catch مقابله
خواهيم کرد. در مثال 1-15 در صورت بروز استثناء،
پيغامی در مورد استثناء رخ داده و اطلاعاتی در مورد محل وقوع آن در Stack برای کاربر بر روی کنسول نمايش داده ميشود.
نکته : توجه نماييد که کليه مثالهای
موجود در اين درس به طور تعمدی دارای خطاهايی
هستند تا شما با نحوه مقابله با
استثناها آشنا شويد.
|
كد: |
|
using
System; |
هر چند کد موجود در مثال 1-15 تنها داری يک بلوک catch است، اما تمامی استثناهايي که ممکن است رخ
دهند را نشان داده و مورد بررسی
قرار ميدهد زيرا از نوع کلاس پايه استثناء، يعنی Exception تعريف
شده است. در کنترل و مقابله با
استثناها، بايد استثناهای خاص را زودتر از استثناهای کلی مورد بررسی قرار داد. کد زير نحوه استفاده از چند بلوک catch را نشان ميدهد :
|
كد: |
|
catch(FileNotFoundException
fnfex) |
در اين کد، در صورتيکه فايل مورد نظر وجود نداشته
باشد، FileNotFoundException رخ داده و توسط اولين
بلوک catch مورد
بررسی قرار ميگيرد. اما در
صورتيکه PathTooLongException رخ
دهد، توسط دومين بلوک catch بررسی
خواهد شد. علت آنست که برای PathTooLongException بلوک catch ای در نظر گرفته نشده
است و تنها گزينه موجود جهت بررسی اين استثناء بلوک کلی Exception است.
نکته ای که در اينجا بايد
بدان توجه نمود آنست که هرچه بلوکهای catch مورد
استفاده خاص تر و جزئی تر باشند،
پيغامها و اطلاعات مفيدتری در مورد خطا ميتوان بدست آورد.
استثناهايی که مورد بررسی قرار نگيرند، در بالای Stack نگهداری می شوند تا زمانيکه
بلوک try/catch مناسبی
مربوط به آنها يافت شود. در صورتيکه برای استثناء رخ داده
بلوک try/catch در نظر
گرفته نشده باشد، برنامه متوقف شده و پيغام خطايي ظاهر ميگردد.
اين چنين حالتی بسيار نا مناسب بوده و کاربران را دچار آشفتگی خواهد کرد. استفاده از روشهای مقابله با استثناها در برنامه، روشی مناسب و
رايج است و باعث قدرتمند تر شدن برنامه
ميشود.
يکی از حالتهای بسيار خطرناک و نامناسب در زمان
وقوع استثناها، هنگامی است که استثناء يا خطای رخ داده باعث از کار افتادن برنامه شود ولی منابع تخصيص داده شده به آن برنامه آزاد نشده
باشند. هر چند بلوک catch برای
برخورد با استثناها مناسب است ولی در مورد گفته شده نمی تواند کمکی به حل مشکل نمايد. برای چنين شرايطی که نياز به آزادسازی منابع تخصيص
داده شده به يک برنامه داريم، از بلوک finally استفاده ميکنيم.
کد نشان داده شده در مثال 2-15، به خوبی روش استفاده از بلوک finally را
نشان ميدهد. همانطور که حتماً ميدانيد،
رشته های فايلی پس از اينکه کار با آنها به اتمام ميرسد بايد بسته شوند، در غير اينصورت هيچ برنامه ديگری قادر به استفاده از آنها نخواهد
بود. در اين حالت، رشته
فايلی، منبعی است که ميخواهيم پس از باز شدن و اتمام کار، بسته شده و به سيستم باز گردد. در مثال 2-15، outStream
با موفقيت باز ميشود، بدين معنا که برنامه handle ای به يک فايل باز شده در اختيار دارد. اما
زمانيکه ميخواهيم inStraem را باز
کنيم، استثناء FileNotFound رخ
داده و باعث ميشود که کنترل برنامه سريعاً
به بلوک catch منتقل
گردد.
در بلوک catch ميتوانيم
فايل outStream را
ببنديم. اما برنامه تنها زمانی به بلوک catch وارد
ميشود که استثنايي رخ دهد. پس اگر هيچ استثنائی رخ
نداده و برنامه به درستی عمل نمايد، فايل باز شده outStream هرگز
بسته نشده و يکی از منابع سيستم به آن بازگردانده نميشود. بنابراين بايد برای بستن اين فايل نيز فکری کرد. اين کاری است که در بلوک finally رخ می دهد. بدين معنا که در هر حالت، چه برنامه با استثنائی روبرو شود و چه نشود، قبل از
خروج از برنامه فايل باز شده، بسته
خواهد شد. در حقيقت ميتوان گفت بلوک finally، بلوکی است که تضمين مينمايد در هر شرايطی اجرا خواهد شد. پس برای حصول اطمينان
از اينکه منابع مورد استفاده برنامه پس
از خروج برنامه، به سيستم باز گردانده ميشوند، ميتوان از اين
بلوک استفاده کرد.
|
كد: |
|
using
System; |
استفاده از بلوک finally الزامی نيست،
اما روشی مناسب برای بالا بردن
کارآيي برنامه است. ممکن است سوالی در اينجا مطرح شود : در صورتيکه پس از بلوک catch و بدون استفاده
از بلوک finally، فايل باز شده را ببنديم، باز هم منبع تخصيص داده شده به برنامه آزاد
می شود. پس چه دليلی برای استفاده از بلوک
finally وجود دارد؟ در پاسخ به اين سوال بايد گفت، در شرايط
نرمال که تمامی برنامه بطور طبيعی
اجرا ميشود و اتفاق خاصی رخ نميدهد، می توان گفت که دستورات
بعد از بلوک catch اجرا شده و
منبع تخصيص داده شده به سيستم آزاد می شود. اما برای بررسی هميشه بايد بدترين حالت را در نظر گرفت. فرض کنيد درون خود
بلوک catch استثنائی رخ دهد که شما آنرا پيشبينی
نکردهايد و يا اين استثناء باعت متوقف شدن برنامه
شود، در چنين حالتی کدهای موجود بعد از بلوک catch هرگر اجرا نخواهند شد و فايل همچنان
باز ميماند. اما با استفاده از بلوک finally ميتوان مطمئن بود که کد موجود در
اين بلوک حتماً اجرا شده و منبع تخصيص داده شده به برنامه آزاد ميگردد.
در اينجا به پايان درس پانزدهم رسيديم. هم اکنون می بايست
درک صحيحی از استثناء بدست آورده
باشيد. همچنين ميتوانيد به سادگی الگوريتمهايي جهت بررسی
استثناها بوسيله بلوکهای try/catch پيادهسازی
نماييد. بعلاوه ميتوانيد با ساتفاده از
بلوک finally مطمئن باشيد که که منابع تخصيص
داده شده به برنامه، به سيستم باز
خواهند گشت چراکه اين بلوک حتما اجرا ميشود و ميتوان کدهای مهمی را که ميخواهيم تحت هر شرايطی اجرا شوند را درون آن قرار داد
انجام پروژه های برنامه نویسی