اندازهی قلم متن
تخمین مدت زمان مطالعهی مطلب:
سه دقیقه
حتما شما هم متوجه شدید که وقتی رخداد یک استثناء را با استفاده از try و catch کنترل میکنیم، هر چیزی که بعد از بسته شدن تگ catch بنویسیم، در هر صورت اجرا میشود.
پس فلسفه استفاده از بخش finally چیست؟
در قسمت finally منابع تخصیص داده شده در try را آزاد میکنیم. کد موجود در این قسمت به هر روی اجرا میشود چه استثناء رخ دهد چه ندهد. البته اگر استثناء رخ داده شده در لیست استثناء هایی که برای آنها catch انجام دادیم نباشد، قسمت finally هم عمل نخواهد کرد مگر اینکه از catch به صورت سراسری استفاده کنیم.
اما مهمترین مزیتی که finally ایجاد میکند در این است که حتی اگر در قسمت try با استفاده از دستوراتی مثل return یا break یا continue از ادامه کد منصرف شویم و مثلا مقداری برگردانیم، چه خطا رخ دهد یا ندهد کد موجود در finally اجرا میشود در حالی که کد نوشته شده بعد از try catch finally فقط در صورتی اجرا میشود که به طور منطقی اجرای برنامه به آن نقطه برسد. اجازه بدهید با یک مثال توضیح دهم. اگر کد زیر را اجرا کنیم:
برنامه خطای تقسیم بر صفر میدهد اما با توجه به کدی که نوشتیم، عدد -1 به خروجی خواهد رفت. در عین حال عبارت ok و can you reach here در خروجی چاپ شده است. اما حال اگر مشکل تقسیم بر صفر را حل کنیم، آیا باز هم عبارت can you reach here در خروجی چاپ خواهد شد؟
مشاهده میکنید که مقدار 1 برگردانده میشود و عبارت can you reach here در خروجی چاپ نمیشود ولی همچنان عبارت ok که در finally ذکر شده در خروجی چاپ میشود. یک مثال خوب استفاده از چنین وضعیتی، زمانی است که شما یک ارتباط با بانک اطلاعاتی باز میکنید، و نتیجه یک عملیات را با دستور return به کاربر بر میگردانید. مسئله این است که در این وضعیت چگونه ارتباط با دیتابیس بسته شده و منابع آزاد میگردند؟ اگر در حین عملیات بانک اطلاعاتی، خطایی رخ دهد یا ندهد، و شما دستور آزاد سازی منابع و بستن ارتباط را در داخل قسمت finally نوشته باشید، وقتی دستور return فراخوانی میشود، ابتدا منابع آزاد و سپس مقدار به خروجی بر میگردد.
try { int i=0; string s = "hello"; i = Convert.ToInt32(s); } catch (Exception ex) { Console.WriteLine("Error"); } Console.WriteLine("I am here!");
پس فلسفه استفاده از بخش finally چیست؟
در قسمت finally منابع تخصیص داده شده در try را آزاد میکنیم. کد موجود در این قسمت به هر روی اجرا میشود چه استثناء رخ دهد چه ندهد. البته اگر استثناء رخ داده شده در لیست استثناء هایی که برای آنها catch انجام دادیم نباشد، قسمت finally هم عمل نخواهد کرد مگر اینکه از catch به صورت سراسری استفاده کنیم.
اما مهمترین مزیتی که finally ایجاد میکند در این است که حتی اگر در قسمت try با استفاده از دستوراتی مثل return یا break یا continue از ادامه کد منصرف شویم و مثلا مقداری برگردانیم، چه خطا رخ دهد یا ندهد کد موجود در finally اجرا میشود در حالی که کد نوشته شده بعد از try catch finally فقط در صورتی اجرا میشود که به طور منطقی اجرای برنامه به آن نقطه برسد. اجازه بدهید با یک مثال توضیح دهم. اگر کد زیر را اجرا کنیم:
public static int GetMyInt() { try { for (int i=10;i>=0;i--) Console.WriteLine(10/i); return 1; } catch { Console.WriteLine("Error!"); } finally { Console.WriteLine("ok"); } Console.WriteLine("can you reach here?"); return -1; }
برنامه خطای تقسیم بر صفر میدهد اما با توجه به کدی که نوشتیم، عدد -1 به خروجی خواهد رفت. در عین حال عبارت ok و can you reach here در خروجی چاپ شده است. اما حال اگر مشکل تقسیم بر صفر را حل کنیم، آیا باز هم عبارت can you reach here در خروجی چاپ خواهد شد؟
public static int GetMyInt() { try { for (int i=10;i>=1;i--) Console.WriteLine(10/i); return 1; } catch { Console.WriteLine("Error!"); } finally { Console.WriteLine("ok"); } Console.WriteLine("can you reach here?"); return -1; }
مشاهده میکنید که مقدار 1 برگردانده میشود و عبارت can you reach here در خروجی چاپ نمیشود ولی همچنان عبارت ok که در finally ذکر شده در خروجی چاپ میشود. یک مثال خوب استفاده از چنین وضعیتی، زمانی است که شما یک ارتباط با بانک اطلاعاتی باز میکنید، و نتیجه یک عملیات را با دستور return به کاربر بر میگردانید. مسئله این است که در این وضعیت چگونه ارتباط با دیتابیس بسته شده و منابع آزاد میگردند؟ اگر در حین عملیات بانک اطلاعاتی، خطایی رخ دهد یا ندهد، و شما دستور آزاد سازی منابع و بستن ارتباط را در داخل قسمت finally نوشته باشید، وقتی دستور return فراخوانی میشود، ابتدا منابع آزاد و سپس مقدار به خروجی بر میگردد.
public int GetUserId(string nickname) { SqlConnection connection = new SqlConnection(...); SqlCommand command = connection.CreateCommand(); command.CommandText = "select id from users where nickname like @nickname"; command.Parameters.Add(new SqlParameter("@nickname", nickname)); try { connection.Open(); return Convert.ToInt32(command.ExecuteScalar()); } catch(SqlException exception) { // some exception handling return -1; } finally { if (connection.State == ConnectionState.Open) connection.Close(); } // if all things works, you can not reach here }