🪤 تله بزرگ: NaN == NaN همیشه False است!
این یکی از عجیبترین و مهمترین نکاتیه که باید بدونید. طبق استاندارد IEEE 754، یک مقدار NaN هیچوقت با هیچ مقدار دیگهای، حتی یک NaN دیگه، برابر نیست!
منطقش اینه که یک "مقدار نامشخص" نمیتونه با یک "مقدار نامشخص" دیگه برابر باشه.
پس اگه بخواید با == چک کنید که آیا متغیری NaN هست یا نه، همیشه جواب false میگیرید و این میتونه باعث باگهای خیلی بدی بشه.
این یکی از عجیبترین و مهمترین نکاتیه که باید بدونید. طبق استاندارد IEEE 754، یک مقدار NaN هیچوقت با هیچ مقدار دیگهای، حتی یک NaN دیگه، برابر نیست!
منطقش اینه که یک "مقدار نامشخص" نمیتونه با یک "مقدار نامشخص" دیگه برابر باشه.
// این کد شما رو به اشتباه میندازه!
Console.WriteLine(0.0 / 0.0 == double.NaN); // خروجی: False
پس اگه بخواید با == چک کنید که آیا متغیری NaN هست یا نه، همیشه جواب false میگیرید و این میتونه باعث باگهای خیلی بدی بشه.
✅ راه حل: پس چطور NaN رو چک کنیم؟
راه درست و تنها راه مطمئن برای چک کردن NaN، استفاده از متدهای استاتیک double.IsNaN() یا float.IsNaN() هست.
🤔 حرف حساب و تجربه شما
درک این مقادیر ویژه، شما رو از خیلی از باگهای محاسباتی پنهان نجات میده.
شما تا حالا تو محاسباتتون به Infinity یا NaN برخوردید؟ یا از تله مقایسه NaN خبر داشتید؟ این رفتار عجیب اعداد اعشاری کجا به کارتون اومده یا براتون دردسر ساخته؟
تجربههاتون رو کامنت کنید! 👇
راه درست و تنها راه مطمئن برای چک کردن NaN، استفاده از متدهای استاتیک double.IsNaN() یا float.IsNaN() هست.
double result = 0.0 / 0.0;
// روش اشتباه
if (result == double.NaN) { /* این کد هرگز اجرا نمیشه */ }
// روش درست
if (double.IsNaN(result))
{
Console.WriteLine("The result is indeed NaN!");
}
🤔 حرف حساب و تجربه شما
درک این مقادیر ویژه، شما رو از خیلی از باگهای محاسباتی پنهان نجات میده.
شما تا حالا تو محاسباتتون به Infinity یا NaN برخوردید؟ یا از تله مقایسه NaN خبر داشتید؟ این رفتار عجیب اعداد اعشاری کجا به کارتون اومده یا براتون دردسر ساخته؟
تجربههاتون رو کامنت کنید! 👇
🔖 هشتگها :
#CSharp
#CodingTips
#DotNet
#FloatingPoint
#NaN
🎭 تلههای برابری (==) در #C:
مقایسه مقدار در برابر مقایسه آدرس!
فکر میکنید اپراتور == همیشه مقادیر رو با هم مقایسه میکنه؟ اگه دو تا شیء داشته باشیم که محتوای داخلیشون یکسانه، آیا با هم برابرن؟
جواب تو سیشارپ همیشه "بله" نیست! این یکی از اون تلههای کلاسیکه که ریشهش مستقیم به تفاوت Value Type و Reference Type برمیگرده. بیاید این موضوع مهم رو یک بار برای همیشه روشن کنیم.
✅ دنیای ساده: مقایسه Value Typeها
برای Value Typeها (مثل int, struct, bool)، اپراتور == دقیقاً همون کاری رو میکنه که انتظار دارید: مقدار واقعی داخل متغیرها رو با هم مقایسه میکنه. ساده و قابل پیشبینی.
مقایسه مقدار در برابر مقایسه آدرس!
فکر میکنید اپراتور == همیشه مقادیر رو با هم مقایسه میکنه؟ اگه دو تا شیء داشته باشیم که محتوای داخلیشون یکسانه، آیا با هم برابرن؟
جواب تو سیشارپ همیشه "بله" نیست! این یکی از اون تلههای کلاسیکه که ریشهش مستقیم به تفاوت Value Type و Reference Type برمیگرده. بیاید این موضوع مهم رو یک بار برای همیشه روشن کنیم.
✅ دنیای ساده: مقایسه Value Typeها
برای Value Typeها (مثل int, struct, bool)، اپراتور == دقیقاً همون کاری رو میکنه که انتظار دارید: مقدار واقعی داخل متغیرها رو با هم مقایسه میکنه. ساده و قابل پیشبینی.
int x = 5;
int y = 10;
int z = 5;
// آیا مقدار x با y برابره؟ نه.
Console.WriteLine(x == y); // خروجی: False
// آیا مقدار x با z برابره؟ بله.
Console.WriteLine(x == z); // خروجی: True
❓ دنیای پیچیده:
اینجاست که داستان جالب میشه! برای Reference Typeها (مثل class)، اپراتور == به صورت پیشفرض، محتوای داخلی آبجکتها رو مقایسه نمیکنه.
در عوض، آدرس اونها در حافظه رو مقایسه میکنه. یعنی از خودش میپرسه: "آیا این دو متغیر، به یک آبجکت یکسان در حافظه اشاره میکنن؟"
یادتونه گفتیم Reference Type مثل کلید یه خونهست؟ اینجا == چک نمیکنه که آیا دو تا خونه دکوراسیون یکسانی دارن یا نه. فقط چک میکنه که آیا این دو تا کلید، برای یک خونهی یکسان هستن یا برای دو تا خونهی جدا که فقط شبیه هم ساخته شدن.
مثال کلاس Dude رو ببینید:
مقایسه Reference Typeها
اینجاست که داستان جالب میشه! برای Reference Typeها (مثل class)، اپراتور == به صورت پیشفرض، محتوای داخلی آبجکتها رو مقایسه نمیکنه.
در عوض، آدرس اونها در حافظه رو مقایسه میکنه. یعنی از خودش میپرسه: "آیا این دو متغیر، به یک آبجکت یکسان در حافظه اشاره میکنن؟"
یادتونه گفتیم Reference Type مثل کلید یه خونهست؟ اینجا == چک نمیکنه که آیا دو تا خونه دکوراسیون یکسانی دارن یا نه. فقط چک میکنه که آیا این دو تا کلید، برای یک خونهی یکسان هستن یا برای دو تا خونهی جدا که فقط شبیه هم ساخته شدن.
مثال کلاس Dude رو ببینید:
public class Dude
{
public string Name;
public Dude(string n) { Name = n; }
}
// ...
// ما دو تا آبجکت جداگانه در حافظه میسازیم که هر دو اسمشون "John" هست
Dude d1 = new Dude("John");
Dude d2 = new Dude("John");
// اینجا d3 کلید خونهی d1 رو میگیره، خونه جدیدی ساخته نمیشه
Dude d3 = d1;
// آیا d1 و d2 به یک آبجکت یکسان اشاره میکنند؟ نه!
// (دو تا خونه جدا ولی شبیه به هم)
Console.WriteLine(d1 == d2); // خروجی: False
// آیا d1 و d3 به یک آبجکت یکسان اشاره میکنند؟ بله!
// (دو تا کلید برای یک خونه)
Console.WriteLine(d1 == d3); // خروجی: True
🪄 جادوی اپراتورهای شرطی #C :
از Short-Circuiting تا اپراتور سهتایی.
فقط یه if ساده نوشتن، کار هر کسیه. اما یه برنامهنویس حرفهای، با استفاده از ابزارهایی مثل Short-Circuiting و اپراتور سهتایی، ifهای هوشمندتر، امنتر و خواناتری مینویسه.
بیاید با این جادوهای پرکاربرد آشنا بشیم.
از Short-Circuiting تا اپراتور سهتایی.
فقط یه if ساده نوشتن، کار هر کسیه. اما یه برنامهنویس حرفهای، با استفاده از ابزارهایی مثل Short-Circuiting و اپراتور سهتایی، ifهای هوشمندتر، امنتر و خواناتری مینویسه.
بیاید با این جادوهای پرکاربرد آشنا بشیم.
🔌 اتصال کوتاه (Short-Circuiting):
ناجی شما از NullReferenceException
این مهمترین مفهومیه که در مورد اپراتورهای && (AND) و || (OR) باید بدونید.
در یک عبارت &&، اگر قسمت اول false باشه، #C اونقدر هوشمنده که دیگه قسمت دوم رو اصلاً بررسی نمیکنه (چون نتیجه کل عبارت قطعاً false هست).
در یک عبارت ||، اگر قسمت اول true باشه، باز هم قسمت دوم بررسی نمیشه (چون نتیجه کل عبارت قطعاً true هست).
این فقط برای سرعت بیشتر نیست، بلکه یه تکنیک حیاتی برای جلوگیری از خطاست. به این مثال طلایی نگاه کنید:
اینجا چون myString != null برابر با false هست، #C هیچوقت به myString.Length نگاه هم نمیکنه و برنامه شما از یک خطای حتمی نجات پیدا میکنه!
ناجی شما از NullReferenceException
این مهمترین مفهومیه که در مورد اپراتورهای && (AND) و || (OR) باید بدونید.
قانون اتصال کوتاه میگه:
در یک عبارت &&، اگر قسمت اول false باشه، #C اونقدر هوشمنده که دیگه قسمت دوم رو اصلاً بررسی نمیکنه (چون نتیجه کل عبارت قطعاً false هست).
در یک عبارت ||، اگر قسمت اول true باشه، باز هم قسمت دوم بررسی نمیشه (چون نتیجه کل عبارت قطعاً true هست).
این فقط برای سرعت بیشتر نیست، بلکه یه تکنیک حیاتی برای جلوگیری از خطاست. به این مثال طلایی نگاه کنید:
string myString = null;
// این کد بدون اتصال کوتاه، خطای NullReferenceException میداد
// چون sb.Length روی یک متغیر null اجرا میشد.
if (myString != null && myString.Length > 0)
{
Console.WriteLine("String has content.");
}
else
{
Console.WriteLine("String is null or empty.");
}
اینجا چون myString != null برابر با false هست، #C هیچوقت به myString.Length نگاه هم نمیکنه و برنامه شما از یک خطای حتمی نجات پیدا میکنه!
✨ اپراتور سهتایی (Ternary Operator ? : )
یک if-else در یک خط!
اپراتور سهتایی یه راه خیلی شیک و کوتاه برای نوشتن یه if-else سادهست که قراره یه مقداری رو برگردونه.
ساختارش اینه:
شرط ? مقدار در صورت درستی : مقدار در صورت نادرستی
مثلاً به جای اینکه اینجوری بنویسیم:
میتونیم خیلی شیک و کوتاه اینجوری بنویسیم:
یک if-else در یک خط!
اپراتور سهتایی یه راه خیلی شیک و کوتاه برای نوشتن یه if-else سادهست که قراره یه مقداری رو برگردونه.
ساختارش اینه:
شرط ? مقدار در صورت درستی : مقدار در صورت نادرستی
مثلاً به جای اینکه اینجوری بنویسیم:
int a = 10;
int b = 20;
int max;
if (a > b)
{
max = a;
}
else
{
max = b;
}
میتونیم خیلی شیک و کوتاه اینجوری بنویسیم:
int a = 10;
int b = 20;
int max = (a > b) ? a : b; // اگر a > b درست بود، a رو برگردون، وگرنه b رو
Console.WriteLine(max); // خروجی: 20
💡 نکته سریع #C :
سه راز در مورد bool که شاید نمیدانستید!
همه ما هر روز از bool برای شرطهامون استفاده میکنیم، ولی این نوع داده منطقی، چند تا راز کوچک و جالب داره که دونستنشون دید شما رو به سیشارپ عمیقتر میکنه.
برخلاف بعضی زبانهای برنامهنویسی دیگه، در C# نوع bool یک نوع منطقی کاملاً مستقله و هیچ ارتباطی با اعداد نداره. شما نمیتونید یک bool رو به int تبدیل کنید یا برعکس. true معادل 1 نیست!
این ویژگی، جلوی بسیاری از خطاهای منطقی ناخواسته رو میگیره.
سه راز در مورد bool که شاید نمیدانستید!
همه ما هر روز از bool برای شرطهامون استفاده میکنیم، ولی این نوع داده منطقی، چند تا راز کوچک و جالب داره که دونستنشون دید شما رو به سیشارپ عمیقتر میکنه.
بولین، عدد نیست!
برخلاف بعضی زبانهای برنامهنویسی دیگه، در C# نوع bool یک نوع منطقی کاملاً مستقله و هیچ ارتباطی با اعداد نداره. شما نمیتونید یک bool رو به int تبدیل کنید یا برعکس. true معادل 1 نیست!
// این کدها اصلاً کامپایل نمیشن!
// int myInt = (int)true; // ❌ خطای کامپایل!
// bool myBool = (bool)1; // ❌ خطای کامپایل!
این ویژگی، جلوی بسیاری از خطاهای منطقی ناخواسته رو میگیره.
یک بیت یا یک بایت؟ مسئله این است!
از نظر تئوری، برای ذخیره یک مقدار bool (true یا false) فقط یک بیت حافظه کافیه. اما در عمل، CLR برای بهینگی و راحتی کار با پردازنده، برای هر متغیر bool یک بایت (۸ بیت) کامل در حافظه در نظر میگیره.
نکته حرفهای:
اگه نیاز دارید تعداد خیلی خیلی زیادی bool رو در حافظه ذخیره کنید و فضا براتون مهمه (مثلاً در کار با فایلهای باینری)، میتونید از کلاس System.Collections.BitArray استفاده کنید که برای هر مقدار، دقیقاً یک بیت مصرف میکنه.
bool فقط یک نام مستعاره!
مثل بقیه انواع داده پیشساخته، کلمه کلیدی bool در #C فقط یک نام مستعار (alias) ساده و راحت برای نوع داده اصلی در فریمورک داتنت یعنی System.Boolean هست.
این دو خط کد، در نهایت یک کار یکسان رو انجام میدن:
bool b1 = true;
System.Boolean b2 = true; // این همون بالاییه، فقط با اسم کاملش!
حرف آخر:
پس یادتون باشه: bool یه نوع منطقی خالصه، نه یه عدد. در حافظه یه بایت جا میگیره و فقط یه نام مستعار برای System.Boolean هست!
🔖 هشتگها :
#CSharp
#QuickTip
#DotNet
#Boolean
#CodingTips
🔒 رازهای string در #C :
همهی ما هر روز با string کار میکنیم، ولی دو تا از بزرگترین رازهای رفتاریش رو میدونید؟ stringها در سیشارپ هم تغییرناپذیرن و هم اپراتور == روشون یه رفتار خاص داره.
درک این دو نکته، دید شما رو به زبان #C و نحوه مدیریت حافظه، عمیقتر میکنه و شما رو به یه توسعهدهنده مسلطتر تبدیل میکنه.
1️⃣ راز اول:
وقتی یه string میسازید، دیگه هرگز نمیتونید محتوای اون رو در حافظه تغییر بدید. هر عملیاتی که به نظر میاد داره یه string رو عوض میکنه (مثل ToUpper() یا +)، در واقع داره یه string کاملاً جدید در حافظه میسازه و قبلی رو دستنخورده رها میکنه.
مثال:
چرا این ویژگی خوبه؟
این ویژگی باعث میشه کد شما قابل پیشبینیتر، امنتر (به خصوص در محیطهای چندنخی یا Multi-threaded) و بهینهتر باشه، چون کامپایلر میتونه رشتههای یکسان رو در حافظه به اشتراک بذاره (به این کار String Interning میگن).
2️⃣راز دوم:
تو پست "تلههای برابری" دیدیم که == روی Reference Typeها، آدرس حافظه رو مقایسه میکنه. string هم یه Reference Type هست، پس چرا این کد true برمیگردونه؟
جواب:
چون تیم طراح #C اپراتور == رو برای string به صورت خاص بازنویسی (Overload) کرده. اونها میدونستن که ۹۹٪ مواقع وقتی دو تا رشته رو مقایسه میکنیم، منظورمون مقایسه محتوای اونهاست، نه آدرس حافظهشون. پس این اپراتور رو هوشمند کردن تا کار ما رو راحتتر کنن و جلوی یه باگ رایج رو بگیرن!
این یکی از نمونههای طراحی هوشمندانه در زبان #C هست.
🤔 حرف حساب و تجربه شما
این رفتارهای خاص string، نمونهای عالی از طراحی هوشمندانه در زبان #C هست که هم به کارایی کمک میکنه و هم جلوی خطاهای رایج رو میگیره.
آیا تا حالا به این فکر کرده بودید که وقتی یه رشته رو با + ترکیب میکنید، در واقع دارید رشتههای جدید در حافظه میسازید (که میتونه روی پرفورمنس تاثیر بذاره)؟ یا از رفتار خاص == برای string خبر داشتید؟
تغییرناپذیری (Immutability) و جادوی برابری
همهی ما هر روز با string کار میکنیم، ولی دو تا از بزرگترین رازهای رفتاریش رو میدونید؟ stringها در سیشارپ هم تغییرناپذیرن و هم اپراتور == روشون یه رفتار خاص داره.
درک این دو نکته، دید شما رو به زبان #C و نحوه مدیریت حافظه، عمیقتر میکنه و شما رو به یه توسعهدهنده مسلطتر تبدیل میکنه.
1️⃣ راز اول:
🛡تغییرناپذیری (Immutability):
وقتی یه string میسازید، دیگه هرگز نمیتونید محتوای اون رو در حافظه تغییر بدید. هر عملیاتی که به نظر میاد داره یه string رو عوض میکنه (مثل ToUpper() یا +)، در واقع داره یه string کاملاً جدید در حافظه میسازه و قبلی رو دستنخورده رها میکنه.
مثال:
string s1 = "hello";
// این کد s1 را تغییر نمیدهد!
// بلکه یک رشته جدید ("HELLO") میسازد و در s2 قرار میدهد.
string s2 = s1.ToUpper();
Console.WriteLine($"s1: {s1}"); // خروجی: s1: hello
Console.WriteLine($"s2: {s2}"); // خروجی: s2: HELLO
چرا این ویژگی خوبه؟
این ویژگی باعث میشه کد شما قابل پیشبینیتر، امنتر (به خصوص در محیطهای چندنخی یا Multi-threaded) و بهینهتر باشه، چون کامپایلر میتونه رشتههای یکسان رو در حافظه به اشتراک بذاره (به این کار String Interning میگن).
2️⃣راز دوم:
🎭جادوی برابری (==)
تو پست "تلههای برابری" دیدیم که == روی Reference Typeها، آدرس حافظه رو مقایسه میکنه. string هم یه Reference Type هست، پس چرا این کد true برمیگردونه؟
string a = "test";
string b = "test";
Console.WriteLine(a == b); // خروجی: True! ولی چرا؟
جواب:
چون تیم طراح #C اپراتور == رو برای string به صورت خاص بازنویسی (Overload) کرده. اونها میدونستن که ۹۹٪ مواقع وقتی دو تا رشته رو مقایسه میکنیم، منظورمون مقایسه محتوای اونهاست، نه آدرس حافظهشون. پس این اپراتور رو هوشمند کردن تا کار ما رو راحتتر کنن و جلوی یه باگ رایج رو بگیرن!
این یکی از نمونههای طراحی هوشمندانه در زبان #C هست.
🤔 حرف حساب و تجربه شما
این رفتارهای خاص string، نمونهای عالی از طراحی هوشمندانه در زبان #C هست که هم به کارایی کمک میکنه و هم جلوی خطاهای رایج رو میگیره.
آیا تا حالا به این فکر کرده بودید که وقتی یه رشته رو با + ترکیب میکنید، در واقع دارید رشتههای جدید در حافظه میسازید (که میتونه روی پرفورمنس تاثیر بذاره)؟ یا از رفتار خاص == برای string خبر داشتید؟
🔖 هشتگها :
#CSharp
#String
#BestPractices
🛠 نوشتن string مثل یک حرفهای:
در سیشارپ، فقط یک راه برای نوشتن رشتهها وجود نداره. ابزارهای مختلفی در اختیار ماست که هر کدوم برای یه سناریوی خاص، بهترین انتخابه. تسلط بر این ابزارها، کد شما رو نه تنها خواناتر، که حرفهایتر هم میکنه.
1️⃣ روش کلاسیک: دردسرهای Escape Sequence (\)
این روش سنتیترین راهه. برای رشتههای ساده خوبه، ولی وقتی پای کاراکترهای خاص مثل بکاسلش (\) یا کوتیشن (") وسط بیاد، کد شما به سرعت زشت و ناخوانا میشه.
به خصوص برای مسیر فایلها در ویندوز، این روش یه کابوس واقعیه!
2️⃣ راه حل تمیز: Verbatim Literals (@)
اینجاست که @ مثل یه قهرمان وارد میشه! با گذاشتن یه @ قبل از رشته، شما به کامپایلر میگید: "هرچیزی که داخل این رشته هست رو دقیقاً همونطور که هست در نظر بگیر و هیچ کاراکتری رو escape نکن."
این قابلیت دو تا مزیت بزرگ داره:
مسیرهای خوانا:
متنهای چند خطی:
میتونید به راحتی و بدون نیاز به \n، متنهای چند خطی بنویسید.
3️⃣ قدرت مدرن: Raw String Literals ("""...""") (از C# 11 به بعد)
این جدیدترین و قدرتمندترین ابزار ماست که برای حل مشکلات کار با JSON، XML، HTML یا هر متن پیچیدهای طراحی شده.
رشته رو بین سه تا (یا بیشتر) دابل کوتیشن میذارید و دیگه هیچ نیازی به هیچگونه escape کردنی ندارید. حتی لازم نیست " رو دو بار بنویسید!
این قابلیت، کد شما رو به شکل چشمگیری تمیزتر و قابل نگهداریتر میکنه، به خصوص وقتی با متنهای ساختاریافته سر و کار دارید.
🤔 حرف حساب و ابزار شما
هر کدوم از این ابزارها، برای یه کاری ساخته شدن.
●برای رشتههای ساده ⟵ روش کلاسیک
●برای مسیر فایل و متنهای چند خطی ⟵ Verbatim (@)
●برای JSON، XML و متنهای پیچیده ⟵ Raw String (""")
شما از کدوم یکی از این روشها بیشتر استفاده میکنید؟ آیا از طرفدارای Raw String Literals جدید هستید یا هنوز با @ کارتون راه میفته؟
در سیشارپ، فقط یک راه برای نوشتن رشتهها وجود نداره. ابزارهای مختلفی در اختیار ماست که هر کدوم برای یه سناریوی خاص، بهترین انتخابه. تسلط بر این ابزارها، کد شما رو نه تنها خواناتر، که حرفهایتر هم میکنه.
1️⃣ روش کلاسیک: دردسرهای Escape Sequence (\)
این روش سنتیترین راهه. برای رشتههای ساده خوبه، ولی وقتی پای کاراکترهای خاص مثل بکاسلش (\) یا کوتیشن (") وسط بیاد، کد شما به سرعت زشت و ناخوانا میشه.
به خصوص برای مسیر فایلها در ویندوز، این روش یه کابوس واقعیه!
// هر \ باید دو بار نوشته بشه!
string classicPath = "C:\\Users\\MyUser\\Documents\\file.txt";
2️⃣ راه حل تمیز: Verbatim Literals (@)
اینجاست که @ مثل یه قهرمان وارد میشه! با گذاشتن یه @ قبل از رشته، شما به کامپایلر میگید: "هرچیزی که داخل این رشته هست رو دقیقاً همونطور که هست در نظر بگیر و هیچ کاراکتری رو escape نکن."
این قابلیت دو تا مزیت بزرگ داره:
مسیرهای خوانا:
// همون مسیر فایل، ولی تمیز و خوانا!
string verbatimPath = @"C:\Users\MyUser\Documents\file.txt";
متنهای چند خطی:
میتونید به راحتی و بدون نیاز به \n، متنهای چند خطی بنویسید.
string multiLine = @"این
یک متن
چند خطی است.";
نکته: برای استفاده از " داخل رشته @، کافیه دو بار بنویسیدش:
@"<tag id=""123"">"
3️⃣ قدرت مدرن: Raw String Literals ("""...""") (از C# 11 به بعد)
این جدیدترین و قدرتمندترین ابزار ماست که برای حل مشکلات کار با JSON، XML، HTML یا هر متن پیچیدهای طراحی شده.
رشته رو بین سه تا (یا بیشتر) دابل کوتیشن میذارید و دیگه هیچ نیازی به هیچگونه escape کردنی ندارید. حتی لازم نیست " رو دو بار بنویسید!
string json = """
{
"Name" : "C# Geeks",
"Quote" : "We write clean code!",
"Path" : "C:\Temp\Data.json"
}
""";
این قابلیت، کد شما رو به شکل چشمگیری تمیزتر و قابل نگهداریتر میکنه، به خصوص وقتی با متنهای ساختاریافته سر و کار دارید.
🤔 حرف حساب و ابزار شما
هر کدوم از این ابزارها، برای یه کاری ساخته شدن.
●برای رشتههای ساده ⟵ روش کلاسیک
●برای مسیر فایل و متنهای چند خطی ⟵ Verbatim (@)
●برای JSON، XML و متنهای پیچیده ⟵ Raw String (""")
شما از کدوم یکی از این روشها بیشتر استفاده میکنید؟ آیا از طرفدارای Raw String Literals جدید هستید یا هنوز با @ کارتون راه میفته؟
🔖 هشتگها :
#CSharp
#String
#CodingTips
✨ ساختن رشتهها در #C :
همهی ما یه زمانی اینجوری رشته میساختیم:
این کد کار میکنه، ولی آیا بهترین، خواناترین و بهینهترین راهه؟
بیاید دو روش اصلی ساختن رشته در سیشارپ رو با تمام جزئیاتشون کالبدشکافی کنیم و ببینیم کی باید از کدوم استفاده کرد.
🔗 روش سنتی: الحاق با + و تلهی پرفورمنس
این سادهترین روشه. اپراتور + دو تا رشته رو به هم میچسبونه. اگه یکی از مقادیر رشته نباشه، #C به صورت خودکار متد ()ToString رو روش صدا میزنه.
اما تله بزرگ کجاست؟
یادتونه تو پست "رازهای string" گفتیم که stringها تغییرناپذیر (Immutable) هستن؟ این یعنی هر بار که از + استفاده میکنید، #C یه رشته کاملاً جدید در حافظه میسازه و قبلیها رو دور میریزه.
اگه این کار رو تو یه حلقه انجام بدید، دارید رسماً حافظه رو به آتیش میکشید و برنامهتون رو به شدت کند میکنید!
برای ساختن رشتههای داینامیک در حلقهها، همیشه از کلاس System.Text.StringBuilder استفاده کنید که بعداً مفصل در موردش صحبت میکنیم.
💲 روش مدرن و حرفهای: درونیابی با $ (String Interpolation)
این روش که با گذاشتن $ قبل از رشته فعال میشه، خواناترین و قدرتمندترین راه برای ساختن رشتههاست. شما میتونید متغیرها و عبارات #C رو مستقیم داخل رشته و بین آکولاد {} بنویسید.
فرمتدهی 🎨: میتونید خروجی رو همونجا با گذاشتن : و یه "فرمت استرینگ" کنترل کنید. مثلاً برای نمایش عدد در مبنای هگزادسیمال:
عبارات پیچیده 🤯: اگه عبارتتون پیچیدهست
یا خودش : داره (مثل اپراتور سهتایی)، کل عبارت رو داخل پرانتز بذارید:
رشتههای ثابت (از C# 10) 💎: اگه تمام مقادیر داخل $ ثابت باشن، خود رشته هم میتونه const باشه:
رشتههای چند خطی (از C# 11) 📄: رشتههای درونیابی میتونن چند خطی هم باشن که کار رو خیلی راحتتر میکنه:
🤔 حرف حساب و انتخاب شما
برای چسبوندن دو یا سه تا رشته ساده ⟵ + کار راه اندازه.
برای هر حالت دیگهای، به خصوص وقتی متغیرها رو داخل رشته میارید ⟵ $ (درونیابی) انتخاب اول و آخر شماست. خوانا، قدرتمند و مدرن.
شما تو کدهاتون بیشتر از کدوم روش استفاده میکنید؟ آیا تا حالا با مشکل پرفورمنس به خاطر استفاده زیاد از + تو حلقهها برخورد کردید؟ یا چه ترفند باحالی برای فرمت کردن رشتهها با $ بلدید؟
+ در برابر $ (الحاق یا درونیابی؟)
همهی ما یه زمانی اینجوری رشته میساختیم:
string s = "User: " + name + " | Age: " + age;
این کد کار میکنه، ولی آیا بهترین، خواناترین و بهینهترین راهه؟
بیاید دو روش اصلی ساختن رشته در سیشارپ رو با تمام جزئیاتشون کالبدشکافی کنیم و ببینیم کی باید از کدوم استفاده کرد.
🔗 روش سنتی: الحاق با + و تلهی پرفورمنس
این سادهترین روشه. اپراتور + دو تا رشته رو به هم میچسبونه. اگه یکی از مقادیر رشته نباشه، #C به صورت خودکار متد ()ToString رو روش صدا میزنه.
string s = "User ID: " + 5; // خروجی: "User ID: 5"
اما تله بزرگ کجاست؟
یادتونه تو پست "رازهای string" گفتیم که stringها تغییرناپذیر (Immutable) هستن؟ این یعنی هر بار که از + استفاده میکنید، #C یه رشته کاملاً جدید در حافظه میسازه و قبلیها رو دور میریزه.
اگه این کار رو تو یه حلقه انجام بدید، دارید رسماً حافظه رو به آتیش میکشید و برنامهتون رو به شدت کند میکنید!
راه حل حرفهای (برای موارد پیچیده)
برای ساختن رشتههای داینامیک در حلقهها، همیشه از کلاس System.Text.StringBuilder استفاده کنید که بعداً مفصل در موردش صحبت میکنیم.
💲 روش مدرن و حرفهای: درونیابی با $ (String Interpolation)
این روش که با گذاشتن $ قبل از رشته فعال میشه، خواناترین و قدرتمندترین راه برای ساختن رشتههاست. شما میتونید متغیرها و عبارات #C رو مستقیم داخل رشته و بین آکولاد {} بنویسید.
int x = 4;
Console.WriteLine($"A square has {x} sides."); // خروجی: A square has 4 sides
قدرتهای پنهان درونیابی:
فرمتدهی 🎨: میتونید خروجی رو همونجا با گذاشتن : و یه "فرمت استرینگ" کنترل کنید. مثلاً برای نمایش عدد در مبنای هگزادسیمال:
string s = $"255 in hex is {byte.MaxValue:X2}"; // X2 یعنی هگزادسیمال با ۲ رقم
// خروجی: "255 in hex is FF"عبارات پیچیده 🤯: اگه عبارتتون پیچیدهست
یا خودش : داره (مثل اپراتور سهتایی)، کل عبارت رو داخل پرانتز بذارید:
bool b = true;
Console.WriteLine($"The answer is {(b ? 1 : 0)}");
رشتههای ثابت (از C# 10) 💎: اگه تمام مقادیر داخل $ ثابت باشن، خود رشته هم میتونه const باشه:
const string greeting = "Hello";
const string message = $"{greeting}, world";
رشتههای چند خطی (از C# 11) 📄: رشتههای درونیابی میتونن چند خطی هم باشن که کار رو خیلی راحتتر میکنه:
string s = $"This interpolation spans {1 + 1} lines";🤔 حرف حساب و انتخاب شما
برای چسبوندن دو یا سه تا رشته ساده ⟵ + کار راه اندازه.
برای هر حالت دیگهای، به خصوص وقتی متغیرها رو داخل رشته میارید ⟵ $ (درونیابی) انتخاب اول و آخر شماست. خوانا، قدرتمند و مدرن.
شما تو کدهاتون بیشتر از کدوم روش استفاده میکنید؟ آیا تا حالا با مشکل پرفورمنس به خاطر استفاده زیاد از + تو حلقهها برخورد کردید؟ یا چه ترفند باحالی برای فرمت کردن رشتهها با $ بلدید؟
🔖 هشتگها :
#CSharp
#String
#DotNet
#Performance
⛓️ راهنمای جامع آرایهها (Arrays) در #C : از ساختار تا حافظه
آرایهها یکی از پایهایترین و در عین حال قدرتمندترین ساختارهای داده تو هر زبان برنامهنویسیه. درک عمیقشون برای نوشتن کدهای بهینه و کارآمد، حیاتیه. بیاید یه شیرجه عمیق بزنیم تو دنیای آرایهها در #C و تمام جزئیاتش رو یاد بگیریم.
1️⃣شالوده آرایه: تعریف، ساختار و دسترسی
آرایه، مجموعهای با تعداد ثابت از متغیرهاست که همگی از یک نوع مشخص هستن.
عناصر یک آرایه، همیشه در یک بلوک حافظه پشت سر هم و یکپارچه (Contiguous) قرار میگیرن که باعث دسترسی فوقالعاده سریع به هر کدوم از عناصر میشه.
آرایه رو با [] بعد از نوع داده مشخص میکنیم. برای دسترسی به هر عنصر (element) هم از ایندکس اون استفاده میکنیم که از صفر شروع میشه.
2️⃣مقداردهی و پیمایش: از روش کلاسیک تا C# 12
برای کار با تمام عناصر یک آرایه، معمولاً از حلقهها استفاده میکنیم. پراپرتی .Length هم به ما تعداد کل عناصر آرایه رو میده.
بعد از ساختن یک آرایه، طول (ظرفیت) آن دیگر قابل تغییر نیست. برای ساختارهای داده با اندازه داینامیک، باید از کالکشنهای پیشرفتهتری که در System.Collection وجود داره استفاده کرد.
میتونید موقع تعریف، مستقیماً آرایه رو مقداردهی کنید:
روش مدرن (از C# 12):
از C# 12 به بعد، میتونید از [] به جای {} برای مقداردهی اولیه استفاده کنید. مزیت بزرگش اینه که میتونید مستقیماً اون رو به متدها پاس بدید:
3️⃣راز مقداردهی پیشفرض (Default Initialization)
وقتی یک آرایه رو میسازید، عناصرش هیچوقت خالی نیستن! #C همیشه اونها رو با مقدار پیشفرضِ نوع دادهشون پر میکنه. این کار با صفر کردن بیتهای حافظه (bitwise zeroing) انجام میشه.
برای انواع عددی مثل int، مقدار پیشفرض 0 است.
برای bool، مقدار پیشفرض false است.
برای تمام Reference Typeها (مثل string یا کلاسها)، مقدار پیشفرض null است.
4️⃣مهمترین بحث: آرایهی Value Type در برابر Reference Type
یادتونه در مورد تفاوت Value و Reference Type حرف زدیم؟ اینجا خودشو به بهترین شکل نشون میده!
آرایهای از Value Type (مثل struct):
وقتی آرایهای از structها میسازید، خودِ آبجکتها به صورت فیزیکی و پشت سر هم در اون بلوک حافظه قرار میگیرن.
آرایهای از Reference Type (مثل class):
اما وقتی آرایهای از classها میسازید، اون بلوک حافظه فقط با ۱۰۰۰ تا رفرنس null پر میشه! هنوز هیچ آبجکتی ساخته نشده.
راه حل: باید بعد از ساختن آرایه، در یک حلقه، هر عنصر رو به صورت جداگانه با new بسازید:
5️⃣نکات تکمیلی و کلیدی
نکته 1: خودِ آرایه، همیشه یک Reference Type است، حتی اگه عناصر داخلش Value Type باشن. این یعنی میتونید یه رفرنس آرایه داشته باشید که null باشه:
نکته 2: تمام آرایهها در #C به صورت پنهان از کلاس System.Array ارثبری میکنند که یک سری متدهای عمومی و کاربردی رو در اختیارشون میذاره.
آرایهها یکی از پایهایترین و در عین حال قدرتمندترین ساختارهای داده تو هر زبان برنامهنویسیه. درک عمیقشون برای نوشتن کدهای بهینه و کارآمد، حیاتیه. بیاید یه شیرجه عمیق بزنیم تو دنیای آرایهها در #C و تمام جزئیاتش رو یاد بگیریم.
1️⃣شالوده آرایه: تعریف، ساختار و دسترسی
آرایه، مجموعهای با تعداد ثابت از متغیرهاست که همگی از یک نوع مشخص هستن.
نکته کلیدی پرفورمنس:
عناصر یک آرایه، همیشه در یک بلوک حافظه پشت سر هم و یکپارچه (Contiguous) قرار میگیرن که باعث دسترسی فوقالعاده سریع به هر کدوم از عناصر میشه.
نحوه تعریف و دسترسی:
آرایه رو با [] بعد از نوع داده مشخص میکنیم. برای دسترسی به هر عنصر (element) هم از ایندکس اون استفاده میکنیم که از صفر شروع میشه.
// یک آرایه از نوع char با ظرفیت ۵ عنصر تعریف میکنیم
char[] vowels = new char[5];
// به هر عنصر با ایندکسش دسترسی پیدا میکنیم
vowels[0] = 'a';
vowels[1] = 'e';
vowels[2] = 'i';
vowels[3] = 'o';
vowels[4] = 'u';
Console.WriteLine(vowels[1]); // خروجی: e
2️⃣مقداردهی و پیمایش: از روش کلاسیک تا C# 12
برای کار با تمام عناصر یک آرایه، معمولاً از حلقهها استفاده میکنیم. پراپرتی .Length هم به ما تعداد کل عناصر آرایه رو میده.
for (int i = 0; i < vowels.Length; i++)
{
Console.Write(vowels[i]); // خروجی: aeiou
}
نکته:
بعد از ساختن یک آرایه، طول (ظرفیت) آن دیگر قابل تغییر نیست. برای ساختارهای داده با اندازه داینامیک، باید از کالکشنهای پیشرفتهتری که در System.Collection وجود داره استفاده کرد.
مقداردهی اولیه سریع:
میتونید موقع تعریف، مستقیماً آرایه رو مقداردهی کنید:
// روش قدیمیتر
char[] vowels = new char[] {'a','e','i','o','u'};
// روش سادهتر
char[] vowels = {'a','e','i','o','u'};
روش مدرن (از C# 12):
Collection Expressions
از C# 12 به بعد، میتونید از [] به جای {} برای مقداردهی اولیه استفاده کنید. مزیت بزرگش اینه که میتونید مستقیماً اون رو به متدها پاس بدید:
// تعریف آرایه با سینتکس جدید
char[] vowels = ['a','e','i','o','u'];
// پاس دادن مستقیم به یک متد
PrintLetters(['x', 'y', 'z']);
3️⃣راز مقداردهی پیشفرض (Default Initialization)
وقتی یک آرایه رو میسازید، عناصرش هیچوقت خالی نیستن! #C همیشه اونها رو با مقدار پیشفرضِ نوع دادهشون پر میکنه. این کار با صفر کردن بیتهای حافظه (bitwise zeroing) انجام میشه.
برای انواع عددی مثل int، مقدار پیشفرض 0 است.
برای bool، مقدار پیشفرض false است.
برای تمام Reference Typeها (مثل string یا کلاسها)، مقدار پیشفرض null است.
int[] numbers = new int[1000];
Console.WriteLine(numbers[123]); // خروجی: 0
4️⃣مهمترین بحث: آرایهی Value Type در برابر Reference Type
یادتونه در مورد تفاوت Value و Reference Type حرف زدیم؟ اینجا خودشو به بهترین شکل نشون میده!
آرایهای از Value Type (مثل struct):
وقتی آرایهای از structها میسازید، خودِ آبجکتها به صورت فیزیکی و پشت سر هم در اون بلوک حافظه قرار میگیرن.
public struct Point { public int X, Y; }
Point[] points = new Point[1000];
// اینجا ما ۱۰۰۰ آبجکت Point کامل در حافظه داریم
// و مقدار پیشفرض هر X و Y در آنها، صفر است
Console.WriteLine(points[500].X); // خروجی: 0 آرایهای از Reference Type (مثل class):
اما وقتی آرایهای از classها میسازید، اون بلوک حافظه فقط با ۱۰۰۰ تا رفرنس null پر میشه! هنوز هیچ آبجکتی ساخته نشده.
public class Point { public int X, Y; }
Point[] points = new Point[1000];
// اینجا ما فقط ۱۰۰۰ تا جای خالی (null) داریم
// این کد خطای NullReferenceException میده!
// چون points[500] نال است و X ندارد.
// int x = points[500].X; // ❌ BOOM!راه حل: باید بعد از ساختن آرایه، در یک حلقه، هر عنصر رو به صورت جداگانه با new بسازید:
for (int i = 0; i < points.Length; i++)
{
points[i] = new Point(); // ساختن هر آبجکت به صورت مجزا
}
5️⃣نکات تکمیلی و کلیدی
نکته 1: خودِ آرایه، همیشه یک Reference Type است، حتی اگه عناصر داخلش Value Type باشن. این یعنی میتونید یه رفرنس آرایه داشته باشید که null باشه:
int[] myArray = null;
نکته 2: تمام آرایهها در #C به صورت پنهان از کلاس System.Array ارثبری میکنند که یک سری متدهای عمومی و کاربردی رو در اختیارشون میذاره.
🤔 حرف حساب و تجربه شما
تسلط بر این رفتارها، شما رو از خیلی از خطاهای رایج مثل NullReferenceException نجات میده و بهتون دید عمیقی نسبت به مدیریت حافظه میده.
مهمترین نکتهای که امروز در مورد آرایهها یاد گرفتید چی بود؟ آیا تفاوت رفتار آرایهی struct و class یا اینکه خود آرایه یک Reference Type هست، براتون جدید بود؟
خب، اینجا که نمیشه همه حرفا رو زد! 😉
ادامهی بحث، سوالات، غر زدنها و گپ و گفتهای خودمونی، فقط تو گروه.
[پاتوق گیک های #C]
تسلط بر این رفتارها، شما رو از خیلی از خطاهای رایج مثل NullReferenceException نجات میده و بهتون دید عمیقی نسبت به مدیریت حافظه میده.
مهمترین نکتهای که امروز در مورد آرایهها یاد گرفتید چی بود؟ آیا تفاوت رفتار آرایهی struct و class یا اینکه خود آرایه یک Reference Type هست، براتون جدید بود؟
خب، اینجا که نمیشه همه حرفا رو زد! 😉
ادامهی بحث، سوالات، غر زدنها و گپ و گفتهای خودمونی، فقط تو گروه.
[پاتوق گیک های #C]
🔖 هشتگها :
#CSharp
#Array
🏗 آرایههای چندبعدی:
گاهی وقتا یه لیست ساده یا آرایه یکبعدی جواب کار ما رو نمیده. وقتی با دادههای جدولی، ماتریسها، یا هر ساختار شبکهای سر و کار داریم، باید بریم سراغ آرایههای چندبعدی.
فکر کنید این نوع آرایه مثل یه جدول اکسل یا یه صفحه شطرنجه. یه شبکه کاملاً منظم که تعداد سطر و ستونش از اول مشخص و ثابته. تمام عناصر در یک بلوک حافظه یکپارچه قرار میگیرن.
نحوه تعریف: ابعاد مختلف رو با کاما (,) از هم جدا میکنیم.
پیمایش: برای گرفتن طول هر بعد، از متد GetLength(dimension) استفاده میکنیم.
2️⃣آرایههای دندانهدار ([][]):
آرایهای از آرایهها ⛓️
این یکی فرق داره. به جای یه شبکه منظم، شما یه آرایه دارید که هر خونهش، خودش میتونه یه آرایه دیگه باشه! مثل یه قفسه کتابه که هر طبقهش میتونه تعداد متفاوتی کتاب جا بده.
نحوه تعریف: برای هر بعد، از یک جفت [] جداگانه استفاده میکنیم.
انعطافپذیری: هر آرایه داخلی میتونه طول متفاوتی داشته باشه.
تله و نکته حیاتی: ⚠️
وقتی یه آرایه دندانهدار میسازید، فقط آرایه بیرونی ساخته میشه و تمام خونههای اون null هستن! شما باید خودتون هر آرایه داخلی رو به صورت جداگانه با new بسازید!
🤔 حرف حساب و انتخاب شما
برای دادههای جدولی و ثابت (مثل تصویر یا ماتریس) ⟵ آرایه مستطیلی ([,])
برای دادههایی که هر ردیفشون طول متفاوتی داره ⟵ آرایه دندانهدار ([][])
شما تو پروژههاتون بیشتر با کدوم نوع آرایه چندبعدی سر و کار داشتید؟ یا سناریوی جالبی دارید که توش یکی از این دو نوع به وضوح برتری داشته؟
نظراتتون رو کامنت کنید! 👇
[@CSharpGeeksChat]
مستطیلی ([,]) در برابر دندانهدار ([][])
گاهی وقتا یه لیست ساده یا آرایه یکبعدی جواب کار ما رو نمیده. وقتی با دادههای جدولی، ماتریسها، یا هر ساختار شبکهای سر و کار داریم، باید بریم سراغ آرایههای چندبعدی.
تو #C دو نوع اصلی داریم:1️⃣آرایههای مستطیلی ([,]): یک شبکه منظم ▦
مستطیلی (Rectangular) و دندانهدار (Jagged). بیاید ببینیم فرقشون چیه و کی باید از کدوم استفاده کرد.
فکر کنید این نوع آرایه مثل یه جدول اکسل یا یه صفحه شطرنجه. یه شبکه کاملاً منظم که تعداد سطر و ستونش از اول مشخص و ثابته. تمام عناصر در یک بلوک حافظه یکپارچه قرار میگیرن.
نحوه تعریف: ابعاد مختلف رو با کاما (,) از هم جدا میکنیم.
پیمایش: برای گرفتن طول هر بعد، از متد GetLength(dimension) استفاده میکنیم.
// تعریف یک ماتریس ۳ در ۳
int[,] matrix = new int[3, 3];
// پیمایش و مقداردهی
for (int i = 0; i < matrix.GetLength(0); i++) // طول بعد اول (سطرها)
{
for (int j = 0; j < matrix.GetLength(1); j++) // طول بعد دوم (ستونها)
{
matrix[i, j] = i * 3 + j;
}
}
// مقداردهی اولیه سریع
int[,] matrix2 =
{
{0, 1, 2},
{3, 4, 5},
{6, 7, 8}
};
2️⃣آرایههای دندانهدار ([][]):
آرایهای از آرایهها ⛓️
این یکی فرق داره. به جای یه شبکه منظم، شما یه آرایه دارید که هر خونهش، خودش میتونه یه آرایه دیگه باشه! مثل یه قفسه کتابه که هر طبقهش میتونه تعداد متفاوتی کتاب جا بده.
نحوه تعریف: برای هر بعد، از یک جفت [] جداگانه استفاده میکنیم.
انعطافپذیری: هر آرایه داخلی میتونه طول متفاوتی داشته باشه.
تله و نکته حیاتی: ⚠️
وقتی یه آرایه دندانهدار میسازید، فقط آرایه بیرونی ساخته میشه و تمام خونههای اون null هستن! شما باید خودتون هر آرایه داخلی رو به صورت جداگانه با new بسازید!
// تعریف یک آرایه بیرونی با ظرفیت ۳ (که هر ۳ خانهاش null است)
int[][] jaggedMatrix = new int[3][];
// حالا باید هر آرایه داخلی را جداگانه بسازیم
for (int i = 0; i < jaggedMatrix.Length; i++)
{
jaggedMatrix[i] = new int[i + 3]; // هر ردیف طولی متفاوت دارد
for (int j = 0; j < jaggedMatrix[i].Length; j++)
{
jaggedMatrix[i][j] = i + j;
}
}
🤔 حرف حساب و انتخاب شما
برای دادههای جدولی و ثابت (مثل تصویر یا ماتریس) ⟵ آرایه مستطیلی ([,])
برای دادههایی که هر ردیفشون طول متفاوتی داره ⟵ آرایه دندانهدار ([][])
شما تو پروژههاتون بیشتر با کدوم نوع آرایه چندبعدی سر و کار داشتید؟ یا سناریوی جالبی دارید که توش یکی از این دو نوع به وضوح برتری داشته؟
نظراتتون رو کامنت کنید! 👇
[@CSharpGeeksChat]
🔖 هشتگها :
#CSharp
#Array
✨ خلاصهنویسی آرایهها در #C :
کدنویسی فقط کار کردن کد نیست؛ زیبا، خوانا و مدرن بودنش هم مهمه. تو #C راههای زیادی برای خلاصهنویسی و تمیزتر کردن کد وجود داره.
امروز میخوایم چند تا از بهترین تکنیکها برای تعریف و مقداردهی آرایهها رو یاد بگیریم که کد شما رو یه لول بالاتر میبره.
1️⃣روش اول:
وقتی میخواید یه آرایه رو همزمان با تعریفش مقداردهی کنید، نیازی نیست همیشه از new و نوع داده کامل استفاده کنید. میتونید اونها رو حذف کنید تا کدتون خلاصهتر بشه:
2️⃣ورود var:
کلمه کلیدی var به کامپایلر میگه: "من نوع این متغیر رو نمینویسم، خودت از روی مقداری که بهش میدم، نوعش رو تشخیص بده!".
این قابلیت کد رو خیلی کوتاهتر میکنه و برخلاف جاوااسکریپت، متغیر شما هنوز هم Strongly-Typed هست؛ یعنی نوعش در زمان کامپایل مشخص و ثابت میشه.
3️⃣ترکیب طلایی:
حالا میتونیم این دو قابلیت رو با هم ترکیب کنیم تا به مدرنترین و تمیزترین روش برای تعریف آرایه برسیم.
وقتی از var استفاده میکنید، باید کلمه new رو بیارید، ولی دیگه لازم نیست نوع آرایه رو مشخص کنید. کامپایلر خودش از روی عناصر داخل آکولاد، نوع آرایه رو تشخیص میده.
نکته حرفهای: اگه عناصر آرایه از چند نوع عددی مختلف باشن، کامپایلر سعی میکنه "بهترین نوعی" که همه عناصر بهش قابل تبدیل هستن رو پیدا کنه:
استفاده از var و این تکنیکهای خلاصهنویسی، کد شما رو مدرنتر و خواناتر میکنه، هرچند بعضی برنامهنویسها هنوز ترجیح میدن نوع رو به صورت صریح بنویسن تا خوانایی در نگاه اول بیشتر باشه.
شما تو کدهاتون چقدر از var استفاده میکنید؟ آیا طرفدارش هستید یا ترجیح میدید همیشه نوع متغیر رو به صورت صریح مشخص کنید؟ دلیلش چیه؟
نظراتتون رو کامنت کنید! 👇
[@CSharpGeeksChat]
تکنیکهای var و مقداردهی اولیه
کدنویسی فقط کار کردن کد نیست؛ زیبا، خوانا و مدرن بودنش هم مهمه. تو #C راههای زیادی برای خلاصهنویسی و تمیزتر کردن کد وجود داره.
امروز میخوایم چند تا از بهترین تکنیکها برای تعریف و مقداردهی آرایهها رو یاد بگیریم که کد شما رو یه لول بالاتر میبره.
1️⃣روش اول:
حذف اضافات در مقداردهی اولیه ✂️
وقتی میخواید یه آرایه رو همزمان با تعریفش مقداردهی کنید، نیازی نیست همیشه از new و نوع داده کامل استفاده کنید. میتونید اونها رو حذف کنید تا کدتون خلاصهتر بشه:
// به جای: new char[] {'a','e','i','o','u'}
char[] vowels = {'a','e','i','o','u'};
// این روش برای آرایههای چندبعدی هم کار میکنه
int[,] rectangularMatrix =
{
{0,1,2},
{3,4,5},
{6,7,8}
};2️⃣ورود var:
قدرت استنتاج نوع (Type Inference) 🤖
کلمه کلیدی var به کامپایلر میگه: "من نوع این متغیر رو نمینویسم، خودت از روی مقداری که بهش میدم، نوعش رو تشخیص بده!".
این قابلیت کد رو خیلی کوتاهتر میکنه و برخلاف جاوااسکریپت، متغیر شما هنوز هم Strongly-Typed هست؛ یعنی نوعش در زمان کامپایل مشخص و ثابت میشه.
// کامپایلر میفهمه که i از نوع int هست
var i = 10;
// کامپایلر میفهمه که name از نوع string هست
var name = "#C Geeks";
3️⃣ترکیب طلایی:
var + مقداردهی اولیه آرایه 🏆
حالا میتونیم این دو قابلیت رو با هم ترکیب کنیم تا به مدرنترین و تمیزترین روش برای تعریف آرایه برسیم.
وقتی از var استفاده میکنید، باید کلمه new رو بیارید، ولی دیگه لازم نیست نوع آرایه رو مشخص کنید. کامپایلر خودش از روی عناصر داخل آکولاد، نوع آرایه رو تشخیص میده.
// کامپایلر از روی عناصر، نوع char[] رو استنتاج میکنه
var vowels = new[] {'a','e','i','o','u'};
// برای آرایههای چندبعدی هم به همین شکل
var matrix = new[,]
{
{0,1,2},
{3,4,5}
};
نکته حرفهای: اگه عناصر آرایه از چند نوع عددی مختلف باشن، کامپایلر سعی میکنه "بهترین نوعی" که همه عناصر بهش قابل تبدیل هستن رو پیدا کنه:
// اینجا ۱ یک int و دومی یک long است.
// کامپایلر نوع long[] رو برای کل آرایه انتخاب میکنه
var numbers = new[] {1, 10_000_000_000L};
🤔 حرف حساب و استایل شما
استفاده از var و این تکنیکهای خلاصهنویسی، کد شما رو مدرنتر و خواناتر میکنه، هرچند بعضی برنامهنویسها هنوز ترجیح میدن نوع رو به صورت صریح بنویسن تا خوانایی در نگاه اول بیشتر باشه.
شما تو کدهاتون چقدر از var استفاده میکنید؟ آیا طرفدارش هستید یا ترجیح میدید همیشه نوع متغیر رو به صورت صریح مشخص کنید؟ دلیلش چیه؟
نظراتتون رو کامنت کنید! 👇
[@CSharpGeeksChat]
🔖 هشتگها :
#CSharp
#Var
#CodingTips
#CleanCode
🚧 خطای IndexOutOfRangeException :
چرا آرایهها از لبه پرت میشوند؟
یکی از اولین و رایجترین خطاهای زمان اجرایی که هر برنامهنویس #C باهاش روبرو میشه، همین IndexOutOfRangeException هست. اون لحظهای که فکر میکنی همه چی درسته، ولی برنامه با این خطا کرش میکنه.
اما این خطا دشمن شما نیست، بلکه یه نگهبان مهمه! بیاید ببینیم داستانش چیه و چرا وجودش برای ما خوبه.
وقتی شما یک آرایه میسازید، در واقع یک محدوده مشخص در حافظه تعریف میکنید. Bounds Checking یعنی CLR (محیط اجرای داتنت)، قبل از دسترسی به هر خانهی آرایه، مثل یه نگهبان با دقت چک میکنه که آیا ایندکسی که دادی، تو محدوده مجاز آرایه هست یا نه.
اگه از محدوده خارج بزنید، اون نگهبان جلوتون رو میگیره و خطای IndexOutOfRangeException رو پرتاب میکنه.
شاید اولش از این خطا بدتون بیاد، ولی وجودش دو تا دلیل خیلی مهم داره:
این بررسی جلوی دسترسی و تخریب ناخواسته بخشهای دیگه حافظه رو میگیره. اگه این خطا نبود، ممکن بود برنامهتون دادههای دیگه رو خراب کنه و هیچوقت نفهمید مشکل از کجاست.
به جای اینکه برنامهتون با یه رفتار عجیب و غریب به کارش ادامه بده و بعداً یه جای نامربوط کرش کنه، دقیقاً همون لحظه و همون خطی که اشتباه کردی، بهت خطا میده. این دیباگ کردن رو فوقالعاده راحتتر میکنه.
شاید فکر کنید این همه چک کردن، برنامه رو کند میکنه. درسته که یه هزینه خیلی جزئی داره، ولی کامپایلر JIT (Just-In-Time) خیلی هوشمنده.
مثلاً، قبل از ورود به یه حلقه، اگه بفهمه تمام ایندکسها امن هستن، چک کردن رو برای کل حلقه بهینه میکنه. پس در ۹۹٪ مواقع، تأثیر این بررسی روی پرفورمنس ناچیز و قابل چشمپوشی است.
پس دفعه بعدی که این خطا رو دیدید، بهش به چشم یه دشمن نگاه نکنید، بلکه ازش به عنوان یه راهنما که داره اشتباهتون رو دقیق نشون میده، تشکر کنید!
اولین باری که به این خطا برخوردید یادتونه؟ داشتید روی چه کدی کار میکردید؟ بیاید از خاطرات مشترک برنامهنویسیمون بگیم!
ادامهی بحث، سوالات، غر زدنها و گپ و گفتهای خودمونی، فقط تو گروه.
[@CSharpGeeksChat]
چرا آرایهها از لبه پرت میشوند؟
یکی از اولین و رایجترین خطاهای زمان اجرایی که هر برنامهنویس #C باهاش روبرو میشه، همین IndexOutOfRangeException هست. اون لحظهای که فکر میکنی همه چی درسته، ولی برنامه با این خطا کرش میکنه.
اما این خطا دشمن شما نیست، بلکه یه نگهبان مهمه! بیاید ببینیم داستانش چیه و چرا وجودش برای ما خوبه.
1️⃣ بررسی محدوده (Bounds Checking) چیست؟
وقتی شما یک آرایه میسازید، در واقع یک محدوده مشخص در حافظه تعریف میکنید. Bounds Checking یعنی CLR (محیط اجرای داتنت)، قبل از دسترسی به هر خانهی آرایه، مثل یه نگهبان با دقت چک میکنه که آیا ایندکسی که دادی، تو محدوده مجاز آرایه هست یا نه.
اگه از محدوده خارج بزنید، اون نگهبان جلوتون رو میگیره و خطای IndexOutOfRangeException رو پرتاب میکنه.
// آرایهای با ۳ عنصر که ایندکسهای مجاز آن 0, 1, 2 هستند
int[] arr = new int[3];
arr[0] = 10; // ✅ مجاز
arr[2] = 30; // ✅ مجاز
// تلاش برای دسترسی به ایندکس 3 که وجود نداره!
arr[3] = 40; // 💥 BOOM! IndexOutOfRangeException
2️⃣چرا این خطا یک "ویژگی" خوب است؟ ✅
شاید اولش از این خطا بدتون بیاد، ولی وجودش دو تا دلیل خیلی مهم داره:
امنیت و پایداری (Safety) :
این بررسی جلوی دسترسی و تخریب ناخواسته بخشهای دیگه حافظه رو میگیره. اگه این خطا نبود، ممکن بود برنامهتون دادههای دیگه رو خراب کنه و هیچوقت نفهمید مشکل از کجاست.
دیباگ آسان :
به جای اینکه برنامهتون با یه رفتار عجیب و غریب به کارش ادامه بده و بعداً یه جای نامربوط کرش کنه، دقیقاً همون لحظه و همون خطی که اشتباه کردی، بهت خطا میده. این دیباگ کردن رو فوقالعاده راحتتر میکنه.
3️⃣آیا این بررسی روی پرفورمنس تأثیر دارد؟ 🚀
شاید فکر کنید این همه چک کردن، برنامه رو کند میکنه. درسته که یه هزینه خیلی جزئی داره، ولی کامپایلر JIT (Just-In-Time) خیلی هوشمنده.
مثلاً، قبل از ورود به یه حلقه، اگه بفهمه تمام ایندکسها امن هستن، چک کردن رو برای کل حلقه بهینه میکنه. پس در ۹۹٪ مواقع، تأثیر این بررسی روی پرفورمنس ناچیز و قابل چشمپوشی است.
نکته حرفهای: البته #C به شما اجازه میده در شرایط خاص و با مسئولیت خودتان، با استفاده از کد unsafe این بررسی امنیتی رو دور بزنید، که یه بحث خیلی پیشرفتهست.🤔 حرف حساب و خاطرات شما
پس دفعه بعدی که این خطا رو دیدید، بهش به چشم یه دشمن نگاه نکنید، بلکه ازش به عنوان یه راهنما که داره اشتباهتون رو دقیق نشون میده، تشکر کنید!
اولین باری که به این خطا برخوردید یادتونه؟ داشتید روی چه کدی کار میکردید؟ بیاید از خاطرات مشترک برنامهنویسیمون بگیم!
ادامهی بحث، سوالات، غر زدنها و گپ و گفتهای خودمونی، فقط تو گروه.
[@CSharpGeeksChat]
🔖 هشتگها :
#CSharp
#ErrorHandling
#DotNet
#Exception
🧠 کالبدشکافی حافظه در C# Stack در برابر Heap
وقتی یه متغیر تعریف میکنید، دقیقاً کجا میره؟ چرا بعضی متغیرها بلافاصله بعد از خروج از متد از بین میرن ولی بعضی دیگه باقی میمونن؟
جواب این سوالها تو دو تا از مهمترین مفاهیم کامپیوتر نهفتهست: Stack (پشته) و Heap (هیپ). درک این دو، شما رو از یه کدنویس ساده به یه توسعهدهنده واقعی تبدیل میکنه.
1️⃣پشته (Stack) :
حافظهی سریع و منظم فکر کنید Stack مثل یه دسته بشقابه که روی هم چیدید. آخرین بشقابی که میذارید، اولین بشقابیه که برمیدارید (LIFO: Last-In, First-Out).
چی توش ذخیره میشه؟ این بخش از حافظه برای ذخیرهی متغیرهای محلی و پارامترهای متدها استفاده میشه. بیشتر Value Type ها اینجا زندگی میکنن.
چطور کار میکنه؟ با هر بار صدا زدن یک متد، حافظه مورد نیاز اون متد بالای پشته قرار میگیره و با تمام شدن متد، اون بخش از حافظه بلافاصله آزاد میشه. این کار فوقالعاده سریعه.
static int Factorial (int x)
{
if (x == 0) return 1;
return x * Factorial (x - 1);
}
هر بار که Factorial خودشو صدا میزنه، یه int x جدید روی پشته ساخته میشه و با برگشتن جواب، اون int از روی پشته برداشته میشه.
2️⃣هیپ (Heap) :
هیپ مثل یه انبار بزرگه. هر وقت یه آبجکت جدید میسازید (new)، CLR یه جای خالی تو این انبار پیدا میکنه، آبجکت رو اونجا میذاره و یه "آدرس" یا "رفرنس" به شما برمیگردونه.
چی توش ذخیره میشه؟ تمام آبجکتها (نمونههای Reference Type) اینجا زندگی میکنن.
چطور کار میکنه؟ این حافظه توسط یه رفتگر هوشمند به اسم Garbage Collector (GC) مدیریت میشه. GC هر چند وقت یکبار میاد و آبجکتهایی که دیگه هیچ رفرنسی بهشون اشاره نمیکنه (آبجکتهای یتیم) رو از حافظه پاک میکنه تا فضا آزاد بشه.
StringBuilder ref1 = new StringBuilder("obj1");
Console.WriteLine(ref1);
// بعد از این خط، دیگه هیچ رفرنسی به آبجکت "obj1" اشاره نمیکنه
// و آماده پاک شدن توسط GC میشه.
StringBuilder ref2 = new StringBuilder("obj2");
StringBuilder ref3 = ref2;
// اینجا با اینکه ref2 دیگه استفاده نمیشه، چون ref3 هنوز به "obj2"
// اشاره میکنه، این آبجکت زنده میمونه!
Console.WriteLine(ref3);نکته: فیلدهای استاتیک هم روی هیپ ذخیره میشن، ولی تا پایان عمر برنامه زنده میمونن.
🤔 پس Value Typeها دقیقاً کجا زندگی میکنند؟
این سوال مهمیه! قانونش اینه: Value Typeها هرجا که تعریف بشن، همونجا زندگی میکنن.
اگه به عنوان یه متغیر محلی تو یه متد تعریف بشن ⟵ روی Stack.
اگه به عنوان یه فیلد داخل یه کلاس (که خودش یه Reference Type هست) تعریف بشن ⟵ همراه با اون آبجکت روی Heap!
حرف حساب و درک عمیقتر
درک تفاوت Stack و Heap فقط یه بحث تئوری نیست؛ روی پرفورمنس، مدیریت حافظه و حتی نحوه طراحی کلاسهای شما تأثیر مستقیم داره.
این توضیح چقدر به درک بهتر شما از رفتار Value Type و Reference Type کمک کرد؟ آیا نکتهای در مورد Stack و Heap بود که براتون تازگی داشته باشه؟
نظراتتون رو کامنت کنید! 👇
[پاتوق گیک های #C]
🔖 هشتگها :
#CSharp
#MemoryManagement
#DotNet #Stack #Heap
🛡 سیاست تخصیص قطعی در #C: چرا کامپایلر از شما باهوشتر است؟
تا حالا براتون سوال شده چرا گاهی اوقات اگه به یه متغیر مقدار اولیه ندید، کامپایلر ازتون ایراد میگیره، ولی گاهی اوقات خودش بهش مقدار صفر یا null میده؟
این رفتار شانسی نیست! پشتش یه قانون مهم و امنیتی در #C به اسم "سیاست تخصیص قطعی" (Definite Assignment) خوابیده. این قانون میگه شما هرگز نمیتونید به حافظهای که مقداردهی اولیه نشده، دسترسی داشته باشید.
1️⃣قانون برای متغیرهای محلی: خودت باید مقدار بدی!
وقتی یه متغیر داخل یک متد تعریف میکنی (Local Variable)، #C به تو اعتماد میکنه و وظیفه مقداردهی اولیه رو به عهده خودت میذاره. اگر قبل از اینکه مقداری بهش بدی، سعی کنی ازش استفاده کنی (بخونیش)، کامپایلر جلوت رو میگیره و خطای زمان کامپایل (Compile-time error) میده.
2️⃣قانون برای فیلدها و آرایهها: همیشه مقدار دارند! ✨
اما برای اعضایی که طول عمر بیشتری دارن، مثل فیلدهای یک کلاس (چه static و چه instance) یا عناصر یک آرایه، داستان فرق میکنه.
اینجا NET runtime. برای جلوگیری از مشکلات، همیشه اونها رو به صورت خودکار با مقدار پیشفرضِ نوع دادهشون مقداردهی میکنه.
🤔 چرا این تفاوت وجود دارد؟
این یه تصمیم هوشمندانه برای امنیته. برای متغیرهای محلی، کامپایلر میتونه به سادگی و با قاطعیت تشخیص بده که شما فراموش کردید مقداردهی کنید و همونجا جلوتون رو بگیره. اما برای فیلدها و آرایهها که چرخه حیاتشون پیچیدهتره، مقداردهی خودکار توسط runtime، جلوی باگهای خطرناک ناشی از حافظهی مقداردهی نشده رو میگیره.
پس این "ایراد گرفتن" کامپایلر، در واقع یه کمک بزرگه!
نظراتتون رو کامنت کنید! 👇
[C# Geeks Hangout]
تا حالا براتون سوال شده چرا گاهی اوقات اگه به یه متغیر مقدار اولیه ندید، کامپایلر ازتون ایراد میگیره، ولی گاهی اوقات خودش بهش مقدار صفر یا null میده؟
این رفتار شانسی نیست! پشتش یه قانون مهم و امنیتی در #C به اسم "سیاست تخصیص قطعی" (Definite Assignment) خوابیده. این قانون میگه شما هرگز نمیتونید به حافظهای که مقداردهی اولیه نشده، دسترسی داشته باشید.
1️⃣قانون برای متغیرهای محلی: خودت باید مقدار بدی!
وقتی یه متغیر داخل یک متد تعریف میکنی (Local Variable)، #C به تو اعتماد میکنه و وظیفه مقداردهی اولیه رو به عهده خودت میذاره. اگر قبل از اینکه مقداری بهش بدی، سعی کنی ازش استفاده کنی (بخونیش)، کامپایلر جلوت رو میگیره و خطای زمان کامپایل (Compile-time error) میده.
void MyMethod()
{
int x;
// ❌ خطای زمان کامپایل!
// کامپایلر میگه: "تو به من نگفتی تو x چی بریزم!"
Console.WriteLine(x);
}
2️⃣قانون برای فیلدها و آرایهها: همیشه مقدار دارند! ✨
اما برای اعضایی که طول عمر بیشتری دارن، مثل فیلدهای یک کلاس (چه static و چه instance) یا عناصر یک آرایه، داستان فرق میکنه.
اینجا NET runtime. برای جلوگیری از مشکلات، همیشه اونها رو به صورت خودکار با مقدار پیشفرضِ نوع دادهشون مقداردهی میکنه.
// آرایهها همیشه با مقادیر پیشفرض پر میشن (برای int، صفره)
int[] numbers = new int[3];
Console.WriteLine(numbers[0]); // خروجی: 0
class Test
{
// فیلدها هم همیشه مقدار پیشفرض میگیرن
public static int X;
}
// ...
Console.WriteLine(Test.X); // خروجی: 0
🤔 چرا این تفاوت وجود دارد؟
این یه تصمیم هوشمندانه برای امنیته. برای متغیرهای محلی، کامپایلر میتونه به سادگی و با قاطعیت تشخیص بده که شما فراموش کردید مقداردهی کنید و همونجا جلوتون رو بگیره. اما برای فیلدها و آرایهها که چرخه حیاتشون پیچیدهتره، مقداردهی خودکار توسط runtime، جلوی باگهای خطرناک ناشی از حافظهی مقداردهی نشده رو میگیره.
پس این "ایراد گرفتن" کامپایلر، در واقع یه کمک بزرگه!
نظراتتون رو کامنت کنید! 👇
[C# Geeks Hangout]
🔖 هشتگها :
#CSharp
#Compiler
#DotNet #SoftwareEngineering
🐣 مقادیر پیشفرض (Default Values) در #C :
هر متغیر با چه مقداری متولد میشود؟
تو پست "سیاست تخصیص قطعی" دیدیم که فیلدهای یک کلاس و عناصر یک آرایه به صورت خودکار مقداردهی اولیه میشن. اما سوال اینجاست: چه مقداری؟ این مقادیر از کجا میان؟
جواب تو یه مفهوم ساده به اسم مقدار پیشفرض (Default Value) هست.
هر نوع دادهای در #C یک مقدار پیشفرض داره که حاصل صفر کردن بیتهای حافظه (Bitwise Zeroing) هست. این یعنی:
●تمام Reference Typeها (کلاس، string، آرایه، ...): مقدار پیشفرضشون null هست.
●تمام Numeric Typeها (int, double, decimal, ...): مقدار پیشفرضشون 0 هست.
●نوع bool: مقدار پیشفرصش false هست.
●نوع char: مقدار پیشفرصش '\0' (کاراکتر نال) هست.
سیشارپ یه کلمه کلیدی شیک و کاربردی به اسم default در اختیار ما میذاره تا بتونیم مقدار پیشفرض هر نوعی رو به راحتی بدست بیاریم، بدون اینکه نیازی به حفظ کردنشون داشته باشیم.
مقدار پیشفرض برای یک struct سفارشی، خیلی سادهست: معادل مقدار پیشفرض تمام فیلدهای داخل اون struct هست.
دونستن مقادیر پیشفرض به شما کمک میکنه رفتار اولیه کدهاتون رو بهتر درک کنید و از خطاهای ناشی از مقادیر ناخواسته جلوگیری کنید.
آیا تا حالا از کلمه کلیدی default تو کدهاتون استفاده کردید؟ یا جایی بوده که مقداردهی پیشفرض یه فیلد یا آرایه شما رو غافلگیر کرده باشه؟
نظراتتون رو کامنت کنید! 👇
[پاتوق گیک های #C]
هر متغیر با چه مقداری متولد میشود؟
تو پست "سیاست تخصیص قطعی" دیدیم که فیلدهای یک کلاس و عناصر یک آرایه به صورت خودکار مقداردهی اولیه میشن. اما سوال اینجاست: چه مقداری؟ این مقادیر از کجا میان؟
جواب تو یه مفهوم ساده به اسم مقدار پیشفرض (Default Value) هست.
مقادیر پیشفرض چیست؟ 🔢
هر نوع دادهای در #C یک مقدار پیشفرض داره که حاصل صفر کردن بیتهای حافظه (Bitwise Zeroing) هست. این یعنی:
●تمام Reference Typeها (کلاس، string، آرایه، ...): مقدار پیشفرضشون null هست.
●تمام Numeric Typeها (int, double, decimal, ...): مقدار پیشفرضشون 0 هست.
●نوع bool: مقدار پیشفرصش false هست.
●نوع char: مقدار پیشفرصش '\0' (کاراکتر نال) هست.
کلمه کلیدی default ✨
سیشارپ یه کلمه کلیدی شیک و کاربردی به اسم default در اختیار ما میذاره تا بتونیم مقدار پیشفرض هر نوعی رو به راحتی بدست بیاریم، بدون اینکه نیازی به حفظ کردنشون داشته باشیم.
// گرفتن مقدار پیشفرض decimal
decimal d1 = default(decimal); // حاصل: 0
// روش مدرنتر و خلاصهتر
// کامپایلر خودش نوع رو از متغیر تشخیص میده
int i = default; // حاصل: 0
bool b = default; // حاصل: false
string s = default; // حاصل: null
تکلیف structها چیست؟ 🏗
مقدار پیشفرض برای یک struct سفارشی، خیلی سادهست: معادل مقدار پیشفرض تمام فیلدهای داخل اون struct هست.
public struct Point
{
public int X; // پیشفرض: 0
public string Name; // پیشفرض: null
}
Point p = default;
// اینجا p.X برابر 0 و p.Name برابر null خواهد بود
Console.WriteLine($"X: {p.X}, Name: {p.Name ?? "null"}");
🤔 حرف حساب و تجربه شما
دونستن مقادیر پیشفرض به شما کمک میکنه رفتار اولیه کدهاتون رو بهتر درک کنید و از خطاهای ناشی از مقادیر ناخواسته جلوگیری کنید.
آیا تا حالا از کلمه کلیدی default تو کدهاتون استفاده کردید؟ یا جایی بوده که مقداردهی پیشفرض یه فیلد یا آرایه شما رو غافلگیر کرده باشه؟
نظراتتون رو کامنت کنید! 👇
[پاتوق گیک های #C]
🔖 هشتگها :
#CSharp
#CodingTips
#SoftwareEngineering