مدیریت استثنا (خطا) در سی شارپ
در هنگام برنامهنویسی اشتباه کردن یک امر متداول است؛ حتی حرفهایترین و کارآزمودهترین برنامهنویسها هم ممکن است در سادهترین مسائل مرتکب خطا شوند. این خطا میتواند حاصل اشتباه محاسباتی، منطق غلط و یا حتی یک اشتباه تایپی باشد.
به طور معمول IDEهای کدنویسی آنقدر هوشمند هستند که میتوانند خطا و مکان آن را تشخیص داده و در مواردی راه اصلاحش را به برنامهنویس پیشنهاد دهند. اما خطاهایی هم وجود دارند که برنامهنویس و کامپایلر قادر به تشخیصشان نیستند و بعد از کامپایل شدن و هنگام اجرای برنامه خودشان را نشان میدهند.
به خطاهایی که در زمان اجرای برنامه رخ میدهند استثنا (به انگلیسی: exception) گفته میشود. از آنجایی که برنامهنویس بعد از اجرا شدن برنامه امکان مدیریت کد و اصلاح آن را ندارد کد به هیچوجه نباید باعث بروز خطا و مشکل شود؛ چرا که حتی یک استثنا میتواند باعث کرش کردن برنامه شود.
با این حال در برخی از موارد رخ دادن یک خطا خارج از کنترل برنامهنویس است. برای مثال فرض کنید یک برنامه یا بازی باید از طریق اینترنت به یک دیتابیس متصل شده و از روی آن اطلاعاتی را بخواند. دلایل زیادی وجود دارد که ممکن است این فرآیند با مشکل روبرو شود؛ دلایلی مثل قطع بودن اینترنت، شلوغی سرور و عدم پاسخگویی، حذف شدن فایل از روی سرور و…
در چنین مواردی خطاهایی رخ میدهند که برنامهنویس نقشی در ایجادشان ندارد. در این حالت برای جلوگیری از کرش کردن برنامه باید تمام حالاتی که ممکن است موجب بروز خطا شوند را شناسایی کرده و تعیین کرد که در صورت بروز خطا کد جایگزین اجرا شود (به عنوان مثال پیام «دریافت داده با خطا مواجه شد» به کاربر نمایش داده شود). به این کار مدیریت استثنا گفته میشود.
مدیریت استثنا به عملیاتی گفته میشود که در طی آن قابلیتهایی به کد اضافه میشود تا در برابر هر نوع استثنا عکسالعمل مناسب داده شده و از کرش برنامه جلوگیری شود.
پیش از بررسی نحوهی مدیریت استثنا در سی شارپ ابتدا با انواع استثناهای این زبان آشنا میشویم. در زبان سی شارپ دو نوع استثنا وجود دارد.
استثناهای ایجاد شده توسط CLR
این استثناها به هنگام برخورد با مشکلاتی که وقوع آنها قبلاً توسط دات نت پیشبینی شده «پرتاب» میشوند (دلیل استفاده از کلمهی پرتاب در ادامه توضیح داده میشود).
برای مثال کد زیر را در نظر بگیرید:
در این کد یک عدد بر صفر تقسیم میشود که این عمل در ریاضی امکانپذیر نیست. بنابراین در صورتی که تلاشی برای اجرای این کد انجام شود در هنگام اجرای کد به همراه Debug (با کلید میانبر F5)، اجرا به محض رسیدن به تقسیم عدد به صفر توسط debugger متوقف (break) شده و به محیط IDE (در اینجا ویژوال استودیو) باز خواهیم گشت:
همانطور که در تصویر مشاهده میشود کامپایلر یک استثنا از نوع DivideByZeroException
گرفته و اخطاری مبنی بر handle نبودن (مدیریت نشدن) آن داده میشود. اگر برنامه در حالت Debug اجرا نمیشد (برای مثال با انتخاب گزینه Start Without Debugging از منوی Debug) یک exception مدیریت (handle) نشده میتوانست باعث کرش کردن برنامه شود.
در سی شارپ استثناها در واقع کلاس هستند و هنگام بروز خطا از روی استثنای مربوطه شی ساخته میشود (Exception Object). اگر بخواهیم بیشتر به این موضوع وارد شویم باید بدانیم کلاسی به نام Exception
وجود دارد که از کلاس Object
مشتق شده است. این کلاس خود فرزندی تحت عنوان SystemException
دارد که تمامی استثناهای از قبل ایجاد شدهی دات نت فریمورک از این کلاس (و در چند مورد از فرزندان این کلاس) ارثبری میکنند.
استثناهای متداول دات نت فریمورک که از قبل ایجاد شدهاند
SystemException
مشتق شدهاند
استثناهایی که از کلاس IndexOutOfRangeException
: هنگام تلاش کاربر برای دسترسی به عضوی از آرایه که index آن در خارج از محدوده مجاز قرار دارد پرتاب میشود.NullReferenceException
: هنگام ارجاع به یک شی null پرتاب میشود.AccessViolationException
: هنگام تلاش کاربر برای دسترسی به حافظه نامعتبر (مانند کد مدیریت نشده، کد Unsafe نادرست و یا استفاده از اشارهگر نامعتبر) پرتاب میشود.InvalidOperationException
: توسط متد و هنگام رسیدن آن به حالتی نامعتبر پرتاب میشود.ArgumentException
: کلاس پایه Argument ExceptionهاExternalException
: کلاس پایه استثناهایی که در خارج از runtime اتفاق افتاده یا هدفگیری میشوند.
ArgumentException
مشتق شدهاند
استثناهایی که از کلاس ArgumentNullException
: هنگام قرارگیری یک مقدار null به عنوان آرگومان در یک متد، توسط آن متد پرتاب میشود.ArgumentOutOfRangeException
: هنگام قرارگیری یک مقدار خارج از بازه و بیمعنی به عنوان آرگومان در یک متد، توسط آن متد پرتاب میشود. البته اگر این مقدار index یک آرایه باشد با استثنایIndexOutOfRangeException
روبرو می شویم.
مدیریت خطا با بلوکهای try/catch
در برنامهنویسی هنگام بروز یک استثنا از فعل پرتاب شدن (throw) استفاده میشود. دلیل استفاده از فعل فوق این است که برنامهنویس باید در نقاطی از کد که به صورت بالقوه امکان ایجاد خطایی وجود دارد اقدام به «گرفتن» یا catch کردن آن خطا نماید تا موجب کرش کردن برنامه نشود. برای پیادهسازی چنین منطقی از بلوکهای try/catch استفاده میکنیم. این ساختار از دو بلوک به نامهای بلوک try
و بلوک catch
تشکیل شده است.
در بلوک
try
کدهایی را مینویسیم که ممکن است با بروز خطا مواجه شوند.در بلوک
catch
کدهایی را مینویسیم که میخواهیم در صورت بروز خطا (هنگام اجرای کدهای بلوکtry
) اجرا شوند.
یک ساختار try/catch به صورت زیر میباشد:
همانطور که در کد بالا قابل ملاحظه است بلوک catch
میتواند همانند یک متد دارای پارامتر باشد. این پارامتر که در کلیترین حالت خود از نوع دادهی Exception
است به شی خطایی اشاره میکند که موجب فراخوانی بلوک catch
شده است (در کد بالا Exception e
). با استفاده از این پارامتر میتوان در بلوک catch
متدهایی را بر روی این شی صدا زد (مثل متدهایی که پیغام خطا را در قالب string به کاربر بازمیگردانند).
اصلیترین نوع استثنا نوع دادهی Exception
است که تمام استثناهای دیگر از روی آن ارثبری کردهاند و بنابراین استفاده از این نوع داده به عنوان پارامتر در بلوک catch
باعث میشود تمام استثناهای رخ داده هنگام اجرای بلوک try
توسط این بلوک گرفته شوند. اما در صورتی که بخواهید برنامه به ازای استثناهای تخصصیتر کدهای مختلفی را اجرا کند میتوانید برای هرکدام بلوک catch
جداگانه بنویسید. در این حالت هر بار که یک استثنا پرتاب میشود بلوک catch
مربوطه آن را دریافت کرده و سپس به جای کرش کردن برنامه آن بلوک catch
اجرا میشود.