در س دوم– واسطها (Interfaces)
در این درس با واسطها در زبان C# آشنا خواهیم شد. اهداف این درس بشرح زیر میباشند :
4- پیادهسازی ارثبری در interface ها
واسطها از لحاظ ظاهری بسیار شبیه به کلاس هستند با این تفاوت که دارای هیچ گونه پیادهسازی نمیباشند. تنها چیزی که در interface به چشم میخورد تعاریفی نظیر رخدادها، متدها، اندیکسرها و یا property ها است. یکی از دلایل اینکه واسطها تنها دارای تعاریف هستند و پیادهسازی ندارند آنست که یک interface میتوان توسط چندین کلاس یا property مورد ارثبری قرار گیرد، از اینرو هر کلاس یا property خواستار آنست که خود به پیادهسازی اعضا بپردازد.
حال باید دید چرا با توجه به اینکه interface ها دارای پیادهسازی نیستند مورد استفاده قرار میگیرند یا بهتر بگوئیم سودمندی استفاده از interface ها در چیست؟ تصور کنید که در یک برنامه با مولفههایی سروکار دارید که متغیرند ولی دارای فیلدها یا متدهایی با نامهای یکسانی هستند و باید نام این متدها نیز یکسان باشد. با استفاده از یک interface مناسب میتوان تنها متدها و یا فیلدهای مورد نظر را اعلان نمود و سپس کلاسها و یا property های مورد از آن interface ارثبری نمایند. در این حالت تمامی کلاسها و property ها دارای فیلدها و یا متدهایی همنام هستند ولی هر یک پیادهسازی خاصی از آنها را اعمال مینمایند.
نکته مهم دیگر درباره interface ها، استفاده و کاربرد آنها در برنامههای بزرگی است که برنامهها و یا اشیاؤ مختلفی در تماس و تراکنش (transact) هستند. تصور کنید کلاسی در یک برنامه با کلاسی دیگر در برنامهای دیگر در ارتباط باشد. فرض کنید این کلاس متدی دارد که مقداری از نوع int بازمیگرداند. پس از مدتی طراح برنامه به این نتیجه میرسد که استفاده از int پاسخگوی مشکلش نیست و باید از long استفاده نماید. حال شرایط را در نظر بگیرید که برای تغییر یک چنین مسئله سادهای چه مشکل بزرگی پیش خواهد آمد. تمامی فیلدهای مورتبط با این متد باید تغییر داده شوند. در ضمن از مسئله side effect نیز نمیتوان چشم پوشی کرد.( تاثیرات ناخواسته و غیر منتظره و یا به عبارتی پیش بینی نشده که متغیر یا فیلدی بر روی متغیر یا فیلدی دیگر اعمال میکند، در اصطلاح side effect گفته میشود.) حال فرض کنید که در ابتدا interface ای طراحی شده بود. درصورت اعمال جزئیترین تغییر در برنامه مشکل تبدیل int به long قابل حل بود، چراکه کاربر یا برنامه و در کل user برنامه در هنگام استفاده از یک interface با پیادهسازی پشت پرده آن کاری ندارد و یا بهتر بگوئیم امکان دسترسی به آن را ندارد. از اینرو اعمال تغییرات درون آن تاثیری بر رفتار کاربر نخواهد داشت و حتی کاربر از آن مطلع نیز نمیشود. در مفاهیم کلی شیء گرایی، interface ها یکی از مهمترین و کاربردی ترین اجزاء هستند که در صورت درک صحیح بسیار مفید واقع میشوند. یکی از مثالهای مشهود درباره interface ها (البته در سطحی پیشرفته تر و بالاتر) رابطهای کاربر گرافیکی (GUI) هستند. کاربر تنها با این رابط سروکار دارد و کاری به نحوه عملیات پشت پرده آن ندارد و اعمال تغییرات در پیادهسازی interface کاربر را تحت تاثیر قرار نمیدهد.
از دیدگاه تکنیکی، واسطها بسط مفهومی هستند که از آن به عنوان انتزاع (Abstract) یاد میکنیم. در کلاسهای انتزاعی (که با کلمه کلید abstract مشخص میشدند.) سازندة کلاس قدر بود تا فرم کلاس خود را مشخص نماید : نام متدها، نوع بازگشتی آنها و تعداد و نوع پارامتر آنها، اما بدون پیادهسازی بدنه متد. یک interface همچنین میتواند دارای فیلدهایی باشد که تمامی آنها static و final هستند. یک interface تنها یک فرم کلی را بدون پیادهسازی به نمایش میگذارد.
از این دیدگاه، یک واسط بیان میدارد که : " این فرم کلی است که تمامی کلاسهایی که این واسط را پیادهسازی میکنند، باید آنرا داشته باشند." از سوی دیگر کلاسها و اشیاء دیگری که از کلاسی که از یک واسط مشتق شده استفاده میکنند، میدانند که این کلاس حتماً تمامی متدها و اعضای واسط را پیادهسازی میکند و میتوانند به راحتی از آن متدها و اعضا استفاده نمایند. پس به طور کلی میتوانیم بگوئیم که واسطها بمنظور ایجاد یک پروتکل (protocol) بین کلاسها مورد استفاده قرار میگیرند. (همچنان که برخی از زبانهای برنامهسازی بجای استفاده از کلمه کلیدی interface از protocol استفاده مینمایند.)
به دلیل اینکه کلاسها و ساختارهایی که از interface ها ارثبری میکنند موظف به پیادهسازی و تعریف آنها هستند، قانون و قاعدهای در این باره ایجاد میگردد. برای مثال اگر کلاس A از واسط IDisposable ارثبری کند، این ضمانت بوجود میآید که کلاس A دارای متد Dispose() است، که تنها عضو interface نیز میباشد. هر کدی که میخواهد از کلاس A استفاده کند، ابتدا چک مینماید که آیا کلاس A واسط IDisposable را پیادهسازی نموده یا خیر. اگر پاسخ مثبت باشد آنگاه کد متوجه میشود که میتواند از متد A.Dispose() نیز استفاده نماید. در زیر نحوه اعلان یک واسط نمایش داده شده است.
interface IMyInterface
{
void MethodToImplement();
}
در این مثال نحوه اعلان واسطی با نام IMyInterface نشان داده شده است. یک قاعده (نه قانون!) برای نامگذاری واسطها آنست که نام واسطها را با "I" آغاز کنیم که اختصار کلمه interface است. در interface این مثال تنها یک متد وجود دارد. این متد میتوان هر متدی با انواع مختلف پارامترها و نوع بازگشتی باشد. توجه نمایید همانطور که گفته شد این متد دارای پیادهسازی نیست و تنها اعلان شده است. نکته دیگر که باید به ان توجه کنید آنست که این متد به جای داشتن {} به عنوان بلوک خود، دارای ; در انتهای اعلان خود میباشد. علت این امر آنست که interface تنها نوع بازگشتی و پارامترهای متد را مشخص مینماید و کلاس یا شیای که از آن ارث میبرد باید آنرا پیادهسازی نماید. مثال زیر نحوه استفاده از این واسط را نشان میدهد.
مثال 1-13 : استفاده از واسطها و ارثبری از آنها
class InterfaceImplementer : IMyInterface
{
static void Main()
{
InterfaceImplementer iImp = new InterfaceImplementer();
iImp.MethodToImplement();
}
public void MethodToImplement()
{
Console.WriteLine("MethodToImplement() called.");
}
}
در این مثال، کلاس InterfaceImplementer همانند ارثبری از یک کلاس، از واسط IMyInterface ارثبری کرده است. حال که این کلاس از واسط مورد نظر ارثبری کرده است، باید، توجه نمایید باید، تمامی اعضای آنرا پیادهسازی کند. در این مثال این عمل با پیادهسازی تنها عضو واسط یعنی متد MethodToImplement() انجام گرفته است. توجه نمایید که پیادهسازی متد باید دقیقا از لحاظ نوع بازگشتی و تعداد و نوع پارامترها شبیه به اعلان موجود در واسط باشد، کوچکترین تغییری باعث ایجاد خطای کامپایلر میشود. مثال زیر نحوه ارثبری واسطها از یکدیگر نیز نمایش داده شده است.
مثال 2-13 : ارثبری واسطها از یکدیگر
using System;
interface IParentInterface
{
void ParentInterfaceMethod();
}
interface IMyInterface : IParentInterface
{
void MethodToImplement();
}
class InterfaceImplementer : IMyInterface
{
static void Main()
{
InterfaceImplementer iImp = new InterfaceImplementer();
iImp.MethodToImplement();
iImp.ParentInterfaceMethod();
}
public void MethodToImplement()
{
Console.WriteLine("MethodToImplement() called.");
}
public void ParentInterfaceMethod()
{
Console.WriteLine("ParentInterfaceMethod() called.");
}
}
مثال 2-13 دارای 2 واسط است : یکی IMyInterface و واسطی که از آن ارث میبرد یعنی IParentInterface. هنگامیکه واسطی از واسط دیگری ارثبری میکند، کلاس یا ساختاری که این واسطها را پیادهسازی میکند، باید تمامی اعضای واسطهای موجود در سلسله مراتب ارثبری را پیادهسازی نماید. در مثال 2-13، چون کلاس InterfaceImplementer از واسط IMyInterface ارثبری نموده، پس از واسط IParentInterface نیز ارثبری دارد، از اینرو باید کلیه اعضای این دو واسط را پیادهسازی نماید.
1- با استفاده از کلمه کلید interface در حقیقت یک نوع مرجعی (Reference Type) جدید ایجاد نمودهاید.
2- از لحاظ نوع ارتباطی که واسطها و کلاسها در ارثبری ایجاد مینمایند باید به این نکته اشاره کرد که، ارثبری از کلاس رابطه "است" یا "بودن" (is-a relation) را ایجاد میکند (ماشین یک وسیله نقلیه است) ولی ارثبری از یک واسط یا interface نوع خاصی از رابطه، تحت عنوان "پیادهسازی" (implement relation) را ایجاد میکند. ("میتوان ماشین را با وام بلند مدت خرید" که در این جمله ماشین میتواند خریداری شدن بوسیله وام را پیادهسازی کند.)
3- فرم کلی اعلان interface ها بشکل زیر است :
[attributes] [access-modifier] interface interface-name [:base-list]{interface-body}
که در اعضای آن بشرح زیر می باشند :
attributes : صفتهای واسط
access-modifiers : private یا public سطح دسترسی به واسط از قبیل
interface-name : نام واسط
:base-list : لیست واسطهایی که این واسط آنها را بسط میدهد.
Interface-body : بدنه واسط که در آن اعضای آن مشخص میشوند
توجه نمایید که نمیتوان یک واسط را بصورت virtual اعلان نمود.
4- هدف از ایجاد یک interface تعیین توانائیهاییست که میخواهیم در یک کلاس وجود داشته باشند.
5- به مثالی در زمینه استفاده از واسطها توجه کنید :
فرض کنید میخواهید واسطی ایجاد نمایید که متدها و property های لازم برای کلاسی را که میخواهد قابلیت خواندن و نوشتن از/به یک پایگاه داده یا هر فایلی را داشته باشد، توصیف نماید. برای این منظور میتوانید از واسط IStorable استفاده نمایید.
در این واسط دو متد Read() و Write() وجود دارند که در بدنه واسط تعریف میشوند ک
interface IStorable
{
void Read( );
void Write(object);
}
حال میخواهید کلاسی با عنوان Document ایجاد نمایید که این کلاس باید قابلیت خواندن و نوشتن از/به پایگاه داده را داشته باشد، پس میتوانید کلاس را از روی واسط IStorable پیادهسازی کنید.
public class Document : IStorable
{
public void Read( ) {...}
public void Write(object obj) {...}
// ...
}
حال بعنوان طراح برنامه، شما وظیفه داری تا به پیادهسازی این واسط بپردازید، بطوریکه کلیه نیازهای شما را برآورده نماید. نمونهای از این پیادهسازی در مثال 3-13 آورده شده است.
مثال 3-13 : پیادهسازی واسط و ارثبری – مثال کاربردی
using System;
// interface اعلان
interface IStorable
{
void Read( );
void Write(object obj);
int Status { get; set; }
}
public class Document : IStorable
{
public Document(string s)
{
Console.WriteLine("Creating document with: {0}", s);
}
public void Read( )
{
Console.WriteLine("Implementing the Read Method for IStorable");
}
public void Write(object o)
{
Console.WriteLine("Implementing the Write Method for IStorable");
}
public int Status
{
get
{
return status;
}
set
{
status = value;
}
}
private int status = 0;
}
public class Tester
{
static void Main( )
{
Document doc = new Document("Test Document");
doc.Status = -1;
doc.Read( );
Console.WriteLine("Document Status: {0}", doc.Status);
IStorable isDoc = (IStorable) doc;
isDoc.Status = 0;
isDoc.Read( );
Console.WriteLine("IStorable Status: {0}", isDoc.Status);
}
}
خروجی برنامه نیز بشکل زیر است :
Output:
Creating document with: Test Document
Implementing the Read Method for IStorable
Document Status: -1
Implementing the Read Method for IStorable
IStorable Status: 0
6- در مثال فوق توجه نمایید که برای متدها واسط IStorable هیچ سطح دسترسی (public,private و ...) در نظر گرفته نشده است. در حقیقت تعیین سطح دسترسی باعث ایجاد خطا میشود چراکه هدف اصلی از ایجاد یک واسط ایجاد شیء است که تمامی اعضای آن برای تمامی کلاسها قابل دسترسی باشند.
7- توجه نمایید که از روی یک واسط نمیتوان نمونهای جدید ایجاد کرد بلکه باید کلاسی از آن ارثبری نماید.
8- کلاسی که از واسط ارثبری میکند باید تمامی متدهای آنرا دقیقا همان گونه که در واسط مشخص شده پیادهسازی نماید. به بیان کلی، کلاسی که از یک واسط ارث میبرد، فرم و ساختار کلی خود را از واسط میگیرد و نحوه رفتار و پیادهسازی آنرا خود انجام میدهد.
خلاصه :
در این درس با مفاهیم کلی و اصلی درباره واسطها آشنا شدید. هم اکنون میدانید که واسطها چه هستند و سودمندی استفاده از آنها چیست. همچنین نحوه پیادهسازی واسط و ارثبری از آنرا آموختید.
مبحث واسطها بسیار گسترده و مهم است و امید است در بخشهای آینده در سایت، بتوانم تمامی مطالب را بطور حرفهای و کامل در اختیار شما قرار دهم
درس سوم – دستورالعملهای کنترلی و شرطی
در این درس با دستورالعملهای کنترل و انتخاب در C# آشنا میشوید. هدف این درس عبارتست از :
بررسی دستور if و انواع مختلف آن
در درسهای گذشته، برنامههایی که مشاهده میکردید از چندین خط دستور تشکیل شده بودند که یکی پس از دیگری اجرا میشدند و سپس برنامه خاتمه مییافت. در این برنامهها هیچ عمل تصمیمگیری صورت نمیگرفت و تنها دستورات برنامه به ترتیب اجرا میشدند. مطالب این درس نحوه تصمیمگیری در یک برنامه را به شما نشان میدهد.
اولین دستور تصمیمگیری که ما آنرا بررسی مینماییم، دستورالعمل if است. این دستور دارای سه فرم کلی : تصمیمگیری ساده، تصمیمگیری دوگانه، تصمیمگیری چندگانه میباشد.
مثال 1-3 – فرمهای دستورالعمل if
using System;
class IfSelect
{
public static void Main()
{
string myInput;
int myInt;
Console.Write("Please enter a number: ");
myInput = Console.ReadLine();
myInt = Int32.Parse(myInput);
//تصمیمگیری ساده و اجرای عمل داخل دو کروشه
if (myInt > 0)
{
Console.WriteLine("Your number {0} is greater than zero.", myInt);
}
//تصمیمگیری ساده و اجرای عمل بدون استفاده از دو کروشه
if (myInt < 0)
Console.WriteLine("Your number {0} is less than zero.", myInt);
// تصمیمگیری دوگانه
if (myInt != 0)
{
Console.WriteLine("Your number {0} is not equal to zero.", myInt);
}
else
{
Console.WriteLine("Your number {0} is equal to zero.", myInt);
}
// تصمیمگیری چندگانه
if (myInt < 0 || myInt == 0)
{
Console.WriteLine("Your number {0} is less than or equal to zero.", myInt);
}
else if (myInt > 0 && myInt <= 10)
{
Console.WriteLine("Your number {0} is between 1 and 10.", myInt);
}
else if (myInt > 10 && myInt <= 20)
{
Console.WriteLine("Your number {0} is between 11 and 20.", myInt);
}
else if (myInt > 20 && myInt <= 30)
{
Console.WriteLine("Your number {0} is between 21 and 30.", myInt);
}
else
{
Console.WriteLine("Your number {0} is greater than 30.", myInt);
}
} //Main()پایان متد
} //IfSelectپایان کلاس
برنامه 1-3 از یک متغیر myInt برای دریافت ورودی از کاربر استفاده مینماید، سپس با استفاده از یک سری دستورات کنترلی، که همان دستور if در اینجاست، عملیات خاصی را بسته به نوع ورودی انجام میدهد. در ابتدای این برنامه عبارت Please enter a umber: در خروجی چاپ میشود. دستور Console.ReadLine() منتظر میماند تا کاربر ورودی وارد کرده و سپس کلید Enter را فشار دهد. همانطور که در قبل نیز اشاره کردهایم، دستور Console.ReadLine() عبارت ورودی را به فرم رشته دریافت مینماید پس مقدار ورودی کاربر در اینجا که یک عدد است به فرم رشتهای در متغیر myInput که از نوع رشتهای تعریف شده است قرار میگیرد. اما میدانیم که برای اجرای محاسبات و یا تصمیمگیری بر روی اعداد نمیتوان از آنها در فرم رشتهای استفاده کرد و باید آنها را بصورت عددی مورد استفاده قرار داد. به همین منظور باید متغیر myInput را به نحوی به مقدار عددی تبدیل نماییم. برای این منظور از عبارت Int32.Parse() استفاده مینماییم. این دستور مقدار رشتهای متغیر داخل پرانتزش را به مقدار عددی تبدیل کرده و آنرا به متغیر دیگری از نوع عددی تخصیص میدهد. در این مثال نیز همانطور که دیده میشود، myInput که تز نوع رشتهای است در داخل پرانتز قرار گرفته و این مقدار برابر با myInt که از نوع int است قرار گرفته است. با این کار مقدار عددی رشته ورودی کاربر به متغیر myInt تخصیص داده میشود. (توضیح کاملتری در مورد Int32 و سایر تبدیلات مشابه به آن در درسهای آینده و در قسمت نوعهای پیشرفته مورد بررسی قرار میگیرند.)حال ما متغیری از نوع مورد نظر در دست داریم و میتوانیم با استفاده از دستور if بر روی آن پردازش انجام داده و تصمیمگیری نماییم.
اولین دستور بصورت if (boolean expression) {statements} آورده شده است. دستور if با استفاده از کلمه کلیدی if آغاز میشود. سپس یک عبارت منطقی درون یک زوج پرانتز قرار میگیرد . پس از بررسی این عبارات منطقی دستورالعمل/دستورالعملهای داخل کروشه اجرا میشوند. همانطور که مشاهده مینمایید، دستور if یک عبارت منطقی را بررسی میکند. در صورتیکه مقدار این عبارات true باشد دستورهای داخل بلوک خود را اجرا مینماید(قبلا توضیح داده شد که دستورهایی که داخل یک زوج کروشه {} قرار میگیرند در اصطلاح یک بلوک نامیده میشوند.) و در صورتیکه مقدار آن برابر با false باشد اجرای برنامه به بعد از بلوک if منتقل میشود. در این مثال همانطور که ملاحظه مینمایید، عبارت منطقی دستور if بشکل if(myInt > 0) است. در صورتیکه مقدار myInt بزرگتر از عدد صفر باشد، دستور داخل بلوک if اجرا میشود و در غیر اینصورت اجرای برنامه به بعد از بلوک if منتقل میگردد.
دومین دستور if دراین برنامه بسیار شبیه به دستور اول است، با این تفاوت که در این دستور، دستور اجرایی if درون یک بلوک قرار نگرفته است. در صورتیکه بخواهیم با استفاده از دستور if تنها یک دستورالعمل اجرا شود، نیازی به استفاده از بلوک برای آن دستورالعمل نمیباشد. استفاده از بلوک تنها زمانی ضروری است که بخواهیم از چندین دستور استفاده نماییم.
در بیشتر موارد از تصمیمگیریهای دوگانه یا چندگانه استفاده میشود. در این نوع تصمیمگیریها، دو یا چند شرط مختلف بررسی میشوند و در صورت true بودن یکی از آنها عمل مربوط به آن اجرا میگردد. سومین دستور if در این برنامه نشان دهنده یک تصمیمگیری دوگانه است. در این حالت درصورتیکه عبارت منطقی دستور if برابر با true باشد دستور بعد از if اجرا میشود و در غیر اینصورت دستور بعد از else به اجرا در میآید. در حقیقت در این حالت میگوئیم " اگر شرط if صحیح است دستورات مربوط به if را انجام بده و درغیر اینصورت دستورات else را اجرا کن".
فرم کلی دستور if-else بصورت زیر است :
if (boolean expression)
{statements}
else
{statements}
که در آن boolean expression عبارت منطقی است که صحت آن مورد بررسی قرار میگیرد و statements دستور یا دستوراتی است که اجرا میگردند.
دستور if-else if … else یا if تودرتو
در صورتیکه نیاز باشد تا چندین حالت منطقی مورد بررسی قرار گیرد و دستورات مربوط به یکی از آنها اجرا شود، از فرم تصمیمگیری چندگانه استفاده مینماییم. این نوع استفاده از دستور if در اصطلاح به if تودرتو (Nested If) معروف است چراکه در آن از چندین دستور if مرتبط به یکدیگر استفاده شده است. چهارمین دستور if در مثال 1-3 استفاده از if تودرتو را نشان میدهد. در این حالت نیز دستور با کلمه کلیدی if آغاز میگردد. شرطی بررسی شده و در صورت true بودن دستورات مربوط به آن اجرا میگردد. اما اگر مقدار این عبارت منطقی false بود آنگاه شرطهای فرعی دیگری بررسی میشوند.این شرطهای فرعی با استفاده از else if مورد بررسی قرار میگیرند. هر یک از این شرطها دارای عبارات منطقی مربوط به خود هستند که در صورت true بودن عبارت منطقی دستورات مربوط به آنها اجرا میگردد و در غیر اینصورت شرط بعدی مورد بررسی قرار میگیرد. باید توجه کنید که در ساختار if تودرتو تنها یکی از حالتها اتفاق میافتد و تنها یکی از شرطها مقدار true را بازمیگرداند.
فرم کلی if تودرتو بشکل زیر است :
if (boolean expression)
{statements}
else if (boolean expression)
{statements}
…
else
{statements}
نکته دیگری که باید در اینجا بدان اشاره کرد، نوع شرطی است که در عبارت منطقی دستور if آخر مورد استفاده قرار گرفته است. در این عبارت منطقی از عملگر || استفاده شده است که بیانگر OR منطقی است. عملگر OR زمانی مقدار true بازمیگرداند که حداقل یکی از عملوندهای آن دارای مقدار true باشد. بعنوان مثال در عبارت (myInt < 0 || myInt == 0)، در صورتیکه مقدار متغیر myInt کوچکتر یا مساوی با صفر باشد، مقدار عبارت برابر با true است. نکته قابل توجه آنست که در زبان C#، همانطور که در درس دوم به آن اشاره شد، دو نوع عملگر OR وجود دارد. یکی OR منطقی که با || نمایش داده میشود و دیگری OR معمولی که با | نشان داده میشود. تفاوت بین این دو نوع OR در آنست که OR معمولی هر دو عملگر خود را بررسی مینماید اما OR منطقی تنها در صورتیکه عملگر اول آن مقدار false داشته باشد به بررسی عملگر دوم خود میپردازد.
عبارت منطقی (myInt > 0 && myInt <= 10) حاوی عملگر AND شرطی (&&) میباشد. این عبارت در صورتی مقدار true بازمیگرداند که هر دو عملوند AND دارای مقدار true باشند. یعنی در صورتیکه myInt هم بزرگتر از صفر باشد و هم کوچگتر از 10، مقدار عبارت برابر با true میگردد. در مورد AND نیز همانند OR دو نوع عملگر وجود دارد. یکی AND معمولی (&) و دیگری AND شرطی (&&). تفاوت این دو نیز در آنست که AND معمولی (&) همیشه هر دو عملوند خود را بررسی مینماید ولی AND شرطی (&&) تنها هنگامی به بررسی عملوند دوم خود میپردازد که مقدار اولین عملوندش برابر با true باشد. عملگرهای منطقی (|| و &&) را در اصطلاح عملگرهای میانبر (short-circuit) مینامند چراکه تنها در صورت لزوم عملوند دوم خود را بررسی مینمایند و از اینرو سریعتر اجرا میشوند.
همانند دستور if، دستور switch نیز امکان تصمیمگیری را در یک برنامه فراهم مینماید.
مثال 2-3 – دستورالعمل switch
using System;
class SwitchSelect
{
public static void Main()
{
string myInput;
int myInt;
begin:
Console.Write("Please enter a number between 1 and 3: ");
myInput = Console.ReadLine();
myInt = Int32.Parse(myInput);
// بهمراه متغیری از نوع صحیح switch دستور
switch (myInt)
{
case 1:
Console.WriteLine("Your number is {0}.", myInt);
break;
case 2:
Console.WriteLine("Your number is {0}.", myInt);
break;
case 3:
Console.WriteLine("Your number is {0}.", myInt);
break;
default:
Console.WriteLine("Your number {0} is not between 1 and 3.", myInt);
break;
} //switchپایان بلوک
decide:
Console.Write("Type "continue" to go on or "quit" to stop: ");
myInput = Console.ReadLine();
// بهمراه متغیری از نوع رشتهای switch دستور
switch (myInput)
{
case "continue":
goto begin;
case "quit":
Console.WriteLine("Bye.");
break;
default:
Console.WriteLine("Your input {0} is incorrect.", myInput);
goto decide;
} //switchپایان بلوک
} //Main()پایان متد
} //SwitchSelectپایان کلاس
مثال 2-3 دو مورد استفاده از دستور switch را نشان میدهد. دستور switch بوسیله کلمه کلیدی switch آغاز شده و به دنبال آن عبارت دستور switch قرار میگیرد. عبارت دستور switch میتواند یکی از انواع زیر باشد : sbyte, byte, short, ushort, int, uint, long, ulong, char, string, enum .(نوع enum در مبحث جداگانهای مورد بررسی قرار خواهد گرفت.) در اولین دستور switch در مثال 2-3، عبارت دستور switch از نوع عددی صحیح (int) میباشد.
به دنبال دستور و عبارت switch، بلوک switch قرار میگیرد که در آن گزینههایی قرار دارند که جهت منطبق بودن با مقدار عبارت switch مورد بررسی قرار میگیرند. هر یک از این گزینهها با استفاده از کلمه کلیدی case مشخص میشوند. پس از کلمه کلیدی case خود گزینه قرار میگیرد و به دنبال آن ":" و سپس دستوری که باید اجرا شود. بعنوان مثال به اولین دستور switch در این برنامه توجه نمایید. در اینجا عبارت دستور switch از نوع int است. هدف از استفاده از دستور switch آنست که از بین گزینههای موجود در بلوک switch، گزینهای را که مقدارش با مقدار عبارت switch برابر است پیدا شده و عمل مرتبط با آن گزینه اجرا شود. در این مثال مقدار متغیر myInt بررسی میشود. سپس اگر این مقدار با یکی از مقادیر گزینههای داخل بلوک switch برابر بود، دستور یا عمل مربوط به آن گزینه اجرا میگردد. توجه نمایید که در این مثال منظور ما از گزینه همان عدد پس از case است و منظور از دستور عبارتی است که پس از ":" قرار گرفته است. بعنوان مثال، در دستور زیر :
case 1:
Console.WriteLine("Your number is {0}.", myInt);
عدد 1، گزینه مورد نظر ما و دستور Console.WriteLine(…)، عمل مورد نظر است. در صورتیکه مقدار myInt برابر با عدد 1 باشد آنگاه دستور مربوط به case 1 اجرا میشود که همان Console.WriteLine("Your number is {0}.", myInt); است. پس از منطبق شدن مقدار عبارت switch با یکی از case ها، بلوک switch باید خاتمه یابد که این عمل بوسیله استفاده از کلمه کلیدی break، اجرای برنامه را به اولین خط بعد از بلوک switch منتقل مینماید.
همانطور که در ساختار دستور switch مشاهده مینمایید، علاوه بر case و break، دستور دیگری نیز در داخل بلوک وجود دارد. این دستور یعنی default، برای زمانی مورد استفاده قرار میگیرد که هیچ یک از گزینههای بلوک switch با عبارت دستور switch منطبق نباشند. به عبارت دیگر درصورتیکه مقدار عبارت switch با هیچ یک از گزینههای case برابر نباشد، دستور مربوط به default اجرا میگردد. استفاده از این دستور در ساختار بلوک switch اختیاری است. همچنین قرار دادن دستور break پس از دستور default نیز اختیاری میباشد.
همانطور که قبلاً نیز گفته شد پس از هر دستور case، به منظور خاتمه دادن اجرای بلوک switch باید از یک break استفاده نمود. دو استثنا برای این موضوع وجود دارد. اول اینکه دو دستور case بدون وجود کد و دستورالعملی در بین آنها، پشت سر هم قرار گیرند و دیگری در زمانیکه از دستور goto استفاده شده باشد.
در صورتیکه دو دستور case بدون وجود کدی در بین آنها، پشت سر یکدیگر قرار گیرند، بدین معناست که برای هر دو case مورد نظر یک عمل خاص در نظر گرفته شده است. به مثال زیر توجه نمایید.
switch (myInt)
{
case 1:
case 2:
case 3:
Console.WriteLine("Your number is {0}.", myInt);
break;
default:
Console.WriteLine("Your number {0} is not between 1 and 3.", myInt);
break;
}
در این مثال، همانطور که مشاهده میکنید، سه دستور case بدون وجود کدی در بین آنها پشت سر یکدیگر قرار گرفتهاند. این عمل بدین معناست که برای تمامی گزینههای 1، 2 و 3 دستور ;(Console.WriteLine("Your number is {0}.", myInt اجرا خواهد شد. یعنی اگر مقدار myInt برابر با هر یک از مقادیر 1، 2 و 3 باشد، یک دستور برای آن اجرا میشود.
نکته قابل توجه دیگر در مورد بلوک switch آنست که، دستورات case حتماً نباید یک دستور باشد بلکه میتوان از یک بلوک دستور برای case استفاده نمود.
دومین استفاده از دستور switch در مثال 2-3، دارای عبارتی از نوع رشتهایست. در این بلوک switch چگونگی استفاده از دستور goto نیز نشان داده شده است. دستور goto اجرای برنامه را به برچسبی (label) که معین شده هدایت مینماید. در حین اجرای این برنامه، اگر کاربر رشته continue وارد نماید، این رشته با یکی از گزینههای دومین switch منطبق میشود. چون دستور case مربوط به این گزینه دارای دستور goto است، اجرای برنامه به برچسبی که این دستور مشخص کرده فرستاده میشود، بدین معنی که اجرای برنامه به ابتدای جایی میرود که عبارت begin: در آنجا قرار دارد (در اوایل متد Main()). بدین صورت اجرای برنامه از بلوک switch خارج شده و به ابتدای برنامه و در جائیکه برچسب begin: قرار گرفته ارسال میشود. در این برنامه، استفاده از چنین حالتی استفاده از goto باعث ایجاد یک حلقه شده است که با وارد کردن عبارت quit اجرای آن به پایان میرسد.
در صورتیکه هیچ یک از عبارات continue و یا quit وارد نشوند، اجرای switch به گزینه default میرود و در این گزینه ابتدا پیغام خطایی بر کنسول چاپ شده و سپس با استفاده از دستور goto پرشی به برچسب decide صورت میگیرد. پس از پرش به برچسب decide، از کاربر پرسیده میشود که آیا میخواهد اجرای برنامه را ادامه دهد یا خیر.( با وارد کردن گزینههای continue یا quit) همانطور که میبینید در اینجا نیز حلقهای تولید شده است.
استفاده از دستور goto در بلوک switch میتواند موثر باشد اما باید توجه نمایید که استفادههای بی مورد از دستور goto باعث ناخوانا شدن برنامه شده و عیبیابی (Debug) برنامه را بسیار دشوار مینماید. در برنامهنویسیهای امروزی استفاده از دستور goto بغیر از موارد بسیار لازم و ضروری منسوخ شده و به هیچ عنوان توصیه نمیشود. برای تولید و ساخت حلقه نیز دستورات مفید و سودمندی در زبان تعبیه شدهاند که استفاده از goto را به حداقل میرسانند. دستورات حلقه در مبحث آینده مورد بررسی قرار خواهند گرفت.
نکته پایانی این مبحث آنست که توجه نمایید که به جای استفاده از دستور switch میتوانید از چندین دستور if-else استفاده نماید. دو قطعه برنامه زیر معادل یکدیگر میباشند.
switch(myChar)
{
case 'A' :
Console.WriteLine("Add operation ");
break;
case 'M' :
Console.WriteLine("Multiple operation ");
break;
case 'S' :
Console.WriteLine("Subtraction operation ");
break;
default :
Console.WriteLine("Error, Unknown operation ");
break;
}
معادل بلوک switch با استفاده از if-else
if (myChar == 'A')
Console.WriteLine("Add operation ");
else if (myChar == 'M')
Console.WriteLine("Multiple operation ");
else if (myChar == 'S')
Console.WriteLine("Subtraction operation ");
else
Console.WriteLine("Error, Unknown operation ");
همانطور که ملاحظه میکنید استفاده از بلوک دستور switch بسیار سادهتر از استفاده از if-else های تودرتو است.
در این درس با نحوه تصمیمگیری در برنامه بوسیله دستور if و switch آشنا شدید. با نحوه عملکرد و استفاده دستور goto نیز آشنایی پیدا کردید. در پایان مجدداً یادآوری میکنم که در استفاده از دستور goto با احتیاط عمل نمایید و به جز در موارد ضروری از آن استفاده نکنید