به نام خدا
سلام و درود ان شاءالله حالتون خوب باشه :-)
کانال یوتویوب Interview Happy یه سری سوال داره که برای مرور مطالب ذهنیمون می تونه مفید باشه
دوره هم داره که اینجا می تونید ببینیدش
حالا در مورد چیه؟ همانطور که از عنوان هم مشخصه سوال های (پایه ای) مصاحبه شی گرایی و سی شارپ که میگن تو مصاحبه ها می پرسن اما هدف من اینجا بیشتر مرور اطلاعاتیه که ممکنه توی پس توی ذهنمون خاک بخوره
مفاهیم اصلی شی گرایی را در تصویر زیر داریم
در این سوال ما فقط شی و کلاس رو توضیح می دهیم بقیه موارد در سوال های بعدی پوشش داده خواهد شد
کلاس : یک کلاس یک واحد منطقی (Logical Unit) یا طرح اولیه (BluePrint) که شامل فیلد ها (fields) رفتار ها (methods) و شاخص ها(properties) می باشد.
سازنده (Constructor): متدی در کلاس می باشد که زمانی که نمونه ای از کلاس ساخته می شود اجرا می شود. و مقدار دهی اولیه درآن انجام می گیرد
فیلد(Field): یک فیلد متغیری از هر نوع داده ای می باشد.
مشخصه (Property) : عضوی است که در خواندن و نوشتن فیلد خصوصی به ما کمک میکند
متد(Method): متد یک بلوک کد است که شامل یک سری عبارات است.
شی (Object) : شی یک نمونه از یک کلاس می باشد
در زیر یک نمونه از کلاس کارمند ایجاد می کنیم
وراثت ایجاد رابطه ی والد-فرزندی بین دو کلاس است که کلاس فرزند به طور خودکار ویژگی های و رفتار های کلاس والد را به ارث می برد
به عنوان مثال در کد زیر Employee یک کلاس والد می باشد و PermanentEmployee کلاس فرزند می باشد. حال وقتی که ما یک شی از PermanentEmployee ایجاد می کنیم، به طور خودکار متد CalculateSalary را دریافت می کند، حتی اگر این متد CalculateSalary در کلاس PermanentEmployee وجود نداشته باشد اما به طور خودکار آن را از کلاس والد خود یعنی Employee دریافت می کند.
وراثت برای قابلیت استفاده مجدد (Reusability)و انتزاع(Abstraction) کد، مناسب می باشد.
به عنوان مثال، اگر فردا نوع جدیدی از Employee را بخواهید ایجاد کنید و آن EmployeeContract باشد، می توانید از همان کلاس پایه یعنی Employee ارث بری کنید واز رفتارها و ویژگی های کلاس والد آن، استفاده کنید.
اگر از ارث بری استفاده نکنید باید کلاس Employee را حذف سپس در کلاس های PermanentEmployee و ContractEmployee هربار رفتار ها و ویژگی ها را باید دوباره نویسی کنیم.
در زیر انواع ارث بری که در سی شارپ وجود دارد را بررسی خواهیم کرد:
ارث بری یکتا (Single inheritance): یک کلاس فرزند فقط از یک کلاس والد ارث بری میکند
ارث بری چندگانه (Multiple Inheritance) : در ارث بری چندگانه یک کلاس فرزند از چند کلاس والد ارث بری می کند.
در سی شارپ ارث بری چندگانه به کمک رابط ها (Interface) قابل دستیابی است. یعنی کلاس فرزند فقط می تواند از یک کلاس والد ارث بری کند و بقیه باید رابط باشند.
ارث بری چند سطحی (Multilevel Inheritance): در این مورد یک کلاس پدربزرگ (Grandparent Class)وجود دارد که کلاس پدر(Parent Class) از آن ارث بری کند و کلاس فرزند(Child Class) از کلاس پدر ارث بری می کند.
کلاس پدربزرگ(مادربزرگ) -> مادر(پدر) -> فرزند
در اینجا کلاس فرزند رفتارها و ویژگی های کلاس والد های خود را به صورت خودکار در اختیار دارد.
ارث بری سلسله مراتبی (Hierarchal inheritance) : در این نوع ارث، بری کلاس فرزند مشتق شده از کلاس والد بیش از یک کلاس می باشد.
به یاد داشته باشید کلاس های Base/Parent/Super در کلاس های والد و Child/Derived/Subclass
در کلاس های فرزند یکی هستند
با استفاده از کلمه ی کلیدی Sealed قبل از کلمه کلیدی class
در تصویر زیر میبینید کلاس ABC که با کلمه کلیدی sealed علامت گذاری شده است. اگر بخواهید از کلاس XYZ را از کلاس ABC مشتق کنید با خطا مواجه خواهید شد
انتزاع به معنی نشان دادن چیز های مورد نیاز و مخفی کرد جزئیات
به عنوان مثال یک راننده ماشین فقط چیزی های کمی مانند گاز، دنده و استارت را می بیند و اینکه موتور ماشین چطوری کار میکند از او مخفی شده است اما باز هم می تواند ماشین را براند.
در کد زیر یک شی از کلاس Employee ایجاد کرده ایم و سپس متد CalculateSalary را صدا میزنیم. حال اگر به کلاس Employee برویم و متد CalculateSalary رو ببینیم متوجه می شویم که در این متد متد های CalculateHra و CalculateBasicSalary استفاده شده است. این متدها وظیفه خود را انجام می دهند اما برای استفاده کننده قابل مشاهده نیستند. بنابراین این ها متد های مخفی یا متد های پشت صحنه هستند. این به معنی انتزاع هست یعنی مخفی کردن جزئیات در پشت صحنه است.
اگر تا به حال از یک مجموعه LIST از DotnetFramework استفاده کرده اید، می دانید که چگونه آیتم ها را به آن اضافه کنید و چگونه آن را پیمایش کنید، اما آیا می دانید که List چگونه به صورت داخلی از IEnumerable و IEnumerator و سایر رابط ها استفاده می کند؟ این همان مفهوم انتزاع می باشد که پشت صحنه را مخفی کرده است.
کپسوله سازی به معنی بسته بندی داده ها و متدها/پراپرتی ها درون یک واحد است
چرا properties را در اینجا قرار دادم؟ زیرا properties چیزی جز method های خاص نیستند.
چرا آنها متدهای خاص هستند؟ زیرا آنها فقط یک کار را انجام می دهند و آن get و set فیلدهای خصوصی است.
به عنوان مثال، یک کلاس Employee داریم که دارای یک فیلد empExperience است که تجربه کارمند است. به یاد داشته باشید که در سی شارپ وقتی می گوییم فیلد فقط داده ها را نشان می دهد. این فیلد از خارج کلاس قابل دسترسی نیست زیرا یک فیلد خصوصی است.
ما باید یک پراپرتی عمومی EmpExperience ایجاد کنیم که این فیلد را نگهدارد و سپس فقط از خارج از این کلاس قابل دسترسی است. بنابراین، دوباره به تعریف نگاه کنید، کپسوله سازی عبارت است از بسته بندی داده هایی که در اینجا فیلد است و تابعی که در اینجا ویژگی است در یک واحد. بدون این بسته بندی، این داده یا فیلد نباید خارج از این کلاس در دسترس باشد. بنابراین این کپسوله سازی است.اکنون می توانید در Main Method را ببینید، ما به ویژگی EmpExperience دسترسی داریم نه فیلد، بنابراین این کپسوله سازی است.
به طور خلاصه، فیلد یا داده فقط در خارج از کلاس فقط از طریق property قابل دسترس می باشد.
چرا کپسوله سازی اجباری است ؟
چون به طور مستقیم با فیلد یا داده ی شما در ارتباط نیست و این داده شما را امن تر می کند. با پراپرتی شما می توانید منطق بیشتری برای مقدار دهی و دریافت مقدار آن فیلد اضافه کنید مثلا encryption/decryption یک نوع پوشش می باشد.
نقض کپسوله سازی
برای مثال، در این کد، کلاس Employee هیچ property ندارد و در متد Main فیلد بدون property قابل دسترسی است.این روش کار خواهد کرد اما مفهوم کپسوله سازی OOPS را نقض می کند، که خوب نیست زیرا داده ها و فیلدهای شما مستقیماً در معرض دید قرار می گیرند و برای امنیت خوب نیست.
چند ریختی توانایی یک متغیر یا آبجکت یا تابع در گرفتن چندین شکل به خود می باشد.
منظور از چند شکل چیست؟
به عنوان مثال در انگلیسی کلمه Running می تواند برای Running a race یعنی دویدن در یک مسابقه یا Running a business یعنی اداره ی یک کسب و کار استفاده شود. در هر دو مورد این کلمه معانی متفاوت دارد اما نام یکسان دارد.
این همان چند شکلی می باشد جایی که نام یک چیز یکسان است اما می تواند برای اهداف متفاوتی استفاده شود. در سی شارپ می توان به کمک پلی مورفیسم به اشکال مختلف دست یافت.
در زیر می توانید دو تابع با نام یکسان "ADD" را مشاهده کنید که دارای اشکال مختلف هستند، به این معنی که هر دو تابع با نام یکسان کارهای مختلفی انجام می دهند، یکی در حال جمع 2 عدد صحیح و دیگری اضافه کردن 2 رشته را انجام میدهد.
این یکی از انواع پلی مورفیسم است و نام این نوع overloading است.
در حال حاضر دو نوع پلی مورفیسم وجود دارد. 1: زمان کامپایل (compile time) و زمان اجرا(run time).
سربارگذاری نوعی چندشکلی زمان کامپایل و دوباره نویسی نوعی چندشکلی زمان اجرا است.
زمان کامپایل به این معنی است که وقتی پروژه خود build می کنید، دات نت شروع به تبدیل کد سی شارپ شما به زبان ماشین و ایجاد یک dll برای آن می کند. این مدت زمان ساخت زبان ماشین، زمان کامپایل(compile time) است.
حالا وقتی build کامل شد و پروژه خود را اجرا می کنید تا خروجی را ببینید، آن مدت زمان اجرا(run time) است.
سربارگذاری (overloading) نوعی چند شکلی است که در آن میتوانیم چند متد با یک نام در یک کلاس بسازیم و همه متدها به روشهای متفاوتی کار میکنند.
سربارگذاری یک چندشکلی زمان کامپایل است. چرا به آن چند شکلی زمان کامپایل می گویند؟
زیرا کامپایلر دات نت (CLR) در زمان کامپایل یا ساخت کد سی شارپ به زبان ماشین می داند که متدهای با نام های یکسان متفاوتند و هر کدام کار متفاوتی انجام می دهد.
سربارگذاری را می توانیم به سه روش زیر انجام دهیم :
1:سربارگذاری (Overloading) یکی چندشکلی هاست که در آن میتوانیم چندین متد به یک نام در یک کلاس ایجاد کنیم و همه متدها به روشهای متفاوتی کار کنند. دوباره نویسی(Overriding) یکی دیگر از چندشکلی هاست که متد دارای نام و امضا یکسان می باشد اما در کلاس های متفاوت است.
2: برای استفاده از سربارگذاری نیازی نیست از کلمه کلیدی استفاده کنیم اما در دوباره نویسی در کلاس والد از کلمه کلیدی virtual استفاده می کند و در کلاس فرزند نیز باید کلمه کلیدی override نوشته شود
3: سربارگذاری نیازی به ارث بری ندارد چرا که در یک کلاس استفاده می شود اما دوباره نویسی نیاز به ارث بری دارد چرا که در یک کلاس امکان استفاده وجود ندارد.
در overriding ما متد های هم نام و هم امضا داریم که در کلاس های مختلف می باشند
اما در متد hiding ما می توانیم متد های پیاده سازی شده در کلاس والد(BaseClass) را با کمک کلمه کلیدی new از کلاس مشتق شده (SubClass) مخفی کنیم
مزایای OOPS:
1. استفاده مجدد(Reuse) از کد از طریق وراثت
2. انعطاف پذیری(Flexibility) از طریق چندشکلی
3. امنیت داده ها(Security) از طریق پنهان کردن داده ها / کپسوله سازی.
4. ساده سازی ارتقا / مقیاس (Simple to upgrade/ scale)از برنامه های کاربردی کوچکتر به بزرگتر.
5. ماژولار(Modularity) بودن برای عیب یابی آسان تر.
محدودیت های OOPS:
برای کاربردهای کوچک مناسب نیست.
1 : در کلاس های ابسترکت هم می توان متد را اعلان (declaration)کرد و هم می توان تعریف(definition) کرد.
public abstract class Employee { public abstract void Project(); // اعلان متد public void Role() //پیاده سازی یا تعریف متد { Console.WriteLine("Software Engineer"); } }
اما در رابط ها فقط می توان متد را اعلان(declaration) کرد.
public interface IEmployee { public void Role(); // فقط اجازه اعلان متد را داریم public void Project(); }
2 : کلمه کلیدی ابسترکت کلاس abstract و کلمه کلیدی رابط interface میباشد.
3 : کلاس های ابسترکت شامل فیلد ها و پراپرتی، ثابت ها سازنده میباشد.اما رابط ها فقط شامل متد تعریف نشده می باشد.
4: کلاس انتزاعی از ارث بری چندگانه پشتیبانی نمی کنند اما رابط ها از ارث بری چندگانه پیروی می کنند.
5: کلاس انتزاعی می تواند سازنده داشته باشد اما اینترفیس چنین مکانی ندارد
6: کلاس انتزاعی می تواند عضو استاتیک داشته باشد اما رابط نمی تواند
7: عضو های کلاس انتزاعی می توانند سطح دسترسی های متفاوتی داشته باشند اما رابط فقط public می باشد.
8: کلاس انتزاعی می تواند به طور کامل یا قسمتی از آن یا هیچ کدام از آنها پیاده سازی نشود اما رابط همه اعضای آن باید پیاده سازی شود
9: کلاس انتزاعی برای پیاده سازی هویت اصلی کلاس استفاده می شود اما رابط برای پیاده سازی توانایی های جانبی کلاس استفاده می شود.
ابسترکت کلاس زمانی خوب است که تعدادی متد(concrete/defined) داشته باشیم که باید به طور یکسان در مشتقات آن به یک روش پیاده سازی شود.
به عنوان مثال، در اینجا می توانید ببینید که ما یک کلاس انتزاعی Employee داریم. بنابراین هر زمان که یک کارمند استخدام می شود، ممکن است پروژه ای داشته باشد که قرار است با آن کار کند، و این پروژه مشخص نیست. شاید هم چندین پروژه در شرکت وجود داشته باشد.
اما نقش کارمند که بر اساس سمت و نقش کارمند که در اینجا مهندس نرم افزار است که به نوعی ثابت است مشخص می شود. بنابراین، این متد Role متد اصلی و از قبل تعریف شده است.
در چنین سناریویی ما abstract class را ترجیح میدهیم که برخی از متدها تعریف شده (definition)و برخی فقط اعلام شده(declaration) باشند.
public abstract class Employee { public abstract void Project(); public void Role() { Console.WriteLine("Software Engineer"); } }
وقتی میدانید یک متد باید وجود داشته باشد، یک interface انتخاب خوبی است، اما میتوان آن را بهصورت متفاوت، توسط کلاسهای مستقل، این رابط را پیاده سازی کرد.
اینجا را ببینید که ما رابط IEmployee را داریم که در آن دو متد Project و Manager داریم و هر دو فقط اعلام شده اند.
public interface IEmployee { public void Manager(); // فقط اجازه اعلان متد را داریم public void Project(); }
زیرا این متدها می توانند برای پروژه های مختلف متفاوت باشند. در یک پروژه نام پروژه و نام مدیر متفاوت است و برای پروژه دیگر می تواند دوباره متفاوت باشد. بنابراین این جزئیات ثابت یا مشخص نیستند، بنابراین ما فقط این متدها را اعلان کردیم اما تعریف نکرده ایم. این استفاده از رابط ها است.
معمولاً ما Interface را ترجیح می دهیم زیرا استفاده ازآن، انعطاف پذیری بیشتری برای اصلاح رفتار در آینده به ما می دهد.
دو دلیل وجود دارد که از رابط ها استفاده می کنیم :
1 : برای اینکه به ارث بری چندگانه دست پیدا کنیم چرا که کلاس ها از ارث بری چندگانه پشتیبانی نمی کنند
2: برای حفظ سازگاری در بین کلاس های مشتق شده
فرض کنید یک شرکت داریم ک که در آن تعدادی کارمند وجود دارد. ما می خواهیم یک رابط برای کارمندان این شرکت بنویسیم.
چرا رابط می نویسیم؟ زیرا هر کارمند باید درآمد و پروژه ای داشته باشند که بر روی آن کار کند.
public interface IEmployee { public void SetSalary(); public void SetProject(); }
وبا ارث بردن این رابط ما مطمئن میشویم که همه متد ها باید در کلاس های مشتق شده پیاده سازی شوند. از آن جا که هر متد رابط باید در کلاس مشتق شده پیاده سازی شود. در حال حاضر ما فقط دو متد داریم اما ممکن است متد های بیشتری داشته باشیم. حال، اگر یک کلاس PermanentEmployee ایجاد کنیم، با به ارث بردن این رابط، مطمئن می شویم که استانداردهای شرکت(تعیین حقوق و تنظیم پروژه) را دنبال می کنیم و هر دو کار را انجام می دهیم.
public class Employee : IEmployee { public void SetProject() { Console.WriteLine("City Bank Project"); } public void SetSalary() { Console.WriteLine("2000000"); } }
مشابه این، اگر بخواهیم نوع دیگری از کلاس Employee به نام کلاس ContractEmployee ایجاد کنیم، با به ارث بردن این رابط، استاندارد شرکت را دنبال می کنیم.
به عبارتی، در سرتاسر برنامه ما یکپارچگی کلاس های کارمند را حفظ می کنیم و بدین صورت برنامه یا سیستم را می توان آسانتر مدیریت کرد.
خیر، زیرا نمی توان از رابط ها نمونه ساخت، بلکه فقط به ارث برده می شوند
خیر، رابط و کلاس انتزاعی فقط در ارث برده شدن استفاده می شوند و برای ساخت شی نیستند
مشخص کننده های دسترسی، کلمات کلیدی هستند که دسترسی به کلاس، متد، فیلد و پراپرتی را مشخص میکنند. این کلمات کلیدی عبارتند از Public ,Protected ,Internal ,Private ,ProtectedInternal می باشند. سطح دسترسی پیش فرض کلاس internal می باشد.
در جدول زیر مشخص می شود که چه کلمه کلیدی چه سطحی از دسترسی را مشخص می کند.
باکسینگ : به فرآیند تبدیل یک value type به refrence type را boxing می گویند.
آنباکسینگ : به فرآیند تبدیل یک refrence type به refrence type را Unboxing می گویند.
public class Program { static void Main() { // آنباکسینگ یک تبدیل صریح می باشد int num = 100; object obj = num; //Boxing int i = (int)obj; //Unboxing } }
در سی شارپ string تغییر ناپذیر(immutable) می باشد یعنی اگر متغیری از نوع string را تعریف و مقداری به تخصیص دادید نمی توانید مقدار آن را تغییر دهید. هر بار که مقدار جدیدی به آن تخصیص می دهید یک متغیر جدیدی از نوع string به وجود می آید.
string str1 = "Hope means " str1 = str1 + "Iran" /* * هر دو این رشته ها متفاوت هستند و در * مکان های متفاوتی از حافظه ذخیره می شود */ Console.WriteLine(str1);
در سی شارپ StringBuilder قابل تغییر(mutable) می باشد یعنی هر بار که داده ی آن دستکاری می شود آدرس جدیدی به آن تخصیص داده می شود نمونه جدیدی در حافظه ایجاد نمی شود.
var str = new StringBuilder(); str.Append("Hope means "); str.Append("iran"); /* * هر دو این رشته ها در یک مکان از حافظه ذخیره می شوند */ Console.WriteLine(str);
اگر رشته شما چندین بار تغییر می کند استفاده از StringBuilder انتخاب مناسب تری نسبت به string می باشد زیرا با هر تغییر، نمونه جدیدی در حافظه ایجاد نمی شود از این منظر کارایی بهتری دارد.
اتصال Concat : دو رشته را میتوان با تابع System.String.Concat ویا با استفاده از عملگر + متصل کرد.
string str1 = "this is one" string str2 = "this is two" string str3 = str1 + str2; string str4 = str1.Concat(str2).ToString(); //output: this is one this is two
جایگذاری Replace : با استفاده از تابع Replace می توان رشته ای را درون یک رشته پیدا و رشته جدیدی به جای قرار داد.
string str1 = "This is one" string str2 = str1.Replace("one","two"); //output: This is two
کوتاه کردن Trim : با استفاده از تابع trim می توان در یک رشته فضای خالی ها را حذف کرد.
string str1 = "This is one " str1.Trim(); //output: "This is one"
شامل بودن contains: اگر بخواهیم عبارتی را درون رشته جستجو کنیم از contains استفاده می کنیم اگر وجود داشت true برمیگرداند در غیر این صورت false برمیگرداند.
string str1 = "This is test" if(str1.Contains("test")) { Console.WriteLine("The 'test' was found."); } //output: The 'test' was found.
نوع های nullable برای نگهداری مقادربر نال استفاده می شود زیرا به طور معمول تایپ ها(به جز رشته) نمی نمی توانند مقدار نال را در خود نگه نمی دارند.
//invalid declaration int number1 = null; //valid declaration Nullable<int> number2 = null; //valid declaration int? number3 = null;
جنریک ها به ما اجازه می دهند کلاس ها و یا متدها بسازیم که اصطلاحا مستقل از نوع یا type safe است
در کد زیر، اگر می خواهید متد AreEqual را مستقل از، نوع داده ورودی کنید. به این معنی که بتواند هر عدد صحیح و هر رشته ها را با هم مقایسه کند، پس باید از جنریک ها استفاده کنیم.
class Program { static void Main() { bool equal = Calculator.IsEqual(4, 4); // will work bool strEqual = Calculator.IsEqual("c#", "cpp"); //will not work } } public class Calculator { public static bool IsEqual(int val1,int val2) { return val1.Equals(val2); } }
کد زیر را ببینید ما متد را به یک متد جنریک تبدیل کرده ایم و اکنون می تواند اعداد صحیح و همچنین رشته ها را مقایسه کند.
class Program { static void Main() { bool equal = Calculator.IsEqual<int>(4, 4); // will work bool strEqual = Calculator.IsEqual<string>("c#", "cpp"); //will not work } } public class Calculator { public static bool IsEqual<T>(T val1, T val2) { return val1.Equals(val2); } }
در برنامه نویسی شی گرا از Exception handling برای مدیریت خطا ها استفاده می شود.
static void Main() { try { int i = 0; int j = 0; int k = i / j; } catch (Exception ex) { Console.WriteLine("Catch called: " + ex.Message); throw; } finally { Console.WriteLine("Finally called"); Console.ReadLine(); } }
تلاش کردن(TRY): یک بلاک از کد است که در آن هر خطایی ممکن است رخ دهد.
گرفتن(CATCH): وقتی هر خطایی در بلاک TRY رخ می دهد، برای رسیدگی به آن به بلوک catch منتقل می شود.
سرانجام(FINALLY): بلاک نهایی برای اجرای مجموعه ای از دستورات استفاده می شود، خواه یک استثنا پرتاب شود یا پرتاب نشود. در هر صورت این باک اجرا می شود.
پرتاب(THROW): در داخل بلاک catch می توانید از کلمه کلیدی throw برای انتقال stack trace به یک سطح به بالا استفاده کنید.
خیر، ما می توانیم چندین بلاک catch را بنویسیم اما فقط یکی از آنها اجرا خواهد شد.
try { int i = 0; int j = 0; int k = i / j; } catch (ArithmeticException ex) { } catch (Exception ex) { Console.WriteLine("Catch called: " + ex.Message); throw; }
چرا به چند بلوک catch نیاز داریم؟
دلیل آن این است که ممکن است بخواهیم خطاهای مختلف را به روش های مختلف مدیریت کنیم. به عنوان مثال، فرض کنید نمی خواهیم همه خطاها را نشان دهیم یا ثبت کنیم اگر هر استثنای محسباتی رخ دهد، می توانیم بلوک catch را خالی نگه داریم.
بلاک finally صرف نظر از هر خطایی اجرا خواهد شد.
به عنوان مثال فرض کنید می خواهید یک ارتباط mssqlserver بزنید و چند عملایت دیتابیسی در برنامه خود انجام دهید ارتباط خود را باز کرده و در حین عملیات به هر دلیلی خطایی رخ می دهد در این حالت ارتباط با دیتابیس هنوز بسته نشده است واین کارایی برنامه ما را پایین می آورد.در این حالت می توانیم از بلاک finally استفاده کنیم و ارتباط به دیتابیس را در اینجا ببندیم.بلاک finally چه خطایی رخ دهد چه رخ ندهد اجرا خواهد شد در نتیجه با این کار مطمئن میشویم ارتباط با دیتابیس بسته خواهد شد.
static void Main() { var con = new SqlConnection("ConnectionString"); try { con.Open(); //some logic //error occurred } catch (Exception ex) { // error handled } finally { //connection closed con.Close(); } }
بله، اما حتما باید بلاک finally را داشته باشیم :
static void Main() { var con = new SqlConnection("ConnectionString"); try { con.Open(); //some logic //error occurred } finally { //connection closed con.Close(); } }
در سی شارپ throw ex اطلاعات پشته را تغییر می دهد(اطلاعات پشته ی همین نقطه و بعد از آن نقطه حذف می شود) اما throw اطلاعات پشته را تغییر نمی دهد و این خطا را به بالای پشته اضافه می کند.
در کد زیر از throw استفاده کرده ایم متد DivideByZero می آید 0 را بر 0 تقسیم می کند و خطا می دهد. این خطا را در بلاک catch میگیریم و سپس به متد Main پرت میکنیم و آنجا در داخل بلاک catch قرار میگیرد و در نهایت stack trace چاپ میشود. نشان می دهد که خطا در خط 27(روی سیستم خودم) است و درست است. بنابراین این خوب است زیرا throw کل stack trace را حفظ می کند.
public static void Main() { try { DivideByZero(); } catch (Exception ex) { Console.WriteLine(ex.StackTrace); //output //at Program.DivideByZero() in C:\Personal\Youtube\InterviewCsharp\OOP\Program.cs:line 31 //at Program.Main() in C:\Personal\Youtube\InterviewCsharp\OOP\Program.cs:line 9 Console.ReadKey(); } } public static void DivideByZero() { try { int i = 0; int j = 0; int k = i / j;//خط 27 } catch (Exception ex) { throw; } }
حالا بیایید مثال throw ex را ببینیم. در کد زیر ما throw را با throw ex جایگزین کرده ایم و بقیه کدها یکسان است. حالا وقتی آن را اجرا می کنیم، نشان می دهد که خطا در خط 28 رخ داده است که صحیح نیست، بنابراین می گوییم که throw ex پشته را تغییر می دهد و برای توسعه دهنده دشوار است که موقعیت صحیح خطا را در داخل متد پیدا کند.
public static void Main() { try { DivideByZero(); } catch (Exception ex) { Console.WriteLine(ex.StackTrace); //output //at Program.DivideByZero() in C:\Personal\Youtube\InterviewCsharp\OOP\Program.cs:line 31 //at Program.Main() in C:\Personal\Youtube\InterviewCsharp\OOP\Program.cs:line 9 Console.ReadKey(); } } public static void DivideByZero() { try { int i = 0; int j = 0; int k = i / j; } catch (Exception ex) { throw new Exception(); //خط 31 } }
حلقه while : مقداردهی اولیه، شرط و شمارنده در خطوط مختلف در حلقه while اتفاق می افتد.
int i = 0;//مقدار دهی اولیه while (i < 5) // شرط { Console.WriteLine(i); i++; // شمارنده }
حلقه Do-While : تنها تفاوت بین while و do while عبارت داخل do است، این عبارت صرف نظر از درست یا غلط بودن شرط while، باید یکبار اجرا شود.
public static void Main() { int i = 0;//مقدار دهی اولیه do { // این عبارت صرف نظر از شرط حلقه، یکبار اجرا می شود Console.WriteLine(i); i++; // شمارنده } while (i < 5); // شرط }
حلقه for : مقداردهی اولیه، شرط و شمارنده همگی همراه با کلمه کلیدی for، در یک خط نوشته می شوند.
for (int i = 0/*مقدار دهی اولیه*/; i < 5/*شرط*/; i++/*شمارنده*/) Console.WriteLine(i);
حلقه Foreach: بیشتر برای شمارش مجموعه هایی مانند array، ArrayList، List ,Hashtable و غیره استفاده می شود.
int[] arr = new int[] { 1, 2, 3, 4, 5, 6, 7 }; foreach (int item in arr) Console.WriteLine(item);
دستور Continue برای رد شدن از عبارات باقی مانده در داخل حلقه و انتقال کنترل به ابتدای حلقه استفاده می شود. دستور Break از حلقه خارج می شود. کنترل برنامه را برای خروج از حلقه انجام می دهد.
public static void Main() { for (int i = 0; i < 5; i++) { if (i == 3) continue; Console.Write(i); } //output : 0124 for (int i = 0; i < 5; i++) { if (i == 3) break; Console.Write(i); } //output : 012 }
1:آرایه Strongly Type است یعنی برای ذخیره داده در آن باید نوع داده یا نوع عنصر را مشخص کنیم اما آرایه ی لیستی هر نوع داده ای یا عنصری را میتواند ذخیره کند.
2:طول، اندازه یا سایز آرایه ثابت است اما آرایه لیستی می تواند هر تعداد از عناصر را در خود ذخیره کند.
public static void Main() { //array int[] array; array= new int[10]; // نوع آرایه باید هنگام تعریف مشخص شود array[0] = 1; array[1] = "iran"// آرایه استرانگلی تایپ است این داده در آن ذخیره نمی شود
//arrayList var arrayList = new ArrayList();// هر داده ای را میتواند در خود ذخیره کند arrayList.Add(1); arrayList.Add("iran"); }
در ArrayList ما فقط می توانیم مقادیر و عناصر را به لیست اضافه کنیم اما در HashTable می توانیم مقادیر و عناصر به همراه کلید وارد کنیم.
public static void Main() { //arraylist var arrayList = new ArrayList(); arrayList.Add(1); arrayList.Add("Iran"); //hashtable var hashTable = new Hashtable(); hashTable.Add("one", 1); hashTable.Add("country", "Iran"); }
مجموعه ها در سی شارپ برای ذخیره، مدیریت و دستکاری داده های مشابه با کارآمدی بیشتری طراحی شده است.
به عنوان مثال ArrayList, Dictionary, List, Hashtable , …
منظور از دستکاری داده ها، اضافه کردن، حذف کردن، جستجو کردن داده در مجموعه ها می باشد.
رابط IEnumerable زمانی استفاده می شود که بخواهیم با استفاده از یک حلقه foreach بین مجموعه ی از کلاس هایمان پیمایش کنیم.
به عنوان مثال، در کد زیر فهرستی از کارمندان وجود دارد، کارمندان جدید را به لیست اضافه می کنید. با یک حلقه foreach کارمندان را پیمایش میکنیم تا شناسه کارمند و نام ها را یکی یکی چاپ کنید. حالا این حلقه چگونه کار می کند؟ فقط توسط IEnumerable فعال می شود زیرا درون List از رابط IEnumerable استفاده می کند.
class Program { public static void Main() { var employes = new List<Employee>() { new Employee(){Id=1,Name="Mehrdad"}, new Employee(){Id=2,Name="Yasin"}, }; foreach (var employee in employes) Console.WriteLine(employee.Id + " " + employee.Name); Console.ReadKey(); } } public class Employee { public int Id { get; set; } public string Name { get; set; } }
اگر به تعریف List بروید، می بینید که چندین رابط را پیاده سازی کرده است و همه آن ها رابط IEnumerable را درون خود پیاده سازی کرده اند. و اگر به تعریف این رابط بروید کدی مانند زیر خواهید دید که در آن بیان می شود که از پیمایش مجموعه های غیر جنریک نیز پشتیبانی میکند.
namespace System.Collections { // // Summary: // Exposes an enumerator, which supports a simple iteration over a non-generic collection. public interface IEnumerable { // // Summary: // Returns an enumerator that iterates through a collection. // // Returns: // An System.Collections.IEnumerator object that can be used to iterate through // the collection. IEnumerator GetEnumerator(); } }
در سی شارپ List در داخل خود از IEnumerable استفاده می کند و IEnumerable درون خود از IEnumerator استفاده می کند پس می تواند گفت IEnumerator بخشی از IEnumerable است.
فرض کنید نمی خواهید از List دات نت فریمورک استفاده کنید و می خواهید Collection سفارشی خود را ایجاد کنید. پس باید به صراحت ابتدا رابط IEnumerable و سپس رابط IEnumerator را برای پیمایش در حلقه for پیاده سازی کنید.
نمودار زیر را ببینید. در بالای آن IList است سپس ICollection سپس IEnumerable و سپس IEnumerable از متد GetEnumerator در رابط IEnumerator استفاده می کند.
آIQueryable نیز مانند IEnumerable است و برای پیمایش داده ها در مجموعه کوئری های sql استفاده می شود و در فضای نام SYSTEM.LINQ قرار دارد.
نحوه کار کرد آن ها
اگر از IEnumerable استفاده می کنید، درخواست به پایگاه داده می رود و کل داده ها را از پایگاه داده می آورد و سپس نتیجه را در سمت سرور فیلتر می کند. بنابراین، آوردن کل داده ها از شبکه بر عملکرد اثر منفی می گذارد.
از سوی دیگر، IQueryable به پایگاه داده میرود، دادهها را در آنجا فیلتر میکند و فقط دادههای فیلتر شده را می آورد، نه کل دادهها را که این برای عملکرد خوب است و بار شبکه کمتر خواهد شد.
در تصویر زیر دلیل اینکه چرا IQueryable از IEnumerable عملکرد بهتری دارد را می بینید.
با استفاده از کلمات کلیدی ref و out میتوانیم آدرس پارامترها رو بفرستیم.
زمانی که بخواهید بیش از یک مقدار را از یک متد، خروجی بگیرید می توانید از کلمه کلیدی out و ref استفاده کنید. در واقع از out برای اختصاص یک مقدار جدید برای متغییری که مقدار ندارد استفاده خواهید کرد.در حالی که شما از ref برای تغییر یک مقدار استفاده خواهید کرد.
اگر پارامتر را با out بفرستید می توانید آن را مقدار دهی اولیه نکنید اما اگر بخواهید با ref پارامتر را ارسال کنید باید مقدار اولیه داشته باشد در غیر این صورت خطا خواهد داد.
درون متد پارامتری که با out فرستاده شده است قبل از اتمام کار و بازگشت خروجی باید مقدار دهی شده باشد در غیر این صورت با خطا روبرو خواهید شد.
public static void Main() { int a; int b = 5; int y = WithRefOut(out a, ref b); Console.WriteLine($"WitRefOut {a} {b}"); Console.ReadKey(); } static int WithRefOut(out int a, ref int b) { a = 100; return b = b * b; }
وقتی بخواهیم آرگومان هایی از یک نوع که تعداد مشخصی ندارد را بگیریم از params استفاده می کنیم.
public static void Main() { int y = Add(5,10,2,3,5); Console.WriteLine($"output: {y}"); Console.ReadKey(); } static int Add(params int[] listNumbers) { int total = 0; foreach (var i in listNumbers) total += i; return total; }
متد خاصی از کلاس است که زمانی که شی از کلاس ساخته می شود فراخوانی می شود.این متد خروجی ندارد و هم نام کلاس می باشد.
چهار نوع سازنده داریم :
سازنده پیشفرض : سازنده ای که هیچ پارامتری ندارد را سازنده پیش فرض می گویند.
class Program { public static void Main() { var defaultConstructor = new DefaultConstructor();
//output:سازنده پیش فرض صدا زده شد } } class DefaultConstructor { public DefaultConstructor() { Console.WriteLine("سازنده پیش فرض صدا زده شد"); } }
سازنده پارامتر دار: سازنده ای که حداقل یک پارامتر ورودی داشته باشد.
class Program { public static void Main() { var paraConstracutor= new ParaConstracutor(5); //output:ParaConstructor numbers: 5 } } class ParaConstracutor { public ParaConstracutor(int number) { Console.WriteLine("ParaConstructor numbers: " + number); } }
سازنده استاتیک: سازنده استاتیک هنگامی که عضو استاتیکی از کلاس صدا زده شود یا نمونه ای از کلاس ساخته شود فراخوانی می شود.فقط یکبار صدا زده می شود.
class Program { public static void Main() { StaticConstracutor.Print(); //output:Static Constracutor method called //Static Print method called } } class StaticConstracutor { static StaticConstracutor() { Console.WriteLine("Static Constracutor method called"); } public static void Print() { Console.WriteLine("Static Print method called"); } }
سازنده خصوصی: کلاس که سازنده آن خصوصی باشد نه میتوان از ارث بری کرد و نه نمونه ای از کلاس ساخت.
سازنده کپی : سازنده ای که یک شی را با کپی کردن متغیرهای شی دیگر می سازد.
class Program { public static void Main() { var obj = new CopyConstracutor("Iran"); var copyObject = new CopyConstracutor(obj); } } class CopyConstracutor { private string name; public CopyConstracutor(string name) { this.name = name; } public CopyConstracutor(CopyConstracutor copy) { this.name = name; } }
یک نمونه سازنده خاص است که برای کلاس های که فقط اعضای آن استاتیک است استفاده می شود.
از سازنده خصوصی برای کلاس الگوی سینگلتون نیز استفاده می کنند.
class Program { public static void Main() { Employee.GetManager(); //output: Happy } } class Employee { private Employee() { } public static void GetManager() { Console.WriteLine("Happy"); } }
اکستنشن متد ها به ما امکان می دهد تا بدون تغییر کد کلاس اصلی، متدهای جدیدی را به کلاس موجود اضافه کنیم.
به عنوان مثال، ما به این متد RightSubstring نیاز داریم برای کلاس String. در این متد مقدار 5 را پاس می کنیم و 5 کاراکتر آخر رشته را می گیریم.
حالا به این متد نیاز داریم اما در دات نت کلاس String چنین متدی ندارد و حتی نمیتوانیم این متد را در کلاس Net اضافه کنیم زیرا String یک کتابخانه کلاس پایه است که کد آن را نمیتوانیم تغییر دهیم.
بنابراین چگونه می توان این روش را اکنون اضافه کرد؟ اینجاست که اکستنشن به کمک ما می آید.
class Program { public static void Main() { string start = "In The Name Of God" string left = start.Substring(0,9); string right = start.RightSubstring(9); //output: In The Name Of God Console.WriteLine(left +""+ right); } } public static class StringExtentions { public static string RightSubstring(this String str, int count) { return str.Substring(str.Length - count, count); } }
چند نکته مهم، اکستنشن متد باید ثابت باشد زیرا مستقیماً از نام کلاس صدا زده می شود، نه با ایجاد شی و از آنجایی که متد ثابت است، کلاس StringExtesnions نیز ثابت است.
نکته دوم این است که از کلمه کلیدی this برای اتصال این متد با کلاس String استفاده می شود.
اگر اکستنشن کلاس دیگری را ایجاد میکنید، باید نام آن کلاس را همراه با کلمه کلیدی this وارد کنید.
به طور کلی زمانی که به کد کلاس اصلی دسترسی ندارید می توانید از این روش استفاده کنید.
یک متغیری است که آدرس یک متد را نگهداری میکند یا میتوان گفت در واقع اشاره گر به متد است.
می تواند به بیش از یک متد با پارامترها و خروجی یکسان اشاره کند.
در زیر ما دو متد استاتیک add و mul داریم. این متد ها نوع بازگشتی و تعداد پارامترهای یکسانی دارند. بنابراین، می توانید آنها را با کمک یک delegate گروه بندی کنید.
وقتی از آن استفاده می کنیم که نیاز داشته باشیم متدی را به عنوان پارامتر پاس بدهیم.
class Program { delegate void Calculator(int x, int y); public static void Main() { var calc = new Calculator(Add); calc(20, 30); //output: 50 } public static void Add(int a, int b) { Console.WriteLine(a + b); } public static void Mul(int a, int b) { Console.WriteLine(a * b); } }
وقتی Delegate آدرس بیش از یک متد را درون خود نگهداری کند
در زیر کد multicast delegate آمده است. 99٪ شبیه به مثال قبلی است.
به جز اضافه شدن خط calc += Mul یعنی پس از فراخوانی متد Add، دلیگیت بلافاصله این متد Mul را نیز فراخوانی می کند. همچنین آن را می توان آن را زنجیره Delegate ها نیز گفت. بنابراین درMulticast Delegate، چندین متد یکی یکی فراخوانی خواهند شد.
class Program { delegate void Calculator(int x, int y); public static void Main() { var calc = new Calculator(Add); calc += Mul; calc(20, 30); //output: 50 and 600 } public static void Add(int a, int b) { Console.WriteLine(a + b); } public static void Mul(int a, int b) { Console.WriteLine(a * b); } }
می توانید یک دلیگت ایجاد کنید، اما نیازی به اعلام متد مرتبط با آن نیست.
در کد زیر میبینید، متد Add یا متد دیگری وجود ندارد. اما منطق اضافه کردن به صورت خطی با استفاده از کلمه کلیدی delegate نوشته شده است.
delegate void Calculator(int x, int y); public static void Main() { Calculator calcAdd = delegate (int a, int b) { //Inline content of the method Console.WriteLine(a+b); }; calcAdd(20,30); //output: 50 }
دلیگیت متغیری است که ارجاع به متد یا اشاره گر به یک تابع را در خود دارد.
ایونت یک مکانیسم اطلاع رسانی است که به نمایندگان بستگی دارد.
یک ایونت به یک دلیگیت وابسته است و بدون دلیگیت ایجاد نمی شود.
ایونت مانند یک پوسته بر روی دلیگیت است تا امنیت آن را بهبود بخشد.
بنابراین Delegate به متدها و ایونت ها به Delegates بستگی دارد.
کلمه کلیدی this اشاره به نمونه حال حاضر ساخته شده از کلاس دارد.
به عنوان مثال در کد زیر، نحوه فرق بین پارامترهای سازنده - id، name و فیلدهای کلاس id و name. با کلمه کلیدی this فیلدهای کلاس را می توان ارجاع داد.
class Student { public int id; public string name; public Student(int id, string name) { //id = id; //name = name; this.id = id; this.name = name; } public void GetStudent() { Console.WriteLine(id+" : "+name); Console.ReadLine(); } }
کلمه کلیدی this از اشتباه گرفتن بین فیلدهای کلاس و پارامترهای سازنده جلوگیری می کند.
استفاده از کلمه کلیدی using در سی شارپ دو هدف دارد:
استفاده DIRECTIVE
به صورت using System.IO
دوم کلمه کلیدی بیان می کند شی حتی اگر استثنائی به وجود بیاد نابود می شود این برای ارتباط با دیتابیس ها مهم است.
public static void Main() { using (var connection = new SqlConnection("ConnectionString")) { var query = "UPDATE YourTable SET Property = Value" var command = new SqlCommand(query, connection); connection.Open(); command.ExecuteNonQuery(); //connection.Dispose(); } }
از عملگر is برای بررسی نوعی از یک شی استفاده می شود.
public static void Main() { int i = 5; bool check = i is int; Console.WriteLine(check); //output: true }
از عملگر as برای انجام تبدیل های نوع های ارجاعی که با هم سازگار هستند استفاده می شود.
public static void Main() { object country = "Iran" string str = country as string; Console.WriteLine(str); //output: Iran }
عملگر is خروجی از نوع boolean است اما عملگر as از نوع boolean نیست.
1 : فیلد های فقط خواندنی را می توان زمان اعلان مقدار دهی کرد اما ثابت ها را باید زمان اعلان مقدار دهی کرد.
2: کلمات کلیدی استفاده ثابت ها const است و کلمه کلید استفاده فیلد های خواندنی readonly است.
3: ثابت زمان کامپایل است اما readonly خواندی زمان اجرا است.
4:ثابت ها همان اول باید مقدار دهی شوند اما فقط خواندی را میتوان بعدا نیز مقدار دهی کرد.
سوال : 49 : کلاس استاتیک چیست؟ چه وقتی باید از آن استفاده کرد؟
کلاس استاتیک کلاسی است که هیچ شی نمی توان ایجاد کرد و همچنین نمی توان از آن ارث بری کرد.
public static class MyCollage { public static string collageName; public static string address; static MyCollage() { collageName = "ABC College" } // static method public static void CollegeBranch() { Console.WriteLine("Computers"); } }
پس فایده ایجاد کلاس های Static چیست؟
کلاس های استاتیک به عنوان container هایی برای اعضای استاتیک مانند متدها، سازنده ها و غیره استفاده می شوند.
کلمه کلیدی var متغیری است که نوع را کامپایلر در زمان کامپایل مشخص می کند.
using System; class Program { static void Main() { var a = 10; a = "Happy"//Cannot implicitly convert type 'string' to 'int' Console.WriteLine(a); Console.ReadLine(); } }
کلمه کلیدی dynamic متغیری است که نوع آن در زمان اجرا مشخص می شود.
using System; class Program { static void Main() { dynamic a = 10; a = "Happy" Console.WriteLine(a); //output: Happy Console.ReadLine(); } }
اینام یک کلاس خاص می باشد که نشان دهنده گروهی از ثابت هاست.
enum Weekdays { Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, }
چند ریختی به توانایی وجود، در اشکال متعدد اشاره دارد. برای یک رابط می توان تعاریف متعددی ارائه داد. به عنوان مثال، اگر کلاسی به نام Vehicle دارید، می تواند متدی به نام speed داشته باشد، اما شما نمی توانید آن را تعریف کنید زیرا خودروهای مختلف سرعت متفاوتی دارند. این متد در در کلاس های فرزند برای خودروهای مختلف، متفاوت تعریف خواهد شد.
چند ریختی ایستا (static binding or static polymorphism) نوعی چندشکلی است که در زمان کامپایل اتفاق می افتد. مثالی از چندشکلی زمان کامپایل، سربارگذاری متد است.
چند شکلی زمان اجرا(dynamic polymorphism) یا چند شکلی دینامیک (dynamic binding) نوعی چندشکلی است که در زمان اجرا اتفاق می افتد. مثالی از چندشکلی زمان اجرا، دوباره نویسی متد است.
سربارگذاری عملگر به پیاده سازی عملگرها برای نوع های تعریف شده توسط کاربر به همراه آرگومان هایی که با آن می فرستد، می پردازد.
بله، اگر متد استاتیک باشد و یا توسط کلاس های فرزندی ارث برده شده باشد.
توابع مجازی توابعی هستند که در کلاس والد وجود دارند و توسط کلاس های فرزند دوباره نویسی (override) می شوند. این توابع برای چند ریختی زمان اجرا استفاده می شوند.
توابع مجازی خالص یا abstract methods توابعی هستند که فقط در کلاس پایه اعلان می شوند. این بدان معنی است که آنها هیچ تعریفی در کلاس پایه ندارند و باید در کلاس فرزند تعریف شوند.
سازنده مخرب متدی است که در هنگام از بین رفتن یک شی به طور خودکار فراخوانی می شود. همچنین فضای هیپی که به شی نابود شده اختصاص داده شده بود را بازیابی می کند، فایل ها و اتصالات پایگاه داده شی را می بندد و غیره.
سازنده کپی با کپی کردن متغیرها از یک شی دیگر از همان کلاس، اشیاء را ایجاد می کند. هدف اصلی سازنده کپی ایجاد یک شی جدید از یک شی موجود است.
خیر مجبور نیستم می توانیم سازنده ای بدون ورودی داشته باشیم.
کپسوله سازی
خیر، ما می توانیم متدی از کلاس را اگر استاتیک تعریف شده باشد بدون نمونه سازی صدا بزنیم
ارث بری
انقیاد(Binding) به فرآیندی اشاره دارد که برای تبدیل شناسه ها (مانند نام متغیرها و تابع ها) به آدرس استفاده می شود.
اتصال استاتیک(early binding) به این معنی است که کامپایلر (پیوند دهنده) می تواند مستقیماً نام شناسه (مانند یک تابع یا نام متغیر) را با یک آدرس ماشین مرتبط کند. به یاد داشته باشید که همه توابع دارای یک آدرس منحصر به فرد هستند. بنابراین هنگامی که کامپایلر (پیوند دهنده) با یک فراخوانی تابع مواجه می شود، فراخوانی تابع را با یک دستورالعمل زبان ماشین جایگزین می کند که به CPU می گوید به آدرس تابع بپرد.
برخی آدرس دهی ها زمان اجرا (زمانی که برنامه اجرا می شود) می توان فهمید که کدام تابع فراخوانی می شود. این به عنوان late binding (اتصال پویا) شناخته می شود.
بله اگر از return درون بلاک catch یا try استفاده کنید باز هم بلاک finally فراخوانی می شود.
یک ویژگی از کلاس exception می باشد که دید مختصری از جزئیات exception والد و فرزند به ما می دهد.