🔪 تله اول: تقسیم صحیح و سلاخی اعشار!
وقتی دو عدد صحیح رو بر هم تقسیم میکنید، سی شارپ بخش اعشاری حاصل رو گرد نمیکنه، بلکه به سادگی حذف (Truncate) میکنه.
این یعنی نتیجهی تقسیم، همیشه یه عدد صحیحه.
int a = 5;
int b = 2;
// انتظار داریم حاصل 2.5 باشه، ولی اعشار حذف میشه!
int result = a / b;
Console.WriteLine(result); // خروجی: 2
نکته حرفهای: تقسیم بر متغیر صفر
int x=0;
5/x;
باعث خطای زمان اجرا(DivideByZeroException) میشه، ولی تقسیم بر خودِ صفر لیترال ;5/0 باعث خطای زمان کامپایل میشه!
👻 تله دوم (و خطرناکترین) :
سرریز شدن سایلنت (Silent Overflow)
این یکی کابوس برنامهنویسهاست! تصور کنید کیلومترشمار یه ماشین قدیمی بعد از اینکه به 999999 میرسه، دوباره صفر میشه. اعداد صحیح هم به صورت پیشفرض دقیقاً همین رفتار رو دارن!
اگه یه عملیات حسابی از حداکثر ظرفیت یه نوع داده بیشتر بشه، هیچ خطایی رخ نمیده! نتیجه به صورت سایلنت به ابتدای محدوده برمیگرده (Wraparound).
int i = int.MaxValue; // بزرگترین عدد ممکن برای int (حدود ۲ میلیارد)
i = i + 1; // یک واحد بهش اضافه میکنیم
// انتظار داریم خطا بده یا یه عدد بزرگتر بشه، ولی...
// عدد از ماکسیمم به مینیمم جهش میکنه!
Console.WriteLine(i == int.MinValue); // خروجی: True!
این رفتار سایلنت میتونه باعث ایجاد باگهای منطقی بسیار خطرناک و غیرقابل ردیابی در محاسبات مالی، علمی و امنیتی بشه.
🛡 راه نجات :
خوشبختانه، #C راه حل این فاجعه سایلنت رو در اختیار ما گذاشته.
هر عبارتی که داخل checked قرار بگیره، در صورت سرریز شدن، به جای رفتار سایلنت، یک خطای واضح و مشخص به نام OverflowException پرتاب میکنه.
نکته حرفهای: میتونید این قابلیت رو در تنظیمات پروژه برای کل برنامه فعال کنید و هرجا که خواستید رفتار سایلنت رو داشته باشید، از unchecked استفاده کنید.
و اما unchecked: وقتی میخوایم خطر کنیم!
🤔 حرف حساب و تجربه شما
شناختن این رفتارها و استفاده از checked، مرز بین کدنویسی آماتور و حرفهایه.
فرشته نگهبان به نام checked
خوشبختانه، #C راه حل این فاجعه سایلنت رو در اختیار ما گذاشته.
اپراتور checked.
هر عبارتی که داخل checked قرار بگیره، در صورت سرریز شدن، به جای رفتار سایلنت، یک خطای واضح و مشخص به نام OverflowException پرتاب میکنه.
int a = 1_000_000;
int b = 1_000_000;
try
{
// این عبارت رو برای سرریز شدن چک کن
// حاصل ضرب از ظرفیت int بیشتر میشه
int c = checked(a * b); // اینجا استثنا (Exception) رخ میده!
Console.WriteLine(c);
}
catch (OverflowException)
{
Console.WriteLine("Overflow detected! Calculation aborted.");
}
نکته حرفهای: میتونید این قابلیت رو در تنظیمات پروژه برای کل برنامه فعال کنید و هرجا که خواستید رفتار سایلنت رو داشته باشید، از unchecked استفاده کنید.
و اما unchecked: وقتی میخوایم خطر کنیم!
int x = int.MaxValue;
// این عملیات چک نمیشه و به حالت سایلنت برمیگرده
int y = unchecked(x + 1);
unchecked
{
// تمام عبارات این بلوک هم چک نمیشن
int z = x + 1;
}
🤔 حرف حساب و تجربه شما
شناختن این رفتارها و استفاده از checked، مرز بین کدنویسی آماتور و حرفهایه.
🔖 هشتگها :
#CSharp
#DotNet
#Bugs
💡 نکته سریع #C :
یکی از سوالای کلاسیک و در عین حال فنی تو مصاحبههای سیشارپ، تفاوت بین ++x (حالت Postfix) و x++ (حالت Prefix) هست.
هر دوتاشون در نهایت مقدار x رو یکی زیاد میکنن، ولی "زمان" این افزایش، زمین تا آسمون با هم فرق میکنه و همین میتونه رفتار کد شما رو کاملاً عوض کنه.
در این حالت، اول مقدار فعلی و قدیمی x در عبارت استفاده میشه و بعد از اینکه اون خط تموم شد، مقدار x یکی زیاد میشه.
فکر کنید x یه چک بانکیه. شما اول چک رو با همون مبلغ فعلی به طرف میدین (مقدار رو برمیگردونین)، بعد که کارتون تموم شد، تو حساب خودتون یکی بهش اضافه میکنید.
مثال:
اینجا برعکسه. اول مقدار x یکی زیاد میشه و بعد، مقدار جدید و افزایشیافته در عبارت استفاده میشه.
اینجا شما اول تو حساب خودتون یکی به مبلغ چک اضافه میکنید، بعد چک جدید رو با مبلغ افزایشیافته به طرف میدین (مقدار جدید رو برمیگردونین).
مثال:
حرف آخر:
اول مقدار فعلی رو برمیگردونه، بعد x رو آپدیت میکنه.
اول x رو آپدیت میکنه، بعد مقدار جدید رو برمیگردونه.
دونستن همین نکته کوچیک، به خصوص در حلقهها و محاسبات پیچیده، خیلی به کار میاد و جلوی باگهای منطقی رو میگیره!
تفاوت ++x و x++ چیه؟
یکی از سوالای کلاسیک و در عین حال فنی تو مصاحبههای سیشارپ، تفاوت بین ++x (حالت Postfix) و x++ (حالت Prefix) هست.
هر دوتاشون در نهایت مقدار x رو یکی زیاد میکنن، ولی "زمان" این افزایش، زمین تا آسمون با هم فرق میکنه و همین میتونه رفتار کد شما رو کاملاً عوض کنه.
حالت Postfix (اول بده، بعداً زیاد کن!):++x
در این حالت، اول مقدار فعلی و قدیمی x در عبارت استفاده میشه و بعد از اینکه اون خط تموم شد، مقدار x یکی زیاد میشه.
فکر کنید x یه چک بانکیه. شما اول چک رو با همون مبلغ فعلی به طرف میدین (مقدار رو برمیگردونین)، بعد که کارتون تموم شد، تو حساب خودتون یکی بهش اضافه میکنید.
مثال:
int x = 5;
// اینجا اول مقدار فعلی x (یعنی 5) چاپ میشه
Console.WriteLine(x++); // خروجی: 5
// حالا که خط بالا تموم شد، x یکی زیاد شده
Console.WriteLine(x); // خروجی: 6
حالت Prefix (اول زیاد کن، بعداً بده!): x++
اینجا برعکسه. اول مقدار x یکی زیاد میشه و بعد، مقدار جدید و افزایشیافته در عبارت استفاده میشه.
اینجا شما اول تو حساب خودتون یکی به مبلغ چک اضافه میکنید، بعد چک جدید رو با مبلغ افزایشیافته به طرف میدین (مقدار جدید رو برمیگردونین).
مثال:
int y = 5;
// اینجا اول y یکی زیاد میشه (میشه 6)، بعد مقدار جدیدش چاپ میشه
Console.WriteLine(++y); // خروجی: 6
// y که قبلاً زیاد شده بود، هنوز همون 6 هست
Console.WriteLine(y); // خروجی: 6
حرف آخر:
x++ (Postfix):
اول مقدار فعلی رو برمیگردونه، بعد x رو آپدیت میکنه.
++x (Prefix):
اول x رو آپدیت میکنه، بعد مقدار جدید رو برمیگردونه.
دونستن همین نکته کوچیک، به خصوص در حلقهها و محاسبات پیچیده، خیلی به کار میاد و جلوی باگهای منطقی رو میگیره!
🔖 هشتگها :
#CSharp
#QuickTip
#DotNet
🌀 دنیای عجیب اعداد اعشاری: Infinity، NaN و تلههای مقایسه!
فکر میکنید تقسیم بر صفر همیشه خطا میده؟ یا یه مقدار همیشه با خودش برابره؟ تو دنیای اعداد اعشاری (float و double)، قوانین فیزیک یه جور دیگهست!
برخلاف اعداد صحیح، این نوعها مقادیر ویژهای دارن که برای مدیریت شرایط خاص ریاضی طراحی شدن. بیاید باهاشون آشنا بشیم.
فکر میکنید تقسیم بر صفر همیشه خطا میده؟ یا یه مقدار همیشه با خودش برابره؟ تو دنیای اعداد اعشاری (float و double)، قوانین فیزیک یه جور دیگهست!
برخلاف اعداد صحیح، این نوعها مقادیر ویژهای دارن که برای مدیریت شرایط خاص ریاضی طراحی شدن. بیاید باهاشون آشنا بشیم.
♾️ بینهایت (Infinity):
وقتی تقسیم بر صفر خطا نیست!
در دنیای اعداد صحیح، تقسیم بر صفر یه گناه نابخشودنیه و باعث خطای DivideByZeroException میشه. اما در دنیای اعداد اعشاری، اینطور نیست!
تقسیم یک عدد غیرصفر بر صفر، منجر به تولید بینهایت مثبت یا منفی میشه و برنامه به کارش ادامه میده.
این رفتار در محاسبات علمی و گرافیکی که با حدود و مقادیر خیلی بزرگ سر و کار داریم، بسیار مفیده.
وقتی تقسیم بر صفر خطا نیست!
در دنیای اعداد صحیح، تقسیم بر صفر یه گناه نابخشودنیه و باعث خطای DivideByZeroException میشه. اما در دنیای اعداد اعشاری، اینطور نیست!
تقسیم یک عدد غیرصفر بر صفر، منجر به تولید بینهایت مثبت یا منفی میشه و برنامه به کارش ادامه میده.
Console.WriteLine(1.0 / 0.0); // خروجی: Infinity
Console.WriteLine(-1.0 / 0.0); // خروجی: -Infinity
Console.WriteLine(1.0 / -0.0); // خروجی: -Infinity
این رفتار در محاسبات علمی و گرافیکی که با حدود و مقادیر خیلی بزرگ سر و کار داریم، بسیار مفیده.
❓ پوچی مطلق (NaN):
وقتی جواب "عدد نیست"!
مثلاً تقسیم صفر بر صفر، یا کم کردن بینهایت از بینهایت، جوابی نداره و سیشارپ به جای خطا دادن، حاصل رو NaN برمیگردونه.
مثل یه پرچمه که میگه "من نتونستم این محاسبه رو انجام بدم، ولی نذاشتم برنامه کرش کنه".
وقتی جواب "عدد نیست"!
NaN مخفف "Not a Number" (عدد نیست) هست. این مقدار ویژه، حاصل عملیات ریاضی تعریف نشده یا نامعتبره.
مثلاً تقسیم صفر بر صفر، یا کم کردن بینهایت از بینهایت، جوابی نداره و سیشارپ به جای خطا دادن، حاصل رو NaN برمیگردونه.
Console.WriteLine(0.0 / 0.0); // خروجی: NaN
Console.WriteLine((1.0 / 0.0) - (1.0 / 0.0)); // خروجی: NaN
NaN
مثل یه پرچمه که میگه "من نتونستم این محاسبه رو انجام بدم، ولی نذاشتم برنامه کرش کنه".
🪤 تله بزرگ: 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 ارثبری میکنند که یک سری متدهای عمومی و کاربردی رو در اختیارشون میذاره.