در هر برنامه ای، همواره خطاهایی رخ داده و اجرای امور با مشکل مواجه می شود. در زبان C#، کامپایلر هوشمند و کارآمدی در اختیار ما قرار داده شده که به کمک آن می توانیم از برخی اشتباه رایج جلوگیری کنیم. البته که برنامه همه خطاهای کد را نخواهید دید و در چنین مواردی، چهارچوب کاری .NET، یک خطا یا Exception اعلام کرده تا به ما بگوید جایی در کد دارای اشکال است. در درس های قبل و در بخش آموزش کار با آرایه ها Arrays در C#، نشان دادیم چگونه با وارد کردن آیتم هایی بیش از تعداد تعیین شده برای آرایه، می توان برنامه را با خطا مواجه کرد.
بیایید نگاهی به کد مثال بخش آموزش آرایه ها در C# بیندازیم :
کد:
using System;using System.Collections; namespace ConsoleApplication1{ class Program { static void Main(string[] args) { int[] numbers = new int[2]; numbers[0] = 23; numbers[1] = 32; numbers[2] = 42; foreach(int i in numbers) Console.WriteLine(i); Console.ReadLine(); } }}
کد فوق را اجرا نموده و مشاهده کنید که برنامه خطا اعلام می کند. خطای کد این است که ما یک آرایه با دو عضو را تعریف نموده، ولی سعی داریم تا 3 عضو را در آن قرار دهیم.
وقتی در محیط یک IDE مثل Visual Studio کد فوق را اجرا کنید، برنامه چند راه حل یا توضیح را درباره خطای رخ داده، اعلام می کند. اما اگر بخواهید برنامه را با دابل کلیک بر روی فایل EXE آن اجرا کنید، یک خطای نامفهوم برایتان رخ داده و اجرای فایل متوقف می شود.
اگر احتمال می دهید در بخشی از کد خطا رخ خواهد داد، بایستی بتوانید ان را مدیریت کنید. اینجاست که Exception در C# به کمک ما می آید. کد مثال فوق را کمی تغییر داده و به صورت زیر بازنویسی کرده ایم :
کد:
int[] numbers = new int[2];try{ numbers[0] = 23; numbers[1] = 32; numbers[2] = 42; foreach(int i in numbers) Console.WriteLine(i);}catch{ Console.WriteLine(\"Something went wrong!\");}Console.ReadLine();
بیایید با یکی از کارآمدترین ابزارهای کدیریت خطا در C#، یعنی ساختار try-catch آشنا شویم. بار دیگر برنامه را اجرا کرده و تفاوت را با سری قبل مشاهده کنید. اما به جای این که ویژوال استودیو یا ویندوز نوع خطا را به ما اعلام کند، خودمان می توانیم پیام آن را تعیین کرده یا به دلخواه تنظیم کنیم. به صورت کد زیر :
کد:
catch(Exception ex){ Console.WriteLine(\"An error occured: \" + ex.Message);}
همانطور که مشاهده می کنید، یک بخش جدید را به ساختار دستوری try-catch اضافه کرده ایم. به وسیله کد اضافه شده، می خواهیم بدانیم کدام خطا در برنامه رخ داده است و در این مثال از کلاس اصلی خطاها، یعنی Exception استفاده کرده ایم. با انجام کد فوق، ما اطلاعاتی راجع به خطاهای رخ داده کسب خواهیم کرد و به وسیله خاصیت Message-Property یک توضیح قابل فهم را درباره خطا مشاهده می کنیم.
همانطور که گفتیم Exception رایج ترین نوع خطا در هنگام اجرای برنامه های C# است. قوانین خطایابی در زبان متنی شارپ به ما می گوید همواره بایستی از EXCEPTION که کمترین احتمال رخ دادن را دارند، در کد خود استفاده کنیم. اما در مثال این درس، ما در اصل می دانستیم چه خطایی ممکن است در برنامه رخ دهد. اما چگونه؟
به دلیل این که ویژوال استودیو خطای مدیریت نشده را به ما اعلام کرد. اما اگر شک دارید، خطا کدام است، معمولا شرح خطا یا Exception توسط برنامه بیان می شود. راه دیگر برای یافتن نوع خطا، استفاده از کلاس Exception Class است. برای این منظور کد مثال قبل را به صورت زیر تغییر دهید :
کد:
Console.WriteLine(\"An error occured: \" + ex.GetType().ToString());
نتیجه کد فوق، همانطور که می توان پیش بینی کرد خطای \\\"مقدار خارج از محدوده\\\" یا Index Out Range Exception می باشد. ما بایستی این خطا را مدیریت یا handle کنیم. اما در هر زمان ، می توان بیش از یک خطا را در کد مدیریت نمود و در صورت رخ دادن هر خطا تصمیم لازم را تعیین کرد. بنابراین کد مثال قبل را به صورت زیر تغییر دهید :
کد:
catch(IndexOutOfRangeException ex){ Console.WriteLine(\"An index was out of range!\");}catch(Exception ex){ Console.WriteLine(\"Some sort of error occured: \" + ex.Message);}
همانگونه که در کد فوق تعیین کرده ایم، برنامه ابتدا به دنبال خطاهای Index Out Range Exception می گردد. اگر از روش دوم برای خطایابی استفاده کنیم، ساختار Catch توسط کلاس Exception Class مشکل کد را پیدا خواهد کرد زیرا تمامی Exception ها از این کلاس مشتق می شوند. به عبارت دیگر، در خطایابی کدها بایستی همواره از خطایی که احتمال رخ دادن آن بیشتر است، اول استفاده کنیم.
چیز دیگری که بایستی در مورد مدیریت خطاها Exception Handling بایستی بدانید، بلوک کد نهایی یا finally block است. بلوک finally را می توان به انتهای ساختار دستوری try-catch اضافه کرده یا بر حسب نیاز، به صورت جداگانه به کار برد.
کد تعیین شده در بخش finally در هر صورت اجرا خواهد شد، چه Exception رخ دهد یا خیر. بنابراین محل خوبی برای قرار دادن کدهایی مثل قطع ارتباط با فایل های برنامه یا تخریب اشیایی که دیگر به آن ها نیاز نداریم، می باشد. از آنجایی که مثال های قبلی ارائه شده، نسبتا ساده بودند، بنابراین به عملیات پاکسازی یا Clean up خاصی نیاز نداشتیم و خود کننده خودکار C# یعنی Garbage Collector کارهای لازم را انجام می داد.
اما گاهی اوقات در کدهای گسترده تر، نیاز به قطعه کد نهایی یا finally داریم که مثال زیر، نحوه استفاده از این امکان را به صورت عملی نشان داده است :
کد:
int[] numbers = new int[2];try{ numbers[0] = 23; numbers[1] = 32; numbers[2] = 42; foreach(int i in numbers) Console.WriteLine(i);}catch(IndexOutOfRangeException ex){ Console.WriteLine(\"An index was out of range!\");}catch(Exception ex){ Console.WriteLine(\"Some sort of error occured: \" + ex.Message);}finally{ Console.WriteLine(\"It\'s the end of our try block. Time to clean up!\");}Console.ReadLine();
اگر کد مثال فوق را اجرا نمایید، مشاهده خواهید کرد که هم دستورات بخش خطا یا Exception و هم دستورات بخش نهایی یا finally code اجرا می شوند. اگر خط کدی که عدد 42 را به آرایه مثال اضافه می کند، پاک نمایید، خواهید دید که فقط دستورات بخش finally انجام می شود، زیرا دیگر خطایی در کد وجود ندارد.
مشئله مهم دیگری که بایستی در مورد exception ها بدانید، نحوه تاثیرگذاری خطاها بر روی متدها و توابعی است که exception در آن ها رخ می دهد. همه خطاهای غیر مدیریت شده در برنامه شما، لزوما از بین برنده برنامه نبوده و باعث خروج استمراری برنامه نمی شوند. اما وقتی هم که باعث توقف کامل برنامه نمی شوند، نبایستی انتظار داشته باشید که بقیه کد متد یا تابع دارای exception اجرا شود.
به عبارت دیگر، اگر شما exception را مدیریت کنید، خطاهای کد بعد از بلوک دستوری try اجرا می شوند، نه دستورات درون خود آن. در مثال فوق، حلقه ای که مقادیر آرایه array را در خروجی چاپ می کند، هرگز اجرا نخواهد شد، زیرا به دلیل بروز exception یا خطا در کد try، برنامه به خط اول بعد از بلوک try و بخش های Catch یا finally پرش می کند. اما خط آخر که در آن برای جلوگیری از خروج ویندوز از برنامه، منتظر دریافت ورودی از کاربر هستیم (Consule.ReadLine)، همواره اجرا شود. بنابراین در زمان طراحی ساختار های دستوری try-catch بایستی این نکته را مد نظر داشته باشید.