در قسمت قبلی زنگ سیشارپ در حل تمرین شمارهی ۱۴ تا آنجا پیش رفتیم که توانستیم یک هنرمند را ذخیره و همچنین توانستیم لیست هنرمندهای ذخیره شده را مشاهده کنیم. در این قسمت به ادامهی حل تمرین شماره ۱۴ میپردازیم.
هنگامی که یک Artist را ذخیره میکنید علاوهبر ذخیره کردن آن، یکسری عملیات دیگر را باید روی آن انجام دهید. در این برنامه عملیات Edit، Delete، Add Album، Add Single Tune، View Single Tunes و View Albums برای هر خواننده مد نظر ما است. بدین معنی که پس از افزودن یک خواننده به جعبهی موسیقی، بتوانیم به آن، آلبوم و تکآهنگ اضافه کنیم، آثار آن هنرمند را ویرایش کنیم، ببینیم، بشنویم و یا اینکه آن هنرمند را بهکلی حذف کنیم.
اگر قصد داشته باشید هنرمندی که ذخیره کردید را حذف کنید، کافی است آن خانه از آرایه که هنرمند مربوطه در آن ذخیره شده است را برابر با null قرار دهید. برای این منظور ما یک متد به نام ()RemoveArtist به کلاس MusicBox اضافه میکنیم:
public void RemoveArtist(int index) { Artists[index] = null; }
کلاس MusicBox آرایهای از Artist دارد. در این متد ما index آرایهای که هنرمند مربوطه در آن ذخیره شده است را مشخص کرده و سپس آن را برابر با null قرار میدهیم.
توجه کنید هنگامیکه هنرمند ذخیره شد، ما باید یک سری عملیات را روی آن انجام دهیم، پس اگر ۴ هنرمند ذخیره کرده باشید باید از بین این ۴ نفر، هنرمند مربوطه انتخاب شود تا عملیات لازم را روی آن انجام دهیم. برای اینکار باید یک جستجو در آرایهی Artists انجام دهیم.
در مثال زیر نمونهای از جستجو کردن در یک آرایه از جنس int را مشاهده میکنید:
using System; class SearchDemo { static void Main() { bool found = false; int[] myArray = { 2, 6, 4, 12, 20 }; int input = Convert.ToInt32(GetInput("Enter a number: ")); for (int i = 0; i < myArray.Length; i++) { if (input.Equals(myArray[i])) { found = true; Console.WriteLine("This number is exist."); break; } } if (!found) Console.WriteLine("This number doesn't exist!"); } static string GetInput(string message) { Console.Write(message); return Console.ReadLine(); } }
همانطور که میبینید یک عدد از کاربر گرفته شده و توسط یک حلقهی for، موجود بودن عدد در آرایه بررسی شده است. در این میان میبینید که بهجای استفاده از == از متد ()Equals به منظور مقایسه استفاده کردیم. این متد یک مقدار بولین را return میکند. در آرایه Artists جستجو را به همین منوال انجام میدهیم با این تفاوت که آرایه از جنس int نیست.
یکی دیگر از کارهایی که باید انجام دهیم افزودن آلبوم برای هر هنرمند است. هر آلبوم یک سری مشخصات و یک سری آهنگ دارد بنابراین برای ساخت یک آلبوم بایستی این اطلاعات را برای آلبوم فراهم کنیم. مجدداً به کلاس Album نگاهی بیندازید:
class Album { // Fields private string AlbumName; private string AlbumOwner; private string AlbumGenre; private ushort AlbumYear; private Tune[] Tunes; // Constructor public Album(string albumName, Artist artist, string albumGenre, ushort albumYear, Tune[] tunes) { AlbumName = albumName; AlbumOwner = artist.GetArtistNameAndFamily(); AlbumGenre = albumGenre; AlbumYear = albumYear; Tunes = tunes; } }
به constructor این کلاس توجه کنید، میبینید که باید نام آلبوم، اسم خواننده، سبک آلبوم، سال و آرایهای از آهنگها را هنگام ساخت شیء از این کلاس تعریف کنیم بنابراین تمام این اطلاعات را از کاربر دریافت کرده، آرایهای از Tune بهوجود میآوریم و یک شیء از کلاس Album را تولید میکنیم. هنگامیکه این شیء بهوجود آمد، توسط متد زیر آن را در آرایهی Albums کلاس Artist ذخیره خواهیم کرد:
public bool AddAlbum(Album album) { if (Counter < Albums.Length) { Albums[Counter] = album; Counter++; return true; } return false; }
این متد یک شیء از جنس Album دریافت و آن را در آرایه Albums ذخیره میکند. متغیر Counter را در constructor کلاس Artist برابر با صفر قرار میدهیم و بعد افزودن هر آلبوم، یک واحد به این متغیر میافزاییم. دلیل قرار دادن دستور if در این متد این است که هنگام افزودن آلبوم جدید از index آرایهی Albums خارج نشویم چراکه در غیر اینصورت برنامه با خطا مواجه میشود. این متد هنگامیکه با موفقیت آلبوم را به آرایه افزود مقدار true و در صورت عدم موفقیت مقدار false را return میکند.
برای پاک کردن یک آلبوم از متد زیر استفاده میکنیم:
public bool RemoveAlbum(int index) { if (Albums[index] != null) { Albums[index] = null; return true; } return false; }
این متد نیز قبل از پاک کردن آلبوم ابتدا بررسی میکند که index مشخص شده null نباشد، سپس آن را برابر با null قرار میدهد.
نکتهی قابل توجه دیگر در این برنامه، متد ()ToLower است. این متد یک پارامتر string میگیرد و تمام حروف آن را تبدیل به حروف کوچک (lowercase) و سپس این string جدید را return میکند. متد ()ToUpper بر خلاف ()ToLower عمل میکنید. به نمونهی زیر توجه کنید:
using System; class ToUpperToLowerDemo { static void Main() { string s1 = "FOR this OnE We'll use TOLOWER() METHOD."; string s2 = "for this one we'll use ToUpper() method."; Console.WriteLine(s1.ToLower()); Console.WriteLine(s2.ToUpper()); } }
همانطور که ذکر شد ما از متد ()Equals بهجای == استفاده کردیم. مطمئناً سوالی که برایتان بهوجود میآید این است که اینها چه تفاوتی با هم دارند؟ البته در این تمرین استفاده از هردو نتیجهی یکسانی را در بر دارد اما دانستن تفاوت این دو خالی از لطف نیست. هنگامیکه یک شیء میسازید، شیء شما شامل دو بخش است. یک بخش شامل محتوای شیء (content) و بخش دیگر شامل آدرسی (reference) است که به محتوا اشاره دارد.
برای مثال اگر شما بهصورت زیر یک شیء بسازید:
object o = ".NET Framework";
در اینجا “.NET Framework” محتوا (content) است و o به محتوا اشاره دارد(reference) .
تفاوت در اینجاست که == Reference ها را باهم مقایسه میکند، در صورتیکه متد ()Equals محتوا (Content) را مورد مقایسه قرار میدهد. در مورد نوع object در سیشارپ همینقدر بدانید که همهی کلاسها از object مشتق میشوند. در مورد ارثبری بعداً مفصل صحبت خواهیم کرد.
بنابراین اگر شما کد زیر را اجرا کنید، == و متد ()Equals هردو مقدار true را نمایش میدهند به این دلیل که Content و Reference ها یکی هستند:
object o = ".NET Framework"; object o1 = o; Console.WriteLine(o == o1); Console.WriteLine(o.Equals(o1));
خروجی:
True
True
اکنون به کد زیر توجه کنید، در کد زیر میبینید که ما دو content یکسان داریم اما برای هرکدارم reference جداگانهای در نظر گرفته شده است. بنابراین اگر کد زیر را اجرا کنید == مقدار False و متد ()Equals مقدار True را return میکند:
object o = ".NET Framework"; object o1 = new string(".NET Framework".ToCharArray()); Console.WriteLine(o == o1); Console.WriteLine(o.Equals(o1));
خروجی:
False
True
در خط دوم این برنامه یک شیء string جدید توسط new و متد ()ToCharArray ساخته شده است که محتوای آن با قبلی یکسان است بنابراین دو شیء و دو reference جداگانه داریم.
هنگامیکه از string data type استفاده میکنید، content ها با هم مقایسه میشوند. به عبارت دیگر، چه از == استفاده کنید و چه از ()Equals، مقایسه همیشه روی content صورت میگیرد.
public void ViewAlbums() { for (int i = 0; i < Albums.Length; i++) { if (Albums[i] == null) continue; Console.WriteLine(Albums[i].GetAlbumName()); } }
به ادامه حل تمرین برمیگردیم. برای نمایش آلبومهای ذخیره شده از متد زیر (که در آلبوم Artist قرار دارد) استفاده میکنیم:
هنگامیکه برنامه شما کلاسهای زیادی دارد دیگر نباید کلاسها را پشت سر هم ردیف و از آنها استفاده کنید بلکه باید هر کلاس در فایل جداگانهای قرار داشته باشد. برای ساختن فایل جدا برای هر کلاس به ترتیب زیر عمل میکنید.
ابتدا در برنامه Visual Studio روی نام پروژه Right Click کرده سپس گزینهی Add، بعد از آن New Item را انتخاب کنید:
سپس از قسمت Visual C# Items گزینهی Class را انتخاب میکنید (یک نام دلخواه نیز برای آن در نظر بگیرید):
هنگامیکه کلاستان را اضافه کردید، کدهای زیر را درون آن میبینید:
در عکس بالا میبینید که یکسری using بهطور پیشفرض در کلاسی که ساختهاید وجود دارد. میتوانید همه را بهجز using System پاک کنید یا اینکه اجازه بدهید در برنامه باشند. اینها به شما اجازه میدهند به کلاسها و متدهای مختلف و گستردهتری از .NET Framework دسترسی داشته باشید. در اینجا ما تنها به using System نیاز داریم. نکتهی قابل توجه در اینجا namespace است. در واقع، هر برنامه سیشارپ از namespace استفاده میکند و ما تا اینجا نیازی به توضیح namespace نداشتیم زیرا سیشارپ بهطور اتوماتیک، یک namespace پیشفرض، یک global namespace برای برنامه شما تعریف میکند بنابراین برنامههایی که تاکنون مینوشتیم از global namespace استفاده میکردند اما در برنامههای واقعی و کاربردی، برنامهنویسان namespace خودشان را تعریف میکنند و همچنین با namespace های دیگری نیز در تعامل هستند. یک namespace در واقع ناحیهای را مشخص میکند که یک مجموعهی اسم بتوانند از یک موجموعهی اسم دیگر، جدا در نظر گرفته شوند. به عبارت دیگر، یک نام در یک namespace با نام یکسانی دیگر در namespace دیگری تداخل پیدا نمیکند. یکی از namespace های استفاده شده در کتابخانهی .NET Framework (که به آن کتابخانهی سیشارپ (C# Library) هم گفته میشود) System است. به این دلیل است که ما از عبارت:
در بالای هر برنامه استفاده میکنیم. namespace های زیادی وجود دارند که وابسته به System هستند و بخشهای دیگر کتابخانهی سیشارپ نگهداری میکنند. namespace ها از اهمیت بالایی برخوردار هستند زیرا نامهای بسیار زیادی در برنامه میتوانند وجود داشته باشند. نام متغیرها، کلاسها و… که در کتابخانهی سیشارپ هستند، نامهایی که در فایلهای dll هستند و وارد برنامهتان میکنید (در مقالات آینده با dll آشنا خواهید شد) و همچنین اسمهایی که در برنامه خودتان تعریف میکنید، همه در صورت عدم وجود namespace با هم تداخل پیدا میکنند. برای مثال اگر در برنامهتان یک کلاس به اسم Accelerate تعریف کنید و در کتابخانههایی که استفاده میکنید کلاسی با این نام وجود داشته باشد، بین این دو تداخل و ناسازگاری بهوجود خواهد آمد. با استفاده از namespace دیگر چنین مشکلاتی بهوجود نخواهد آمد.
پروژه حل تمرین شماره ۱۴ را میتوانید از اینجا دانلود کنید. پروژهای که اکنون دانلود میکنید شامل مواردی است که تاکنون در مورد این تمرین روی آنها بحث کردیم. این پروژه را بهصورت کامل در قسمتهای بعدی (بعد از اینکه روی تمام قسمتهای حل آن بحث شد) میتوانید دانلود کنید.
احسان
4 May 2013
از زحماتتون واقعا مچكرم. بسيار عالي يه !
رضا
4 May 2013
سلام جناب آقای مهندس درویشیان ببخشیید من یه مشکلی داشتم توی حل تمرین شماره ی ۱۰ که گفتید یک مستطیل بکشم که سایزشو دریافت کنه به مشکل برخوردم هرچند اینکه توضیح دادید توی مقاله ولی بازم به مشکل برخوردم میشه دوباره از اول بیشتر توضیح بدهید باتشکر
مسعود درویشیان
7 May 2013
سلام به کد زیر با دقت نگاه کن:
با متد ()GetInput که فکر نمیکنم مشکلی داشته باشی، با این متد ما ورودیها (طول و عرض) رو از کاربر گرفتیم و ریختیم توی دوتا متغیر به اسم width و height. مهم فهمیدن حلقههای تو در تو هست. الان ما دوتا حلقه داریم. دقت کن، حلقهی اول (حلقهی بیرونی) شمارندهی سطر و حلقهی دومی (حلقهی داخلی) شمارنده ستون هست. خوب ممکنه بپرسی چرا اولی سطره و دومی ستون! ببین، وقتی برنامه به حلقهی اول میرسه، شرط حلقه بیرونی رو چک میکنه و میبینه که آره شرط برقراره، پس وارد حلقهی اول میشه…
خب حالا حلقهی اول رو رد کردیم رسیدیم به حلقهی دوم (هنوز هیچی توی خروجی چاپ نشده) حالا برنامه رسیده به حلقهی دوم، خب میاد شرطش رو چک میکنه میبینه که آره شرط حلقه داخلی برقراره، پس الان وارد حلقهی دوم میشه…
و یه تعداد ستاره رو چاپ میکنه…
وقتی مثلاً 5 تا ستاره رو چاپ کرد، برنامه از حلقهی داخلی خارج میشه و میره سر خط…
اما برنامه هنوز توی حلقهی اولی هست! پس دوباره شرط حلقهی اول رو چک میکنه و اگه شرطش برقرار بود دوباره وارد حلقهی دوم میشه و به تعداد width ستاره چاپ میکنه…
به این ترتیب، حلقهی اولی (بیرونی) شمارنده سطر (یا همون height) و حلقهی دومی (داخلی) شمارنده ستون (یا همون width) هست.
میلاد
4 May 2013
با تشکر
خیلی مرسی ،استفاده بردیم
عبدالغفور صبوری
6 May 2013
خیلی متشکرم. موضوعاتی رو که تو این سایت ارایه شده من تونستم ازش خوب استفاده کنم. متشکرم.
اگه میشه زنک سی شارپ رو افزایش دهید مطالب بیشتر اضافه کنید.
مسعود درویشیان
7 May 2013
خواهش میکنم. بله حتماً، زنگ سیشارپ حالا حالاها مهمونتونه :)
داوود
7 May 2013
سلام مسعود جان؛
وقتت بخیر؛
آقا مسعود اگه ما بخوایم به یک متد استاتیک از یک کلاس معمولی دسترسی داشته باشیم؛ حتما باید از اون کلاس یک شی ء بسازیم؟ اگه بخوایم بدون ساختن یک شی ء به متد استاتیک اون دسترسی داشته باشیم باید چه کاری انجام بدیم؟ آیا باید کلاس رو هم استاتیک در نظر بگیریم؟
مسعود درویشیان
7 May 2013
داوود
7 May 2013
دستت درد نکنه عزیزم؛ خیلی خیلی ممنون از پاسخت. چرا من هرجوری می فرستم بازهم مثل شما کدهام اینجوری نمیشن؟
همه روشها رو هم امتحان کردم.
[C#/]….[#C]
یا
[C#]….[/#C]
ولی بازهم درست نمیشن!!!!!
مسعود درویشیان
7 May 2013
خواهش میکنم :)
باید کدهاتو بین اینا قرار بدی:
داوود
7 May 2013
مرسی عزیزم؛ کاش زودتر می پرسیدم این سوال رو ؛ از 6 ماهه که پستهای زیبات شروع شده مدام این سوال تو ذهنم بود که چرا هر جور می فرستم درست نمیشه!!! خوب شد پرسیدم؛ فکر کنم یکی از صدها مشغله ذهنی م حل شد.
همچنین از اینکه اینقدر سریع و تند (که طی روزهای اخیر در نوع خودش بی سابقه ست) تشکر ویژه دارم. :)
مسعود درویشیان
7 May 2013
خواهش میکنم. لطف داری
داوود
8 May 2013
آقا مسعود این تکه کد مربوط به Update کردن یک عنصر از عناصر داخل آرایه هستش؛ در خصوص سوالی که در قسمتهای قبلی و در خصوص دفترچه تلفن بود؛ آیا درست نوشتم و خوبه:همینجوری هستش؟
[“code lang=”csharp]
public void UpDate(string text)
{
Console.WriteLine(text);
int id = Convert.ToInt32(Console.ReadLine());
Console.WriteLine(“Please Enter New Name that you want Replaced.”);
string newname = Console.ReadLine();
for (int j = 0; j < Names.Length; j++)
{
if (id == j)
{
Names[j] = newname;
Console.WriteLine("Element {0} has been Updated successfully", j);
}
}
}
ابتدا ID نام شخصی رو که میخوایم تغییرش بدیم میگیریم و بعدش نام جدید رو وارد می کنیم. در انتها با استفاده از متد show که در پستهای قبلی اونو گذاشتم ؛ تغییرات رو نشون میدیم.
آیا خوبه همینجوری یا نه؟؟ و اینکه منظور شما همینجوری بود؟
مسعود درویشیان
8 May 2013
سلام. من یه برنامه کوچیک نوشتم برات که توش کاملاً متوجه آپدیت کردن بشی
هرجاشو نفهمیدی بگو تا بیشتر توضیح بدم
داوود
9 May 2013
ممنون عزیزم؛ باشه الان trace می کنم و اگه دیدم متوجه نشدم بهت میگم.
حوصله نداشتی برنامه خودم رو ببینی؟ میخوام از نظر منطق ببینم درسته یا نه؟؟ یه نگاه بکن دیگه! جان ما. :)
مسعود درویشیان
9 May 2013
داوود جان همون اول برنامهات رو دیدم و بهنظرم اومد که نیازه یه برنامه برات بنویسم تا صحیحتر اینکار رو انجام بدی
داوود
9 May 2013
دستت درد نکنه عزیزم؛ ببخشید پس زود قضاوت کردم استاد!!!
ممنونم مسعود جان.
داوود
11 May 2013
سلام مسعود جان؛
بامداد اولین روز هفته ت بخیر و خوشی انشاءالله.
مسعود جان در قسمت بیست و پنجم یه برنامه ای بود که میانگین رو بر حسب تعداد اعداد مختلف حساب می کرد و شما میخواستی params رو توضیح بدی! من یه برنامه مشابه نوشتم که مقادیر رو از کاربر میگیره ، یعنی همون ابتدا باید مشخص کنی که چندتا عدد میخوای بدی و بعدش اعداد رو میگیره و در انتها میانگین رو حساب می کنه.
با مطالبی که شما آموزش دادی آیا درست نوشتم؟
داوود
11 May 2013
مسعود جان سلام؛ ببخشید من فکر کنم حواسم نبوده و گرنه میخواستم در این برنامه حتما از params استفاده کنم ؛ اگه نه دلیلی نداشت که بحث params رو پیش بکشم ولی ازش استفاده ای نکنم! اصلاحش می کنم و با Params می فرستمش.
داوود
11 May 2013
سلام آقا مسعود؛ وقتتون بخیر؛ در قسمت بیست و پنجم و در ابتدای درس یک مثال آوردید.
یکی از تکه کدها اینه:
که جهت مقایسه بکار میره.
حالا وقتی که میخواستین این متد رو صدا بزنین اینجوری صدا زدین:
من اینجا رو که در واقع به کلاس بالا ارتباط داره، خوب نفهمیدم؛ البته منظور در مقدار دهی و در کلاس بالا هستن.
چرا در اینجا علیرغم اینکه متغیرها به کلاسها اختصاص یافتن و فکر کنم مقدارشون صفر شده ولی چرا دارن تو مقایسه شرکت داده میشن و مقدار دارن؟ منظورم Name , Surname و َAge هستن! آیا نمیخواست در اینجا از ob1 استفاده کنیم؟ یعنی ob1.name؟
مسعود درویشیان
13 May 2013
سلام. اگه دقت کنی توی این مثالی که تو قسمت ۲۵ بود، کلاس MyClass سه تا فیلد Name و Surname و Age داشت. همینطور یه constructor هم داشت که مجبورت میکرد وقتی داری شیء میسازی، فیلدها رو هم مقداردهی کنی. یکی از متدهای این کلاس هم که ()SameAs بود. یعنی شما هر شیءای از این کلاس بسازی به این متد هم دسترسی داری. خوب این متد یه شیء از جنس MyClass رو بهعنوان پارامتر میگیره. حالا ما اولین کاری که کردیم یه شیء ob1 از MyClass ساختیم که Name = Damon و Surname = Salvatore و Age = 22 هست. یه شیء ob2 هم ساختیم که Name = Stefan و Surname = Salvatore و Age = 21 هست. خب حالا میخوام از طریق متد ()SameAs فیلدهای این دوتا شیء رو با هم مقایسه کنم. داخل متد ()SameAs منظور از Name، فیلد Name همون شیءای هست که از طریق اون شیء متد ()SameAs رو صدا زدیم. شاید میگفتم this.Name بهتر متوجه میشدی. مثلاً اگه با شیء ob1 متد ()SameAs رو صدا بزنی، Name = Damon هست.
حالا اون شیءای که به متد میدی هم همینطوره. ما اومدیم شیء ob2 رو بهعنوان argument دادیم به متد و از اون طرف بهعنوان پارامتر ob گرفتیمش. پس وقتی میگیم ob.Name منظورمون Stefan هست چون توی شیء ob2 فیلد Name برابر Stefan بود.
اگه متوجه نشدی بگو تا بیشتر توضیح بدم.
پ.ن: ممنون که سوالات و مشکلاتت رو میپرسی
داوود
21 May 2013
سلام مسعود جان؛
دستت درد نکنه عزیزم؛ واقعا توضیحاتت خوب و قابل درک بود.
شرمنده از اینکه بعلت مسافرت نتونستم زودتر پاسخ محبتت رو بدم.
موفق باشید.
mohammad sadegh
16 May 2013
کجای کارم اشتباه هست که خطای منطقی میده
محمد
14 June 2013
سلام
با تشکر فراوان از اموزش های خوبتون
اقای درویشیان در اینده کار با بانک های اطلاعاتی در سی شارپ رو اموش می دید؟
(ado.net)
چون منبع فارسی خوبی در این زمینه نداریم.خواهشا جز برنامتون باشه.
محمد
27 June 2013
متشکرم
فرهاد
22 July 2015
چرا متغیر Counter را در constructor کلاس Artist برابر با صفر قرار میدهیم؟ چرا در constructor این کارو انجام میدیم
چرا متد AddAlbum را در کلاس Artist تعریف میکنیم مگه مربوط به آلبوم نمیشه در کل برای تحلیل کلاسبندی احتیاج به توضیح دارم ممنون میشم راهنمایی کنید