⚠️ خطرناکترین کلمه در #C :
اگه یه جایزهای برای منفورترین و رایجترین خطا تو دنیای داتنت وجود داشت، بدون شک به NullReferenceException میرسید. این خطا مثل یه روح سرگردانه که هر لحظه ممکنه ظاهر بشه و برنامهتون رو از کار بندازه. اما ریشه این کابوس، فقط یه کلمهست: null.
بزن بریم بفهمیم این null چیه و چطور از فاجعه جلوگیری کنیم.
👻 null دقیقاً یعنی چی؟
فکر کنید یه متغیر از نوع Reference Type (مثل یه class)، یه کارت ویزیته که روش آدرس یه خونه نوشته شده.
"null"
یعنی اون کارت ویزیت کاملاً سفیده و هیچ آدرسی روش نیست. پوچه! این رفرنس به هیچ آبجکتی در حافظه اشاره نمیکنه.
💥 فاجعه کجا رخ میده؟ (NullReferenceException)
حالا اگه شما سعی کنید با اون کارت ویزیت سفید، درِ خونه رو باز کنید (یعنی به یکی از اعضای اون آبجکت مثل X دسترسی پیدا کنید)، برنامه قاطی میکنه و با تمام قدرت فریاد میزنه: NullReferenceException!
ترجمه این خطا به زبون خودمونی اینه: "داری سعی میکنی از چیزی استفاده کنی که اصلاً وجود نداره!"
🚫 تکلیف Value Type ها چیه؟
خبر خوب اینه که Value Type ها (مثل int, struct, bool) ذاتاً نمیتونن null باشن. اونا همیشه یه مقداری دارن (حتی اگه مقدار پیشفرضشون صفر باشه).
Value Type مثل یه سکه هست که یا این روئه یا اون رو؛ حالت "هیچی" نداره.
🤔 حرف حساب و تکنیک شما چیه؟
مقابله با null یه مهارت روزمرهست. از چک کردنهای ساده مثل if (myVar != null) گرفته تا تکنیکهای مدرنتر در C#.
استراتژی شما برای جلوگیری از این خطای رو مخ چیه؟ آیا از Null-conditional operator (?.) و Null-coalescing operator (??) زیاد استفاده میکنید؟ یا روش خاصی برای مدیریت null تو کدهاتون دارید؟
همه چیز درباره null
اگه یه جایزهای برای منفورترین و رایجترین خطا تو دنیای داتنت وجود داشت، بدون شک به NullReferenceException میرسید. این خطا مثل یه روح سرگردانه که هر لحظه ممکنه ظاهر بشه و برنامهتون رو از کار بندازه. اما ریشه این کابوس، فقط یه کلمهست: null.
بزن بریم بفهمیم این null چیه و چطور از فاجعه جلوگیری کنیم.
👻 null دقیقاً یعنی چی؟
فکر کنید یه متغیر از نوع Reference Type (مثل یه class)، یه کارت ویزیته که روش آدرس یه خونه نوشته شده.
"null"
یعنی اون کارت ویزیت کاملاً سفیده و هیچ آدرسی روش نیست. پوچه! این رفرنس به هیچ آبجکتی در حافظه اشاره نمیکنه.
class Point { public int X, Y; }
// اینجا یه رفرنس به اسم p داریم که به هیچجا اشاره نمیکنه
Point p = null;
Console.WriteLine(p == null);
// True💥 فاجعه کجا رخ میده؟ (NullReferenceException)
حالا اگه شما سعی کنید با اون کارت ویزیت سفید، درِ خونه رو باز کنید (یعنی به یکی از اعضای اون آبجکت مثل X دسترسی پیدا کنید)، برنامه قاطی میکنه و با تمام قدرت فریاد میزنه: NullReferenceException!
ترجمه این خطا به زبون خودمونی اینه: "داری سعی میکنی از چیزی استفاده کنی که اصلاً وجود نداره!"
// این خط فاجعه رو به بار میاره!
// چون p به هیچ آبجکتی اشاره نمیکنه، X هم وجود نداره
Console.WriteLine(p.X);
🚫 تکلیف Value Type ها چیه؟
خبر خوب اینه که Value Type ها (مثل int, struct, bool) ذاتاً نمیتونن null باشن. اونا همیشه یه مقداری دارن (حتی اگه مقدار پیشفرضشون صفر باشه).
Value Type مثل یه سکه هست که یا این روئه یا اون رو؛ حالت "هیچی" نداره.
struct Point { public int X, Y; }
// این کدها اصلاً کامپایل نمیشن! چون Value Type نمیتونه null باشه
Point p = null; // Compile-time error
int x = null; // Compile-time errorنکته: البته C# برای شرایط خاص، چیزی به اسم "Nullable Value Types" داره (مثلاً int?) که بعداً بهش میرسیم. ولی اصل داستان همینه.
🤔 حرف حساب و تکنیک شما چیه؟
مقابله با null یه مهارت روزمرهست. از چک کردنهای ساده مثل if (myVar != null) گرفته تا تکنیکهای مدرنتر در C#.
استراتژی شما برای جلوگیری از این خطای رو مخ چیه؟ آیا از Null-conditional operator (?.) و Null-coalescing operator (??) زیاد استفاده میکنید؟ یا روش خاصی برای مدیریت null تو کدهاتون دارید؟
🔖 هشتگها :
#CSharp
#ErrorHandling #NullReferenceException
خانواده بزرگ اعداد در #C :
وقتی تو #C میخوایم با اعداد کار کنیم، فقط int رو نداریم! یه خانواده بزرگ و متنوع از انواع عددی وجود داره که هر کدوم برای یه کار خاص ساخته شدن و قدرتهای متفاوتی دارن.
بیاید با همه اعضای اصلی این خانواده آشنا بشیم تا بدونید برای هر موقعیتی، کدوم سرباز رو باید به میدان بفرستید.
اینها برای کار با اعداد کامل و بدون اعشار هستن.
انتخاب اول و پیشفرض برای ۹۰٪ کارها. برای شمارندهها، شناسهها (ID) و محاسبات عمومی، همیشه انتخاب اول شما int هست.
وقتی int برای جا دادن عددتون کم میاره، long مثل یه تانک وارد میشه و مشکل رو حل میکنه.
اینا نسخههای کوچیکتر int هستن. معمولاً بهشون نیاز پیدا نمیکنید، مگر اینکه روی حافظه خیلی حساس باشید.
اینا همون اعداد صحیح هستن، با این تفاوت که فقط مقادیر مثبت رو قبول میکنن (Unsigned). در عوض، میتونن اعداد مثبت بزرگتری رو نسبت به همتای علامتدار خودشون ذخیره کنن.
اینها برای کار با اعداد اعشاری و کسری به کار میان.
انتخاب پیشفرض و سریع برای اعداد اعشاریه. برای محاسبات علمی، گرافیکی و فیزیکی که سرعت در اونها مهمه، double بهترین گزینهست.
این یکی فوقالعاده دقیقه و هیچ خطای گردکردنی نداره. برای هر نوع محاسبه مالی و پولی، استفاده از هر چیزی به جز decimal یه اشتباه بزرگه!
مثل یه نسخه کوچیکتر و کمدقتتر از double میمونه. جایی کاربرد داره که هم به اعشار نیاز دارید و هم حافظه براتون خیلی مهمه.
وقتی تو #C میخوایم با اعداد کار کنیم، فقط int رو نداریم! یه خانواده بزرگ و متنوع از انواع عددی وجود داره که هر کدوم برای یه کار خاص ساخته شدن و قدرتهای متفاوتی دارن.
بیاید با همه اعضای اصلی این خانواده آشنا بشیم تا بدونید برای هر موقعیتی، کدوم سرباز رو باید به میدان بفرستید.
اعداد صحیح (Integers):
اینها برای کار با اعداد کامل و بدون اعشار هستن.
int
انتخاب اول و پیشفرض برای ۹۰٪ کارها. برای شمارندهها، شناسهها (ID) و محاسبات عمومی، همیشه انتخاب اول شما int هست.
long
وقتی int برای جا دادن عددتون کم میاره، long مثل یه تانک وارد میشه و مشکل رو حل میکنه.
short و sbyte
اینا نسخههای کوچیکتر int هستن. معمولاً بهشون نیاز پیدا نمیکنید، مگر اینکه روی حافظه خیلی حساس باشید.
uint, ulong, ushort, byte
اینا همون اعداد صحیح هستن، با این تفاوت که فقط مقادیر مثبت رو قبول میکنن (Unsigned). در عوض، میتونن اعداد مثبت بزرگتری رو نسبت به همتای علامتدار خودشون ذخیره کنن.
Real Numbers
اینها برای کار با اعداد اعشاری و کسری به کار میان.
double
انتخاب پیشفرض و سریع برای اعداد اعشاریه. برای محاسبات علمی، گرافیکی و فیزیکی که سرعت در اونها مهمه، double بهترین گزینهست.
decimal
این یکی فوقالعاده دقیقه و هیچ خطای گردکردنی نداره. برای هر نوع محاسبه مالی و پولی، استفاده از هر چیزی به جز decimal یه اشتباه بزرگه!
float
مثل یه نسخه کوچیکتر و کمدقتتر از double میمونه. جایی کاربرد داره که هم به اعشار نیاز دارید و هم حافظه براتون خیلی مهمه.
🪄 جادوی اعداد در #C :
نوشتن اعداد مثل یک حرفهای (Literals, Suffixes & More)
فکر میکنی نوشتن 1000 تو سیشارپ همیشه به معنی int هست؟ یا چطور به کامپایلر بفهمونیم منظورمون یه عدد decimalئه نه double؟
اینا جزئیات و "فوت کوزهگری" هستن که فرق کدنویس معمولی و حرفهای رو مشخص میکنه. بزن بریم این ترفندها رو یاد بگیریم!
👓 خوانایی کد با لیترالهای حرفهای
نوشتن اعداد، فقط مقدارشون نیست؛ خوانایی هم مهمه!
جداکننده ارقام (_): برای اعداد بزرگ، میتونید از آندرلاین برای جدا کردن ارقام و خوانایی بیشتر استفاده کنید. کامپایلر این آندرلاینها رو نادیده میگیره.
مبنای شانزده (Hexadecimal): با پیشوند 0x، میتونید اعداد رو در مبنای ۱۶ بنویسید. این برای کار با رنگها، فلگها و عملیات سطح پایین خیلی کاربردیه.
مبنای دو (Binary): با پیشوند 0b، میتونید مستقیماً عدد رو به صورت باینری وارد کنید.
🏷 پسوندها (Suffixes): تعیین تکلیف برای کامپایلر!
بعضی وقتا کامپایلر خودش نوع عدد رو حدس میزنه (که بهش میگیم Type Inference)، ولی گاهی ما باید حرف آخر رو بزنیم! اینجاست که پسوندها وارد میشن.
مهمترینهاشون اینان:
اگه این پسوند رو نذارید، کامپایلر عدد اعشاری شما رو double در نظر میگیره و کدتون خطا میده چون double به راحتی به float تبدیل نمیشه.
این یکی فوقالعاده مهمه. برای تمام محاسبات مالی و پولی، باید از decimal استفاده کنید و برای مقداردهی بهش، حتماً از پسوند M استفاده کنید.
این پسوندها کمتر ضروری هستن، چون معمولاً کامپایلر میتونه نوعشون رو حدس بزنه یا به صورت خودکار تبدیل کنه. ولی خوبه که بدونید وجود دارن.
❓ شما چطور اعداد رو مینویسید؟
اینا فقط یه سری قاعده خشک و خالی نیستن، ابزارهایی برای نوشتن کد دقیقتر و خواناتر هستن.
نوشتن اعداد مثل یک حرفهای (Literals, Suffixes & More)
فکر میکنی نوشتن 1000 تو سیشارپ همیشه به معنی int هست؟ یا چطور به کامپایلر بفهمونیم منظورمون یه عدد decimalئه نه double؟
اینا جزئیات و "فوت کوزهگری" هستن که فرق کدنویس معمولی و حرفهای رو مشخص میکنه. بزن بریم این ترفندها رو یاد بگیریم!
👓 خوانایی کد با لیترالهای حرفهای
نوشتن اعداد، فقط مقدارشون نیست؛ خوانایی هم مهمه!
جداکننده ارقام (_): برای اعداد بزرگ، میتونید از آندرلاین برای جدا کردن ارقام و خوانایی بیشتر استفاده کنید. کامپایلر این آندرلاینها رو نادیده میگیره.
int bigNumber = 1_000_000; // خیلی خواناتر از 1000000
var binaryData = 0b1010_1011_1100;
مبنای شانزده (Hexadecimal): با پیشوند 0x، میتونید اعداد رو در مبنای ۱۶ بنویسید. این برای کار با رنگها، فلگها و عملیات سطح پایین خیلی کاربردیه.
int redColor = 0xFF0000;
مبنای دو (Binary): با پیشوند 0b، میتونید مستقیماً عدد رو به صورت باینری وارد کنید.
var filePermissions = 0b110_110_100; // rwx-wx--
🏷 پسوندها (Suffixes): تعیین تکلیف برای کامپایلر!
بعضی وقتا کامپایلر خودش نوع عدد رو حدس میزنه (که بهش میگیم Type Inference)، ولی گاهی ما باید حرف آخر رو بزنیم! اینجاست که پسوندها وارد میشن.
مهمترینهاشون اینان:
F برای float
اگه این پسوند رو نذارید، کامپایلر عدد اعشاری شما رو double در نظر میگیره و کدتون خطا میده چون double به راحتی به float تبدیل نمیشه.
// float f = 4.5; // ❌ کامپایل نمیشه!
float f = 4.5F; // ✅ درسته!
M برای decimal
این یکی فوقالعاده مهمه. برای تمام محاسبات مالی و پولی، باید از decimal استفاده کنید و برای مقداردهی بهش، حتماً از پسوند M استفاده کنید.
// decimal balance = 100.99; // ❌ کامپایل نمیشه!
decimal balance = 100.99M; // ✅ درسته!
L برای long و U برای unsigned
این پسوندها کمتر ضروری هستن، چون معمولاً کامپایلر میتونه نوعشون رو حدس بزنه یا به صورت خودکار تبدیل کنه. ولی خوبه که بدونید وجود دارن.
long bigNum = 9_000_000_000L; // با L بهش میگیم این یه عدد خیلی بزرگه
❓ شما چطور اعداد رو مینویسید؟
اینا فقط یه سری قاعده خشک و خالی نیستن، ابزارهایی برای نوشتن کد دقیقتر و خواناتر هستن.
🔖 هشتگها :
#CSharp
#CodingTips
#Literals
🎲 قوانین بازی با اعداد در #C :
تا حالا فکر کردید وقتی یه int رو توی یه long میریزید، یا یه double رو به زور تو یه int جا میدید، پشت صحنه چه اتفاقی میفته؟
این تبدیلها (Conversions) قوانین خودشون رو دارن و ندونستن این قوانین، میتونه به از دست رفتن داده و باگهای وحشتناک ختم بشه. بزن بریم این قوانین رو یک بار برای همیشه یاد بگیریم.
این تبدیلها مثل ریختن آب از یه لیوان کوچیک تو یه سطل بزرگه؛ کاملاً امن، بدون دردسر و بدون از دست رفتن حتی یک قطره آب!
وقتی نوع داده مقصد، گنجایش تمام مقادیر ممکن در نوع داده مبدأ رو داشته باشه، #C خودش هوشمندانه و به صورت خودکار این تبدیل رو انجام میده.
اینجا دیگه سیشارپ به شما اجازه نمیده این کار رو خودکار انجام بدید. شما باید با مسئولیت خودتون و با استفاده از Casting (قرار دادن نوع مقصد داخل پرانتز)، به کامپایلر بگید: "میدونم دارم چیکار میکنم و ریسکش رو میپذیرم!"
💣 دو تله مرگبار در تبدیلهای صریح
وقتی کست میکنید، حواستون به این دوتا تله باشه:
بریده شدن اعشار (Truncation):
وقتی یه عدد اعشاری (double یا float) رو به یه عدد صحیح (int) کست میکنید، بخش اعشاری عدد گرد نمیشه، بلکه وحشیانه بریده و حذف میشه!
از دست رفتن دقت (Precision Loss):
گاهی اوقات، حتی در تبدیلهای امن (مثل int به float)، ممکنه دقت رو از دست بدید. float میتونه اعداد خیلی بزرگتری رو نشون بده، ولی دقتش در جزئیات کمتر از int هست.
همه چیز درباره تبدیلهای عددی (Casting)
تا حالا فکر کردید وقتی یه int رو توی یه long میریزید، یا یه double رو به زور تو یه int جا میدید، پشت صحنه چه اتفاقی میفته؟
این تبدیلها (Conversions) قوانین خودشون رو دارن و ندونستن این قوانین، میتونه به از دست رفتن داده و باگهای وحشتناک ختم بشه. بزن بریم این قوانین رو یک بار برای همیشه یاد بگیریم.
✅ نوع اول: تبدیلهای امن و ضمنی (Implicit)
این تبدیلها مثل ریختن آب از یه لیوان کوچیک تو یه سطل بزرگه؛ کاملاً امن، بدون دردسر و بدون از دست رفتن حتی یک قطره آب!
وقتی نوع داده مقصد، گنجایش تمام مقادیر ممکن در نوع داده مبدأ رو داشته باشه، #C خودش هوشمندانه و به صورت خودکار این تبدیل رو انجام میده.
int myInt = 12345;
// int (32-bit) به راحتی در long (64-bit) جا میشه
long myLong = myInt;
// int (عدد صحیح) به راحتی به double (عدد اعشاری) تبدیل میشه
double myDouble = myInt;
float myFloat = 123.45F;
// float (32-bit) به راحتی در double (64-bit) جا میشه
double anotherDouble = myFloat;
⚠️ نوع دوم: تبدیلهای خطرناک و صریح (Explicit Casting)حالا برعکسش رو تصور کنید: میخواید آب یه سطل بزرگ رو تو یه لیوان کوچیک جا بدید. احتمال اینکه آب سرریز بشه و بخشی ازش از دست بره، خیلی زیاده!
اینجا دیگه سیشارپ به شما اجازه نمیده این کار رو خودکار انجام بدید. شما باید با مسئولیت خودتون و با استفاده از Casting (قرار دادن نوع مقصد داخل پرانتز)، به کامپایلر بگید: "میدونم دارم چیکار میکنم و ریسکش رو میپذیرم!"
long bigNum = 3_000_000_000L;
double myDouble = 12345.6789;
// برای جا دادن long در int، باید کست کنیم
// اگه عدد بزرگ باشه، داده از دست میره!
int myInt = (int)bigNum;
// برای جا دادن double در int، باید کست کنیم
int anotherInt = (int)myDouble; // مقدارش میشه 12345
💣 دو تله مرگبار در تبدیلهای صریح
وقتی کست میکنید، حواستون به این دوتا تله باشه:
بریده شدن اعشار (Truncation):
وقتی یه عدد اعشاری (double یا float) رو به یه عدد صحیح (int) کست میکنید، بخش اعشاری عدد گرد نمیشه، بلکه وحشیانه بریده و حذف میشه!
double price = 99.99;
int priceAsInt = (int)price; // مقدارش میشه 99، نه 100!
از دست رفتن دقت (Precision Loss):
گاهی اوقات، حتی در تبدیلهای امن (مثل int به float)، ممکنه دقت رو از دست بدید. float میتونه اعداد خیلی بزرگتری رو نشون بده، ولی دقتش در جزئیات کمتر از int هست.
int bigInt = 100_000_001;
// اینجا مقدار حفظ میشه، ولی دقت از بین میره
float myFloat = bigInt;
// وقتی برمیگردونیم، میبینیم که عدد تغییر کرده!
int convertedInt = (int)myFloat; // مقدارش میشه 100_000_000
🔖 هشتگها :
#CSharp
#CodingTips
#Casting
💡 نکته سریع #C :
همه ما با عملگرهای حسابی مثل + و * آشناییم. ولی آیا میدونستید یه نکتهی جالب و مهم در موردشون موقع کار با تایپهای کوچیکی مثل byte و short وجود داره؟
فرض کنید میخواید دو تا byte رو با هم جمع کنید. چه اتفاقی میفته؟
چرا خطا میده؟
چون در #C، وقتی عملیات حسابی روی تایپهای کوچکتر از int (مثل byte یا short) انجام میشه، اونها اول به صورت خودکار به int تبدیل میشن و حاصل عملیات هم یک int خواهد بود!
بنابراین، کد صحیح این شکلیه:
دلیل این کار چیه؟
این رفتار برای بهینهسازی و سادگی در سطح پردازنده و CLR طراحی شده. کار کردن با int (که معمولاً اندازه کلمه پردازنده است) سریعتر و بهینهتره.
حالا اگه حتماً نتیجه رو تو یه byte میخواید چی؟
باید خودتون صراحتاً نتیجه رو کست کنید (و البته حواستون به سرریز شدن یا Overflow باشه!):
حرف آخر: یادتون باشه، حاصل عملیات حسابی روی تایپهای کوچیک، همیشه یه intئه! دونستن همین نکته کوچیک، جلوی خیلی از خطاهای کامپایل ناگهانی رو میگیره.
راز عملگرهای حسابی با تایپهای کوچک!
همه ما با عملگرهای حسابی مثل + و * آشناییم. ولی آیا میدونستید یه نکتهی جالب و مهم در موردشون موقع کار با تایپهای کوچیکی مثل byte و short وجود داره؟
فرض کنید میخواید دو تا byte رو با هم جمع کنید. چه اتفاقی میفته؟
byte b1 = 10;
byte b2 = 20;
// انتظار داریم این کد کار کنه، ولی...
// ❌ این خط کامپایل نمیشه!
byte result = b1 + b2;
چرا خطا میده؟
چون در #C، وقتی عملیات حسابی روی تایپهای کوچکتر از int (مثل byte یا short) انجام میشه، اونها اول به صورت خودکار به int تبدیل میشن و حاصل عملیات هم یک int خواهد بود!
بنابراین، کد صحیح این شکلیه:
// ✅ درسته! حاصل جمع دو بایت، یک int است.
int result = b1 + b2;
دلیل این کار چیه؟
این رفتار برای بهینهسازی و سادگی در سطح پردازنده و CLR طراحی شده. کار کردن با int (که معمولاً اندازه کلمه پردازنده است) سریعتر و بهینهتره.
حالا اگه حتماً نتیجه رو تو یه byte میخواید چی؟
باید خودتون صراحتاً نتیجه رو کست کنید (و البته حواستون به سرریز شدن یا Overflow باشه!):
// ✅ درسته (با مسئولیت خودتان!)
byte result = (byte)(b1 + b2);
حرف آخر: یادتون باشه، حاصل عملیات حسابی روی تایپهای کوچیک، همیشه یه intئه! دونستن همین نکته کوچیک، جلوی خیلی از خطاهای کامپایل ناگهانی رو میگیره.
🔖 هشتگها :
#CSharp
#QuickTip
💣 تلههای کار با اعداد صحیح :
تقسیم، Overflow و checked
فکر میکنید 2÷5 همیشه حاصلش 2.5 میشه؟ یا int.MaxValue + 1 یه عدد خیلی بزرگه؟ اگه جوابتون "بله" هست، این پست برای شماست!
تو دنیای اعداد صحیح، تلههای پنهانی وجود داره که میتونه بیصدا و بدون هیچ هشداری، کدهای شما رو به فنا بده. بزن بریم این خطرات رو بشناسیم و یاد بگیریم چطور ازشون جلوگیری کنیم.
تقسیم، Overflow و checked
فکر میکنید 2÷5 همیشه حاصلش 2.5 میشه؟ یا int.MaxValue + 1 یه عدد خیلی بزرگه؟ اگه جوابتون "بله" هست، این پست برای شماست!
تو دنیای اعداد صحیح، تلههای پنهانی وجود داره که میتونه بیصدا و بدون هیچ هشداری، کدهای شما رو به فنا بده. بزن بریم این خطرات رو بشناسیم و یاد بگیریم چطور ازشون جلوگیری کنیم.
🔪 تله اول: تقسیم صحیح و سلاخی اعشار!
وقتی دو عدد صحیح رو بر هم تقسیم میکنید، سی شارپ بخش اعشاری حاصل رو گرد نمیکنه، بلکه به سادگی حذف (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