C# Geeks (.NET) – Telegram
🚀 انقلاب switch در #C مدرن: Pattern Matching

تو پست قبلی، با switch کلاسیک آشنا شدیم. اما داستان اینجا تموم نمیشه! از #C 7 به بعد، switch یه تحول بزرگ داشته و به یه ابزار فوق‌العاده قدرتمند به اسم Pattern Matching مجهز شده.

بیاید ببینیم این قابلیت‌های جدید چی هستن و چطور switch رو از یه ابزار ساده، به یه غول تبدیل کردن.

1️⃣ سوییچ روی نوع (Switching on Type)
دیگه محدود به مقادیر ثابت نیستید! حالا می‌تونید یه متغیر از نوع object رو به switch بدید و بر اساس نوع واقعی اون در زمان اجرا، تصمیم‌گیری کنید و حتی همون لحظه اون رو تو یه متغیر جدید بریزید.
void TellMeTheType(object x)
{
switch (x)
{
// اگه x از نوع int بود، اون رو در متغیر i بریز و این بلوک رو اجرا کن
case int i:
Console.WriteLine($"It's an int! Square is {i * i}");
break;
// اگه از نوع string بود...
case string s:
Console.WriteLine($"It's a string! Length is {s.Length}");
break;
case null:
Console.WriteLine("It's null");
break;
default:
Console.WriteLine("I don't know what it is");
break;
}
}


2️⃣ اضافه کردن شرط با when

گاهی اوقات، فقط چک کردن نوع کافی نیست و می‌خواید یه شرط اضافه هم داشته باشید. کلمه کلیدی when به شما اجازه میده دقیقاً همین کار رو بکنید.
void ProcessShape(object shape)
{
switch (shape)
{
// این case فقط برای دایره‌هایی اجرا میشه که شعاعشون بزرگتر از 10 هست
case Circle c when c.Radius > 10:
Console.WriteLine("Big circle");
break;

case Circle c:
Console.WriteLine("Small or medium circle");
break;

case bool b when b == true: // فقط وقتی که بولین، true باشه
Console.WriteLine("It's true!");
break;
}
}


3️⃣ نکات تکمیلی و مهم

اهمیت ترتیب: برخلاف switch کلاسیک، در Pattern Matching ترتیب caseها مهمه! #C اون‌ها رو از بالا به پایین چک می‌کنه و به محض رسیدن به اولین گزینه‌ی منطبق، کار تموم میشه.

روی هم چیدن caseها: می‌تونید چند case مختلف رو برای اجرای یک کد مشترک، روی هم بچینید:
switch (x)
{
case float f when f > 1000:
case double d when d > 1000:
case decimal m when m > 1000:
Console.WriteLine("It's a large number!");
break;
}


🤔 حرف حساب و تجربه شما

این Pattern Matching، دستور switch رو از یه ابزار ساده به یه غول قدرتمند برای کار با داده‌ها و انواع مختلف تبدیل کرده.

شما از این قابلیت‌های مدرن switch تو کدهاتون استفاده می‌کنید؟ کدوم ویژگیش (سوییچ روی نوع، when، ...) براتون کاربردی‌تر بوده؟

خب، اینجا که نمیشه همه حرفا رو زد! 😉
ادامه‌ی بحث، سوالات، غر زدن‌ها و گپ و گفت‌های خودمونی، فقط تو گروه.
[C# Geeks Hangout]

🔖 هشتگ‌ها:
#CSharp #Programming #Developer #DotNet #PatternMatching #SwitchCase #ModernCSharp
از دستور به عبارت: تکامل switch در #C مدرن (Switch Expressions)

تو پست‌های قبلی با switch کلاسیک و switch با Pattern Matching آشنا شدیم. اما انقلاب switch اینجا تموم نمیشه! از C# 8 به بعد، switch می‌تونه به عنوان یک عبارت (Expression) هم استفاده بشه.

یعنی چی؟ یعنی می‌تونه یه مقدار رو برگردونه! این قابلیت، کدهای شرطی رو به شدت کوتاه‌تر و خواناتر می‌کنه.

1️⃣ معرفی switch expression: خداحافظی با case و break

در این حالت جدید، switch دیگه یه دستور برای کنترل جریان نیست، بلکه یه عبارته که یه مقدار تولید می‌کنه. سینتکسش خیلی خلاصه‌تر و تمیزتره:

متغیر قبل از switch میاد.

به جای case x: و break;، از x => result, استفاده می‌کنیم.

برای حالت default، از _ (discard pattern) استفاده می‌کنیم.
string cardName = cardNumber switch
{
13 => "King",
12 => "Queen",
11 => "Jack",
_ => "Pip card" // معادل default
};


نکته مهم ⚠️: برخلاف switch statement، اگه تو یه switch expression حالت پیش‌فرض (_) رو نذارید و هیچکدوم از حالت‌ها هم با مقدار ورودی نخونه، برنامه‌تون با یه Exception کرش می‌کنه!
2️⃣ قدرت اصلی: سوییچ روی چند مقدار با Tuple Pattern

قدرت واقعی switch expression وقتی مشخص میشه که می‌فهمید می‌تونید روی چند تا مقدار همزمان سوییچ کنید! این کار با استفاده از Tuples انجام میشه.
string cardName = (cardNumber, suite) switch
{
(13, "spades") => "King of spades",
(13, "clubs") => "King of clubs",
(12, "spades") => "Queen of spades",
// ...
_ => "Some other card"
};


این قابلیت به شما اجازه میده شرط‌های خیلی پیچیده رو به شکلی فوق‌العاده خوانا و تمیز بنویسید.

🤔 حرف حساب و تجربه شما

این switch expression یکی از بهترین قابلیت‌های مدرن #C برای نوشتن کدهای تابعی (Functional)، خوانا و خلاصه‌ست و در LINQ هم بسیار پرکاربرده.

شما از switch expression تو کدهاتون استفاده می‌کنید؟ به نظرتون این سینتکس از switch statement قدیمی بهتره؟

خب، اینجا که نمیشه همه حرفا رو زد! 😉
ادامه‌ی بحث، سوالات، غر زدن ها و گپ و گفت‌های خودمونی، فقط تو گروه.
[C# Geeks Hangout]

🔖 هشتگ‌ها:
#CSharp #Programming #Developer #DotNet #ModernCSharp #SwitchExpression #CleanCode
کدام کلمه کلیدی در #C برای جلوگیری از تغییر مقدار یک متغیر بعد از تعریف اولیه استفاده می‌شود؟
Anonymous Quiz
6%
static
13%
readonly
75%
const
6%
fixed
🔄 تکرار کد بدون تکرار! راهنمای کامل حلقه‌ها (Loops) در #C

چطور می‌تونیم یه کار رو صد بار انجام بدیم، بدون اینکه صد خط کد بنویسیم؟ با استفاده از حلقه‌ها (Loops)! حلقه‌ها ستون فقرات هر الگوریتم پیچیده‌ای هستن و به ما اجازه میدن یک بلوک کد رو بارها و بارها تکرار کنیم.

امروز با ۴ نوع اصلی حلقه در #C آشنا میشیم.

1️⃣ حلقه while: تا وقتی که...
این ساده‌ترین نوع حلقه‌ست. یه شرط bool رو بررسی می‌کنه و تا وقتی اون شرط true باشه، بلوک کد رو تکرار می‌کنه.

نکته کلیدی: شرط قبل از هر بار اجرای بدنه حلقه چک میشه. پس اگه شرط از اول false باشه، بدنه حلقه هیچوقت اجرا نمیشه.
int i = 0;
while (i < 3)
{
Console.Write(i); // 012
i++;
}


2️⃣ حلقه do-while: حداقل یک بار اجرا کن!
این حلقه خیلی شبیه while هست، با این تفاوت مهم که شرط رو بعد از اجرای بلوک کد چک می‌کنه.

نتیجه مهم: این یعنی تضمین میشه که بدنه حلقه حداقل یک بار اجرا بشه، حتی اگه شرط از اول false باشه.
int i = 0;
do
{
Console.Write(i); // 012
i++;
}
while (i < 3);


3️⃣ حلقه for: حلقه همه‌کاره و قدرتمند 🔢
این حلقه برای تکرارهایی که تعدادشون مشخصه، عالیه. سه بخش اصلی و قدرتمند داره:
for (بخش مقداردهی اولیه; بخش شرط; بخش گام تکرار)

مقداردهی اولیه: فقط یک بار، قبل از شروع حلقه اجرا میشه (معمولاً برای تعریف شمارنده).

شرط: قبل از هر بار تکرار، این شرط bool چک میشه.

گام تکرار: بعد از هر بار اجرای بدنه حلقه، این بخش اجرا میشه (معمولاً برای افزایش شمارنده).
for (int i = 0; i < 3; i++)
{
Console.Write(i); // 012
}


مثال پیشرفته (دنباله فیبوناچی):
// محاسبه ۱۰ عدد اول دنباله فیبوناچی
for (int i = 0, prevFib = 1, curFib = 1; i < 10; i++)
{
Console.WriteLine(prevFib);
int newFib = prevFib + curFib;
prevFib = curFib;
curFib = newFib;
}


نکته حرفه‌ای: شما می‌تونید هر کدوم از سه بخش for رو خالی بذارید. مثلاً for (;;) یه حلقه بی‌نهایته!

4️⃣ حلقه foreach: پیمایش کلکسیون‌ها به سبک مدرن
این مدرن‌ترین، خواناترین و امن‌ترین راه برای حرکت روی تمام اعضای یک مجموعه قابل پیمایش (مثل آرایه یا string) هست. دیگه نگران ایندکس و خطای IndexOutOfRangeException نیستید!
// پیمایش روی تمام کاراکترهای رشته "beer"
foreach (char c in "beer")
{
Console.WriteLine(c);
}
// خروجی:
// b
// e
// e
// r


🤔 حرف حساب و تجربه شما

هر کدوم از این حلقه‌ها ابزار مناسبی برای یه کار خاص هستن. انتخاب درست بینشون، کد شما رو تمیزتر و بهینه‌تر می‌کنه.

شما تو کدهاتون بیشتر از کدوم نوع حلقه استفاده می‌کنید؟ for یا foreach؟ سناریوی جالبی دارید که توش do-while بهترین انتخاب بوده باشه؟

خب، اینجا که نمیشه همه حرفا رو زد! 😉
ادامه‌ی بحث، سوالات، غر زدن‌ها و گپ و گفت‌های خودمونی، فقط تو گروه.
[C# Geeks Hangout]

🔖 هشتگ‌ها:
#CSharp #Programming #Developer #DotNet #ControlFlow #Loops #ForEach #ForLoop
🚀 پرش‌های حرفه‌ای در #C: راهنمای کامل break, continue, return, throw و goto

برنامه همیشه خط به خط اجرا نمیشه. گاهی وقتا لازمه از یه حلقه بپریم بیرون، یه تکرار رو رد کنیم، یا کل متد رو متوقف کنیم. #C برای این "پرش"ها، چند تا دستور کلیدی داره که بهشون میگیم Jump Statements.
بیاید با این ابزارهای قدرتمند برای کنترل جریان اجرا آشنا بشیم.

💡نکته مهم: تمام این دستورات پرش، از قوانین بلوک try...finally پیروی می‌کنن. یعنی اگه از داخل یه try به بیرون بپرید، بلوک finally همیشه قبل از پرش اجرا میشه.

1️⃣ break: فرار از حلقه!

دستور break مثل یه درِ خروج اضطراری برای حلقه‌ها (for, while, ...) و switch عمل می‌کنه. به محض اجرا، حلقه یا switch رو فوراً متوقف می‌کنه و اجرا به اولین خط بعد از اون منتقل میشه.
int i = 0;
while (true) // حلقه بی‌نهایت
{
if (i > 5)
break; // از حلقه فرار کن!

Console.Write(i + " ");
i++;
}
// خروجی: 0 1 2 3 4 5
// اجرا از اینجا ادامه پیدا می‌کنه


2️⃣ continue: این یکی رو بی‌خیال شو!

continue مثل دکمه "اسکیپ" برای تکرار فعلی حلقه‌ست. بقیه کدهای داخل حلقه رو برای این تکرار نادیده می‌گیره و مستقیم میره سراغ شرط و گام تکرار بعدی.
for (int i = 0; i < 10; i++)
{
if ((i % 2) == 0) // اگه عدد زوجه
continue; // این تکرار رو بی‌خیال شو و برو سراغ i بعدی

Console.Write(i + " ");
}
// خروجی: 1 3 5 7 9


3️⃣ return: ماموریت انجام شد!

return به متد میگه "کار من اینجا تموم شد!" و اجرا رو به جایی که متد صدا زده شده بود، برمی‌گردونه. اگه متد خروجی داشته باشه (non-void)، return باید اون مقدار رو هم با خودش برگردونه.
decimal AsPercentage(decimal d)
{
decimal p = d * 100m;
return p; // با مقدار p به بیرون از متد برگرد
}


4️⃣ throw: اعلام وضعیت قرمز!

این throw برای پرتاب کردن یه Exception و اعلام وضعیت خطاست. وقتی یه throw اجرا میشه، اجرای عادی متوقف میشه و #C دنبال یه بلوک catch برای مدیریت اون خطا می‌گرده (که بعداً مفصل بهش می‌رسیم).
if (string.IsNullOrWhiteSpace(name))
{
throw new ArgumentNullException("Name cannot be null or empty!");
}


5️⃣ goto: شمشیر دولبه (و خطرناک!)

goto قدیمی‌ترین و قدرتمندترین دستور پرشه، ولی استفاده ازش در ۹۹٪ مواقع توصیه نمیشه چون می‌تونه کد رو به "کد اسپاگتی" تبدیل کنه که دنبال کردنش خیلی سخته. با این حال، دونستنش برای شناخت کامل زبان خوبه. goto اجرا رو به یک لیبل (label) منتقل می‌کنه.
int i = 1;
startLoop: // این یک لیبل است
if (i <= 5)
{
Console.Write(i + " ");
i++;
goto startLoop; // به لیبل startLoop بپر
}
// خروجی: 1 2 3 4 5


🤔 حرف حساب و تجربه شما

تسلط بر این دستورات پرش، به شما کنترل کاملی روی جریان اجرای برنامه‌تون میده.

شما از کدوم یکی از این دستورات بیشتر استفاده می‌کنید؟ آیا تا حالا از goto تو یه سناریوی واقعی استفاده کردید که واقعاً لازم بوده باشه؟

خب، اینجا که نمیشه همه حرفا رو زد! 😉
ادامه‌ی بحث، سوالات، غر زدن‌ها و گپ و گفت‌های خودمونی، فقط تو گروه.
[پاتوق گیک های #C]

🔖 هشتگ‌ها:
#CSharp #Programming #Developer #DotNet #ControlFlow #BestPractices #Exception
خروجی کد زیر چیست؟
int x = 5;
Console.WriteLine(++x);
Anonymous Quiz
45%
5
50%
6
5%
Compile Time Erorr
0%
4
📂 نظم و ترتیب در #C: همه چیز درباره namespace (از روش کلاسیک تا 10 #C)

وقتی پروژه‌هاتون بزرگ میشه و تعداد کلاس‌ها زیاد میشه، چطور ازش یه آش شله‌قلمکار نسازیم؟ ابزار اصلی #C برای سازماندهی و جلوگیری از تداخل اسمی، namespace هست.

این namespace مثل یه سیستم پوشه‌بندیه (folders) برای کدهاتون. شما می‌تونید کلاس‌های مرتبط رو تو یه namespace بذارید تا هم پیداکردنشون راحت‌تر باشه و هم اگه دو تا کلاس با اسم یکسان تو دو تا پوشه مختلف داشتید، با هم قاطی نشن.

1️⃣ روش کلاسیک تعریف namespace

در روش سنتی، شما یک بلوک namespace تعریف می‌کنید و کلاس‌هاتون رو داخلش قرار میدید. برای ایجاد ساختار تودرتو، می‌تونید از نقطه استفاده کنید.
// روش تودرتو با استفاده از نقطه (روش پیشنهادی)
namespace MyProject.Services.Authentication
{
class AuthService { }
class TokenService { }
}

// این کد معادل کد بالاست ولی باعث تو رفتگی (indentation) بیشتر میشه
namespace MyProject
{
namespace Services
{
namespace Authentication
{
class AuthService { }
class TokenService { }
}
}
}


برای استفاده از یک کلاس، باید اسم کاملش رو به همراه namespace وارد کنید، مگر اینکه اون namespace رو بالای فایل using کرده باشید.

2️⃣ روش مدرن (از 10 #C): File-Scoped Namespaces

تیم #C دید که معمولاً تمام کلاس‌های یه فایل، تو یه namespace مشترک هستن. پس از 10 #C به بعد، یه راه خیلی تمیزتر معرفی کردن که کل فایل رو داخل یه namespace قرار میده و از یه لایه تو رفتگی اضافی جلوگیری می‌کنه.

قبل از 10 #C:
namespace MyNamespace
{
class Class1
{
// Code...
}

class Class2
{
// Code...
}
}

از 10 #C به بعد:
namespace MyNamespace; // این برای کل فایل اعمال میشه و تمام!

class Class1
{
// Code...
}

class Class2
{
// Code...
}


این قابلیت، کد شما رو خیلی تمیزتر و خلاصه‌تر می‌کنه.

🤔 حرف حساب و تجربه شما

استفاده درست از namespaceها، اولین قدم برای نوشتن یه پروژه بزرگه که قابل نگهداری باشه.

شما تو پروژه‌هاتون چقدر روی ساختار namespaceها وقت میذارید؟ از قابلیت جدید File-Scoped Namespaces استفاده می‌کنید؟

خب، اینجا که نمیشه همه حرفا رو زد! 😉
ادامه‌ی بحث، سوالات، غر زدن‌ها و گپ و گفت‌های خودمونی، فقط تو گروه.
[C# Geeks Hangout]

🔖 هشتگ‌ها:
#CSharp #Programming #Developer #DotNet #CleanCode #BestPractices #Namespace
🏛 اصول SOLID: پنج ستون اصلی برای ساخت نرم‌افزار حرفه‌ای

تا حالا شده به کدی برگردید که چند ماه پیش نوشتید و دیگه هیچی ازش نفهمید؟ یا بخواید یه تغییر کوچیک بدید و ببینید کل برنامه به هم ریخت؟

💡این مشکلات یه راه حل معروف دارن: اصول SOLID.

اینSOLID مجموعه‌ای از پنج اصل بنیادی در طراحی شیءگراست که توسط "رابرت مارتین (عمو باب)" معرفی شدن. این اصول، مثل ستون‌های یه ساختمون بزرگ، به شما کمک می‌کنن کدی بنویسید که انعطاف‌پذیر، قابل نگهداری و توسعه‌پذیر باشه.

حالا SOLID مخفف چیست؟ 🤔
هر حرف در SOLID، نماینده یک اصله:

1️⃣ S - Single Responsibility Principle (اصل تک مسئولیتی):
یک کلاس باید فقط و فقط یک دلیل برای تغییر داشته باشه.
2️⃣ O - Open/Closed Principle (اصل باز/بسته):
کد شما باید برای "توسعه" باز، ولی برای "تغییر" بسته باشه.
3️⃣ L - Liskov Substitution Principle (اصل جایگزینی لیسکوف):
باید بتونید یک نمونه از کلاس فرزند رو به جای کلاس پدر استفاده کنید، بدون اینکه برنامه به مشکل بخوره.
4️⃣ I - Interface Segregation Principle (اصل تفکیک اینترفیس‌ها):
کلاس‌ها نباید مجبور بشن اینترفیس‌هایی رو پیاده‌سازی کنن که بهشون نیازی ندارن.
5️⃣ D - Dependency Inversion Principle (اصل وارونگی وابستگی):
ماژول‌های سطح بالا نباید به ماژول‌های سطح پایین وابسته باشن؛ هر دو باید به "انتزاع" (Abstraction) وابسته باشن.
🚀 شروع مینی-سریال جدید!
امروز، قراره یه مینی-سریال داشته باشیم و هر کدوم از این ۵ اصل رو به صورت جداگانه، با زبون ساده و مثال‌های عملی در #C کالبدشکافی کنیم.

این سری، سطح شما رو از "کدنویس" به "معمار نرم‌افزار" نزدیک‌تر می‌کنه.

با ما همراه باشید!
💬 بحث و گفتگوی بیشتر در گروه کامیونیتی:
[C# Geeks Community]

🔖 هشتگ‌ها:
#CSharp #Programming #Developer #SOLID #SoftwareArchitecture #CleanCode #BestPractices
1️⃣ اصل اول SOLID: تک مسئولیتی (Single Responsibility Principle)

تا حالا یه کلاسی نوشتید که اولش کوچیک بوده، ولی کم کم اونقدر بزرگ و پیچیده شده که دیگه دست زدن بهش ترسناک بوده؟ این مشکل معمولاً از زیر پا گذاشتن اولین و مهم‌ترین اصل SOLID یعنی اصل تک مسئولیتی (SRP) میاد.

این اصل چی میگه؟ 🎯
به زبان ساده:

✨️یک کلاس باید فقط و فقط یک دلیل برای تغییر داشته باشه.

یعنی هر کلاس باید یه وظیفه مشخص و واحد داشته باشه و فقط همون رو به بهترین شکل انجام بده.

مثال از دنیای واقعی: یه چاقوی سوئیسی رو تصور کنید که هم چاقوئه، هم پیچ‌گوشتی، هم در باز کن. شاید به نظر کارآمد بیاد، ولی هیچکدوم از کارها رو به خوبی یه ابزار تخصصی انجام نمیده. تو کدنویسی هم همینه.

مثال عملی: کد "بدبو" 👎 در برابر کد "تمیز" 👍
فرض کنید یه کلاسی برای ثبت‌نام کاربر داریم.

مثال بد (نقض اصل تک مسئولیتی):
public class UserService
{
public void RegisterUser(string username, string password)
{
if (string.IsNullOrWhiteSpace(username))
{
// مسئولیت اول: لاگ کردن خطا در فایل
File.WriteAllText("errors.log", "Username is empty");
}

// مسئولیت دوم: ثبت کاربر در دیتابیس
// ... code to save user to database ...

// مسئولیت سوم: ارسال ایمیل خوش‌آمدگویی
// ... code to send a welcome email ...
}
}


مشکل چیه؟ این کلاس الان سه تا دلیل برای تغییر داره: ۱. اگه منطق ثبت‌نام عوض بشه. ۲. اگه نحوه لاگ کردن عوض بشه. ۳. اگه متن ایمیل عوض بشه. این یعنی یه کلاس شلوغ و شکننده!

مثال خوب (رعایت اصل تک مسئولیتی):
حالا میایم و هر مسئولیت رو به یه کلاس جدا و متخصص می‌سپاریم.
// ۱. کلاسی فقط برای لاگ کردن
public class FileLogger
{
public void LogError(string message) { /* ... */ }
}

// ۲. کلاسی فقط برای ارسال ایمیل
public class EmailService
{
public void SendWelcomeEmail(string username) { /* ... */ }
}

// ۳. کلاس اصلی که حالا فقط روی منطق کاربر تمرکز داره
public class UserService
{
private readonly FileLogger _logger;
private readonly EmailService _emailService;

public UserService(FileLogger logger, EmailService emailService)
{
_logger = logger;
_emailService = emailService;
}

public void RegisterUser(string username, string password)
{
if (string.IsNullOrWhiteSpace(username))
{
_logger.LogError("Username is empty");
return;
}

// ... code to save user to database ...

_emailService.SendWelcomeEmail(username);
}
}


حالا هر بخش، کار تخصصی خودش رو انجام میده. کد تمیز، قابل تست و قابل نگهداریه!

🔖 هشتگ‌ها:
#CSharp #Programming #Developer #SOLID #SoftwareArchitecture #CleanCode #SRP
2️⃣ اصل دوم SOLID: باز/بسته (Open/Closed Principle)

تا حالا شده یه تیکه کد بنویسید که خوب کار می‌کنه، ولی هر بار که یه قابلیت جدید می‌خواید، مجبور میشید برگردید و همون کد قدیمی و حساس رو دستکاری کنید؟ این کار ریسک ایجاد باگ‌های جدید رو خیلی بالا می‌بره.

اصل باز/بسته (OCP) برای حل همین مشکل اومده.

این اصل چی میگه؟ 🎯
به زبان ساده:

کلاس‌ها و ماژول‌های شما باید برای "توسعه" (Extension) باز، ولی برای "تغییر" (Modification) بسته باشند.


یعنی شما باید بتونید رفتار جدیدی به سیستم اضافه کنید، بدون اینکه نیاز به تغییر کدهای موجودی که قبلاً تست شده و کار می‌کنه، داشته باشید.

مثال عملی: کد "شکننده" 👎 در برابر کد "انعطاف‌پذیر" 👍
فرض کنید یه کلاسی داریم که مساحت اشکال مختلف رو محاسبه می‌کنه.

مثال بد (نقض اصل باز/بسته):
این کد برای تغییر "بسته" نیست. اگه بخوایم یه شکل جدید مثل "مثلث" رو اضافه کنیم، مجبوریم این کلاس رو باز کنیم و یه if جدید بهش اضافه کنیم. این یعنی دستکاری کد قدیمی و ریسک ایجاد باگ.
public class AreaCalculator
{
public double CalculateTotalArea(object[] shapes)
{
double totalArea = 0;
foreach (var shape in shapes)
{
if (shape is Rectangle r)
{
totalArea += r.Width * r.Height;
}
if (shape is Circle c)
{
totalArea += Math.PI * c.Radius * c.Radius;
}
// برای اضافه کردن مثلث، باید این کلاس رو دستکاری کنیم!
}
return totalArea;
}
}


مثال خوب (رعایت اصل باز/بسته):
حالا با استفاده از یک انتزاع (Abstraction) مثل interface، کد رو جوری بازنویسی می‌کنیم که برای توسعه "باز" باشه.

قدم اول: ساختن یک قرارداد (Interface)
public interface IShape
{
double CalculateArea();
}

قدم دوم: پیاده‌سازی قرارداد برای هر شکل
public class Rectangle : IShape
{
public double Width { get; set; }
public double Height { get; set; }
public double CalculateArea() => Width * Height;
}

public class Circle : IShape
{
public double Radius { get; set; }
public double CalculateArea() => Math.PI * Radius * Radius;
}

قدم سوم: کلاس محاسبه‌گر انعطاف‌پذیر
حالا کلاس AreaCalculator ما دیگه به نوع مشخصی وابسته نیست، فقط با قرارداد IShape کار می‌کنه.
public class AreaCalculator
{
public double CalculateTotalArea(IShape[] shapes)
{
double totalArea = 0;
foreach (var shape in shapes)
{
totalArea += shape.CalculateArea();
}
return totalArea;
}
}


جادو اینجا اتفاق میفته: حالا اگه بخوایم شکل جدید "مثلث" رو اضافه کنیم، هیچ نیازی به تغییر کلاس AreaCalculator نداریم! فقط یه کلاس جدید می‌سازیم:
public class Triangle : IShape
{
public double Base { get; set; }
public double Height { get; set; }
public double CalculateArea() => 0.5 * Base * Height;
}



کد ما برای توسعه (اضافه کردن کلاس جدید) باز بود، ولی برای تغییر (دستکاری AreaCalculator) بسته بود!

🔖 هشتگ‌ها:
#CSharp #Programming #Developer #SOLID #SoftwareArchitecture #CleanCode #OCP
3️⃣ اصل سوم SOLID: جایگزینی لیسکوف (Liskov Substitution Principle)

تا حالا شده یه کلاس فرزند بسازید که از یه کلاس پدر ارث‌بری می‌کنه، ولی وقتی ازش به جای پدر استفاده می‌کنید، یهو همه چی به هم می‌ریزه و برنامه رفتار غیرمنتظره‌ای نشون میده؟

اصل جایگزینی لیسکوف (LSP) دقیقاً برای جلوگیری از همین فاجعه طراحی شده.

این اصل چی میگه؟ 🎯
به زبان ساده:

شما باید همیشه بتونید یک نمونه از کلاس فرزند (Subclass) رو به جای یک نمونه از کلاس پدر (Superclass) استفاده کنید، بدون اینکه برنامه به مشکل بخوره یا رفتارش عوض بشه.
یعنی کلاس فرزند نباید "قراردادها" و انتظاراتی که از کلاس پدر میره رو زیر پا بذاره. اگه کلاس پدر قول داده یه کاری رو انجام بده، کلاس فرزند هم باید همون قول رو بده، نه اینکه بزنه زیرش!

مثال عملی: کابوس معروف مربع و مستطیل!
این یه مثال کلاسیکه که نقض LSP رو به بهترین شکل نشون میده.

مثال بد (نقض اصل جایگزینی لیسکوف):
در نگاه اول، به نظر منطقی میاد که Square (مربع) از Rectangle (مستطیل) ارث‌بری کنه، چون مربع یه نوع خاص از مستطیله. ولی این کار، قرارداد رو می‌شکنه!
public class Rectangle
{
public virtual int Width { get; set; }
public virtual int Height { get; set; }
}

public class Square : Rectangle
{
// برای اینکه مربع باقی بمونه، وقتی عرض رو تغییر میدیم،
// باید ارتفاع رو هم تغییر بدیم (و برعکس).
public override int Width
{
set { base.Width = base.Height = value; }
}
public override int Height
{
set { base.Width = base.Height = value; }
}
}


مشکل کجاست؟
حالا یه متدی رو تصور کنید که یه Rectangle می‌گیره و انتظار داره با تغییر عرض، ارتفاع ثابت بمونه. اگه ما بهش یه Square پاس بدیم، کل منطقش به هم می‌ریزه!
public void SomeMethod(Rectangle r)
{
// این متد انتظار داره با تغییر عرض، ارتفاع ثابت بمونه
r.Width = 10;
r.Height = 5;

// اگه r یک مستطیل واقعی باشه، مساحت 50 میشه
// ولی اگه یه مربع بهش پاس داده باشیم، مساحت 25 میشه! (چون ارتفاع هم 5 شده)
// این یعنی رفتار برنامه غیرمنتظره شده!
Console.WriteLine(r.Width * r.Height);
}


اینجا کلاس فرزند (Square) نتونست بدون دردسر جای پدرش (Rectangle) رو بگیره.

مثال خوب (رعایت اصل جایگزینی لیسکوف):
راه حل اینه که به جای وراثت مستقیم، از یه انتزاع مشترک مثل interface استفاده کنیم.
public interface IShape
{
double CalculateArea();
}

public class Rectangle : IShape
{
public int Width { get; set; }
public int Height { get; set; }
public double CalculateArea() => Width * Height;
}

public class Square : IShape
{
public int Side { get; set; }
public double CalculateArea() => Side * Side;
}

حالا دیگه هیچکدوم قرارداد اون یکی رو نمی‌شکنه و هر کدوم زندگی خودشون رو دارن.

🔖 هشتگ‌ها:
#CSharp #Programming #Developer #SOLID #SoftwareArchitecture #CleanCode #LSP
4️⃣ اصل چهارم SOLID: تفکیک اینترفیس‌ها (Interface Segregation Principle)

تا حالا شده یه اینترفیس (Interface) رو پیاده‌سازی کنید و ببینید مجبورید کلی متد رو به صورت خالی یا با پرتاب Exception پیاده‌سازی کنید، چون اصلاً به کار کلاس شما نمیان؟

این مشکل معمولاً از طراحی اینترفیس‌های بزرگ و "چاق" (Fat Interfaces) به وجود میاد. اصل تفکیک اینترفیس‌ها (ISP) برای حل همین مشکل طراحی شده.

این اصل چی میگه؟ 🎯
به زبان ساده:
کلاس‌ها نباید مجبور بشن اینترفیس‌هایی رو پیاده‌سازی کنن که به متدهای اون نیازی ندارن.
به عبارت دیگه، به جای ساختن یک اینترفیس بزرگ و همه‌کاره، بهتره چندین اینترفیس کوچک، تخصصی و مجزا بسازیم.

مثال عملی: کابوس پرینترهای همه‌کاره!
فرض کنید یه اینترفیس برای کار با دستگاه‌های اداری طراحی می‌کنیم.

مثال بد (نقض اصل تفکیک اینترفیس‌ها):
اینجا یه اینترفیس "چاق" داریم. اگه یه پرینتر ساده و ارزون داشته باشیم که فقط قابلیت پرینت داره، باز هم مجبوره متدهای Scan و Fax رو پیاده‌سازی کنه، که این کار منطقی نیست و کد رو کثیف می‌کنه.
//  اینترفیس "چاق" و بد
public interface IMultiFunctionDevice
{
void Print(string content);
void Scan(string content);
void Fax(string content);
}

public class CheapPrinter : IMultiFunctionDevice
{
public void Print(string content)
{
// OK
}

public void Scan(string content)
{
// این پرینتر اسکنر نداره! مجبوریم خطا برگردونیم
throw new NotImplementedException();
}

public void Fax(string content)
{
// این پرینتر فکس هم نداره!
throw new NotImplementedException();
}
}


مثال خوب (رعایت اصل تفکیک اینترفیس‌ها):
حالا میایم و اون اینترفیس بزرگ رو به چند اینترفیس کوچک‌تر و تخصصی‌تر می‌شکنیم.
//  اینترفیس‌های کوچک و تخصصی
public interface IPrinter
{
void Print(string content);
}

public interface IScanner
{
void Scan(string content);
}

حالا هر کلاسی، فقط اینترفیسی رو پیاده‌سازی می‌کنه که واقعاً بهش نیاز داره.
// این پرینتر ساده، فقط قرارداد پرینت رو امضا می‌کنه
public class CheapPrinter : IPrinter
{
public void Print(string content)
{
// OK
}
}

// این دستگاه همه‌کاره، هر دو قرارداد رو امضا می‌کنه
public class AllInOnePrinter : IPrinter, IScanner
{
public void Print(string content)
{
// OK
}

public void Scan(string content)
{
// OK
}
}

کد ما حالا خیلی تمیزتر، انعطاف‌پذیرتر و قابل فهم‌تره!

🔖 هشتگ‌ها:
#CSharp #Programming #Developer #SOLID #SoftwareArchitecture #CleanCode #ISP
5️⃣ اصل پنجم SOLID: وارونگی وابستگی (Dependency Inversion Principle)

تا حالا شده یه کلاس بنویسید که داخلش یه کلاس دیگه رو با new می‌سازید و بعداً برای تغییر اون کلاس داخلی، مجبور میشید کلاس اصلی رو هم دستکاری کنید؟ این یعنی کدهای شما به هم سفت و سخت (Tightly Coupled) وصل شدن.

اصل وارونگی وابستگی (DIP) برای حل همین مشکل و ایجاد کدهای انعطاف‌پذیر طراحی شده.

این اصل چی میگه؟ 🎯
این اصل دو بخش مهم داره:
ماژول‌های سطح بالا نباید به ماژول‌های سطح پایین وابسته باشند. هر دو باید به انتزاع (Abstraction) وابسته باشند.

انتزاع‌ها نباید به جزئیات وابسته باشند. این جزئیات هستن که باید به انتزاع‌ها وابسته باشند.
به زبان ساده: کلاس‌های اصلی و سطح بالای شما (مثلاً بیزنس لاجیک) نباید به جزئیات پیاده‌سازی (مثلاً نحوه کار با دیتابیس یا ارسال ایمیل) وابسته باشن. در عوض، هر دو باید به یک قرارداد مشترک (Interface) وابسته باشن.

مثال عملی: سیستم اطلاع‌رسانی
فرض کنید یه سیستمی برای اطلاع‌رسانی به کاربر داریم.

مثال بد (نقض اصل وارونگی وابستگی):
اینجا کلاس سطح بالای Notification مستقیماً به کلاس سطح پایین EmailSender وابسته است. اگه فردا بخوایم به جای ایمیل، با SMS اطلاع‌رسانی کنیم، مجبوریم کلاس Notification رو تغییر بدیم. این یعنی وابستگی سفت و سخت.
//  این کلاس سطح پایین است
public class EmailSender
{
public void Send() => Console.WriteLine("Email sent!");
}

// این کلاس سطح بالاست و مستقیماً به کلاس پایینی وابسته است
public class Notification
{
private readonly EmailSender _emailSender = new EmailSender();

public void SendNotification()
{
_emailSender.Send();
}
}


مثال خوب (رعایت اصل وارونگی وابستگی):
حالا با استفاده از یک Interface، این وابستگی رو "وارونه" می‌کنیم.

قدم اول: ساختن قرارداد (Interface)
این قرارداد در لایه سطح بالا تعریف میشه.
public interface IMessageSender
{
void SendMessage();
}

قدم دوم: کلاس‌های سطح پایین از قرارداد پیروی می‌کنند
public class EmailSender : IMessageSender
{
public void SendMessage() => Console.WriteLine("Email sent!");
}

public class SmsSender : IMessageSender
{
public void SendMessage() => Console.WriteLine("SMS sent!");
}

قدم سوم: کلاس سطح بالا به قرارداد وابسته است، نه به جزئیات
public class Notification
{
private readonly IMessageSender _sender;

// وابستگی از طریق Constructor تزریق می‌شود (Dependency Injection)
public Notification(IMessageSender sender)
{
_sender = sender;
}

public void SendNotification()
{
_sender.SendMessage();
}
}

جادو اینجا اتفاق میفته: حالا کلاس Notification دیگه کاری نداره که پیام چطوری ارسال میشه (با ایمیل یا SMS). اون فقط "قرارداد" رو می‌شناسه. ما می‌تونیم موقع ساختن آبجکت Notification، هر نوع IMessageSender که دوست داریم رو بهش پاس بدیم، بدون اینکه یک کلمه از کدش رو تغییر بدیم!

🤔 حرف حساب و تجربه شما
این DIP قلب معماری‌های تمیز و مدرنه و اساس کار تزریق وابستگی (Dependency Injection) هست. رعایت این اصل، کد شما رو فوق‌العاده انعطاف‌پذیر، قابل تست و توسعه‌پذیر می‌کنه.

شما چقدر به این اصل در پروژه‌هاتون اهمیت میدید؟ بهترین مثالی که از کاربرد این اصل تو ذهنتون دارید چیه؟

💬 بحث و گفتگوی بیشتر در گروه کامیونیتی:
[C# Geeks Community]

🔖 هشتگ‌ها:
#CSharp #Programming #Developer #SOLID #SoftwareArchitecture #CleanCode #DIP
💡کدام اصل SOLID در کلاس UserManager به وضوح نقض شده است؟
public class UserManager
{
public void RegisterUser(string email, string password)
{
// ۱. فرمت ایمیل را اعتبارسنجی می‌کند
// ۲. چک می‌کند کاربر در دیتابیس وجود دارد یا نه
// ۳. کاربر جدید را در دیتابیس ذخیره می‌کند
// ۴. یک ایمیل خوش‌آمدگویی برای کاربر ارسال می‌کند
}
}
🛡 دستور using در #C: بهترین دوست IDisposable و راهی برای جلوگیری از نشت منابع
وقتی با فایل‌ها، دیتابیس، ارتباطات شبکه یا هر منبع خارجی دیگه‌ای کار می‌کنید، یه خطر بزرگ وجود داره: یادتون بره منابعی که باز کردید رو ببندید! این کار باعث "نشت منابع" (Resource Leaks) و مشکلات جدی در پرفورمنس و پایداری برنامه میشه.

سی‌شارپ یه راه حل خیلی شیک، امن و حرفه‌ای برای این مشکل داره: دستور using.

1️⃣ مشکل کجاست؟ IDisposable و مدیریت منابع
بعضی از آبجکت‌ها در دات‌نت، منابعی رو مدیریت می‌کنن که Garbage Collector به صورت خودکار نمی‌تونه اون‌ها رو آزاد کنه (مثل دستگیره فایل در سیستم‌عامل). این کلاس‌ها، اینترفیس IDisposable رو پیاده‌سازی می‌کنن که فقط یک متد به اسم Dispose() داره.

وظیفه ما به عنوان برنامه‌نویس اینه که همیشه و تحت هر شرایطی، بعد از تموم شدن کارمون با این آبجکت‌ها، متد Dispose() اون‌ها رو صدا بزنیم.

2️⃣ راه حل قدیمی (و زشت): try...finally 👎
راه سنتی برای اینکه مطمئن بشیم Dispose() همیشه صدا زده میشه (حتی اگه وسط کار یه Exception رخ بده)، استفاده از بلوک try...finally هست:
StreamReader reader = null;
try
{
reader = new StreamReader("myFile.txt");
// ... کار کردن با فایل ...
}
finally
{
if (reader != null)
{
reader.Dispose(); // تضمین می‌کنه که فایل همیشه بسته میشه
}
}

این کد کار می‌کنه، ولی طولانی و زشته.

3️⃣ راه حل مدرن و شیک: دستور using
دستور using یه "شکر سینتکسی" (Syntactic Sugar) برای همون بلوک try...finally هست.

به محض اینکه اجرای کد از بلوک using خارج بشه (چه به صورت عادی و چه به خاطر یه Exception)، کامپایلر به صورت خودکار متد Dispose() رو روی اون آبجکت صدا می‌زنه. اینجوری کد شما خیلی تمیزتر، کوتاه‌تر و امن‌تر میشه.
// این کد دقیقاً معادل کد try...finally بالاست
using (StreamReader reader = new StreamReader("myFile.txt"))
{
// ... کار کردن با فایل ...
}
// reader.Dispose() اینجا به صورت خودکار در هر حالتی صدا زده میشه


4️⃣ فرم جدیدتر (از C# 8): using declarations 🚀
از C# 8 به بعد، کار حتی از این هم ساده‌تر شده. اگه متغیر using رو به این شکل تعریف کنید، دیگه نیازی به آکولاد {} ندارید و اون متغیر در انتهای اسکوپ فعلی (معمولاً در انتهای متد) به صورت خودکار Dispose میشه.
void ReadFile()
{
using var reader = new StreamReader("myFile.txt");
using var writer = new StreamWriter("log.txt");

// ... کار کردن با فایل‌ها ...

} // reader.Dispose() و writer.Dispose() اینجا به صورت خودکار صدا زده میشن


🤔 حرف حساب و قانون طلایی

قانون طلایی: هر کلاسی که IDisposable رو پیاده‌سازی کرده، باید داخل یه بلوک using (یا با سینتکس جدیدش) ازش استفاده بشه.

شما بیشتر از کدوم فرمت using استفاده می‌کنید؟ کلاسیک با براکت یا فرمت جدید بدون براکت؟

🔖 هشتگ‌ها:
#CSharp #Programming #Developer #DotNet #BestPractices #CleanCode #IDisposable
😵‍💫 برنامه‌نویس NET.؟

می‌خوای از باگ‌های دردناک، امنیت داغون و کد اسپاگتی در امان بمونی؟
این 10 قاتل خاموش رو دست‌کم نگیر!
(حتی تو پروژه‌های به‌ظاهر «Enterprise» هم می‌بینمشون…)



1️⃣ بدون اعتبارسنجی ورودی؟ خداحافظ با ثبات 😬

داده‌ی خراب بیاد تو، همه‌چی میریزه به هم.
پاک کردن داده‌ی خراب ۱۰ برابر سخت‌تر از جلوگیری از ورودشه!

🧱 همیشه ورودی رو چک کن →
Validate before trust



2️⃣ مقادیر هاردکد شده 🎯

عاشق سرتاسری Replace زدن تو پروژه‌ای؟
از «عدد جادویی» استفاده کن 😎
نه‌؟ پس برو سراغ:
🔸 فایل پیکربندی
🔸 ثابت‌ها
🔸 دیتابیس



3️⃣ وابستگی سفت و سخت بین کلاس‌ها 🔗

وقتی یه کلاس به کلاس دیگه دوخته شده باشه، انعطاف می‌ره هوا.
🙅‍♂️ با abstraction (مثل Interface) کدت رو قابل تغییر نگه دار.



4️⃣ تست واحد؟ وقت ندارم؟ 😐

اگه الان وقت نداری تست بنویسی،
فردا کلی وقت داری باگ‌هات رو دیباگ کنی! 🔥



5️⃣ مدیریت نکردن Exception ها 💣

اگه استک‌تریس کامل رو فرستادی تو پاسخ HTTP، یه کادو به هکر دادی!
📛 ارورها رو لاگ کن.
پیام امن بده به کاربر.



6️⃣ کد غیرقابل خوندن 🤯

واسه انسان بنویس نه ماشین!
اگه باید یه کد رو ۳ بار بخونی تا بفهمی چی می‌گه، خیلی پیچیده‌ست.

🧼 تمیز بنویس تا خودت ۳ ماه دیگه هم بفهمیش!



7️⃣ طراحی ضعیف دیتابیس 🐢

طرح دیتای اشتباه یعنی:
🔹 کندی
🔹 غیرقابل توسعه بودن
🔹 زجر در آینده

بلندمدت فکر کن موقع مدل‌سازی!



8️⃣ امنیت؟ بذار بعداً!

👾 این‌جوری هک می‌شی رفیق!
ورودی‌ها رو بررسی کن
کمترین سطح دسترسی
هرچی می‌اد رو پاکسازی کن (Sanitize)

امنیت چیز اختیاری نیست… هیچ‌وقت!



9️⃣ بدون لاگ و مانیتورینگ 🚫📊

برنامه رو منتشر کردی؟ دم‌ت گرم!
ولی حالا از کجا می‌فهمی داره درست کار می‌کنه؟
🕵️‍♂️ بدون لاگ = پرواز کور



🔟 اختراع دوباره چرخ؟ 😅

مگه قراره ORM اختراع کنی؟
تا وقتی یه ابزار تست‌شده و امن هست،
استفاده‌ش کن
🧠 وقتتو صرف چیزای ارزشمندتر کن!



🎯 لازم نیست کدت بی‌نقص باشه…

ولی اگه اینا رو نادیده بگیری،
کد Legacy درست کردی، نه پروژه حرفه‌ای!



🔖 هشتگ‌ها:
#DotNet #CSharp #CodingTips #BestPractices #Programming #SoftwareEngineering #DevLife
💾 حافظه کلاس‌ها در #C: راهنمای کامل Fields, readonly و const

تا حالا شده از خودتون بپرسید فرق بین یه فیلد readonly و یه const چیه؟ یا اینکه متغیرهایی که داخل یه کلاس تعریف می‌کنیم (فیلدها) چطور و کِی مقداردهی میشن؟

امروز می‌خوایم بریم تو دل کلاس‌ها و با اعضای داده‌ای اون‌ها و قوانین حاکم بر اون‌ها آشنا بشیم.

1️⃣ فیلدها (Fields):

حافظه داخلی یک آبجکت فیلدها، متغیرهایی هستن که داخل یه کلاس یا struct تعریف میشن و "وضعیت" یا "داده"‌های اون آبجکت رو در طول عمرش نگه می‌دارن. هر نمونه (instance) از کلاس، یک کپی مستقل از فیلدهای خودش رو داره.

💡نکته حرفه‌ای (قرارداد نام‌گذاری):
یه قرارداد رایج و خوب، استفاده از آندرلاین (_) برای فیلدهای private هست تا به راحتی از متغیرهای محلی و پارامترها تشخیص داده بشن (مثلاً name_).
مقداردهی اولیه فیلدها:
مقداردهی اولیه به فیلدها اختیاریه. اگه مقداری بهشون ندید، مقدار پیش‌فرض نوعشون رو می‌گیرن. نکته مهم اینه که این مقداردهی قبل از اجرای اولین خط کد در سازنده (constructor) انجام میشه.

2️⃣ کلید readonly: فیلدهای فقط-خواندنی 🔒

اگه می‌خواید یه فیلد فقط یک بار مقداردهی بشه و بعد از ساخته شدن آبجکت دیگه هرگز تغییر نکنه، از readonly استفاده کنید. این برای مقادیری که در زمان ساخت آبجکت مشخص میشن ولی بعدش باید ثابت بمونن، عالیه.

قانون مهم: فیلدهای readonly فقط در دو جا می‌تونن مقدار بگیرن: یا موقع تعریف اولیه، یا داخل سازنده (constructor) اون کلاس.
public class UserProfile
{
// مقداردهی موقع تعریف
public readonly Guid UserId = Guid.NewGuid();

// تعریف بدون مقدار اولیه
public readonly DateTime RegistrationDate;

public UserProfile()
{
// مقداردهی در سازنده
RegistrationDate = DateTime.UtcNow;
}

public void UpdateProfile()
{
// UserId = Guid.NewGuid(); // خطای زمان کامپایل!
}
}


3️⃣ دوئل بزرگ: const در برابر static readonly ⚔️

این یکی از کلاسیک‌ترین سوالات مصاحبه و یکی از مهم‌ترین نکات در طراحی حرفه‌ایه. هر دو برای مقادیر ثابت به کار میرن، ولی تفاوت‌های حیاتی دارن.

🔵 const (ثابت زمان کامپایل):

• مقدارش باید در زمان کامپایل مشخص و ثابت باشه
(const double Pi = 3.14;).
• کامپایلر مقدارش رو مستقیماً در کد جایگزین می‌کنه (مثل ماکرو).
• فقط برای انواع داده ساده (عددی، رشته، بولین و...) قابل استفاده‌ست.

🔴 static readonly (ثابت زمان اجرا):

• مقدارش در زمان اجرا (معمولاً موقع استارت برنامه) مشخص میشه.
• یه فیلد واقعیه و در حافظه نگهداری میشه، مقدارش جایگزین نمیشه.
• برای هر نوع داده‌ای، حتی آبجکت‌های پیچیده، قابل استفاده‌ست.
public static readonly DateTime StartupTime = DateTime.Now;

😈تله خطرناک public const:

اگه شما یه public const رو در یک کتابخانه (Assembly A) تعریف کنید و در یک پروژه دیگه (Assembly B) ازش استفاده کنید، مقدار اون ثابت موقع کامپایل در Assembly B "پخته" یا "bake in" میشه. حالا اگه شما مقدار const رو در Assembly A عوض کنید، تا وقتی که Assembly B رو دوباره کامپایل نکنید، همچنان از همون مقدار قدیمی استفاده خواهد کرد! این می‌تونه منجر به باگ‌های فاجعه‌بار بشه. static readonly این مشکل رو نداره و همیشه آخرین مقدار رو می‌خونه، بنابراین انتخاب امن‌تریه.

🤔 حرف حساب و تجربه شما

انتخاب درست بین const و static readonly یکی از نشانه‌های یه توسعه‌دهنده باتجربه‌ست که به نگهداری بلندمدت کد فکر می‌کنه.
شما بیشتر از const استفاده می‌کنید یا static readonly؟ آیا تا حالا به مشکل اسمبلی با const برخورد کرده بودید؟

🔖 هشتگ‌ها:
#CSharp #Programming #Developer #DotNet #OOP #CleanCode #BestPractices