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
🛡 دستور 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
💾 حافظه کلاسها در #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
🛠 متدهای مدرن در #C
Expression-bodied و Local Methods
سیشارپ مدرن، پر از قابلیتهای شیک و کاربردیه که به ما کمک میکنه کدهای کوتاهتر، خواناتر و منظمتری بنویسیم. امروز با دو تا از این تکنیکهای خفن برای کار با متدها آشنا میشیم.
1️⃣ متدهای Expression-bodied (=>): خداحافظی با return و {}
اگه متد شما فقط از یک عبارت (Expression) تشکیل شده، دیگه نیازی به نوشتن بلوک {} و کلمه کلیدی return ندارید! میتونید با استفاده از "فت ارو" (=>)، اون رو در یک خط بنویسید.
این کار، کد شما رو فوقالعاده تمیز و مینیمال میکنه.
// روش قدیمی
int Double(int x)
{
return x * 2;
}
// روش مدرن و شیک با Expression-bodied
int DoubleModern(int x) => x * 2;
// این قابلیت برای متدهای void هم کار میکنه
void SayHello() => Console.WriteLine("Hello!");
2️⃣ متدهای محلی (Local Methods): متد در دل متد!
گاهی وقتا یه منطق کمکی دارید که فقط و فقط داخل یک متد دیگه استفاده میشه. به جای اینکه اون رو به عنوان یه متد private تو کل کلاس تعریف کنید و کلاس رو شلوغ کنید، میتونید اون رو به عنوان یه متد محلی، دقیقاً داخل همون متدی که بهش نیاز دارید، تعریف کنید.
مزایای این کار:
• کپسولهسازی عالی: اون متد کمکی، فقط همونجا قابل دسترسه و هیچ جای دیگهای از کلاس رو آلوده نمیکنه.
• دسترسی به متغیرهای محلی: متد محلی میتونه به متغیرهای محلی و پارامترهای متد بیرونی دسترسی داشته باشه.
void WriteCubes()
{
Console.WriteLine(Cube(3));
Console.WriteLine(Cube(4));
// این متد، فقط داخل WriteCubes قابل دسترسه
int Cube(int value) => value * value * value;
}
💡نکته حرفهای (static local methods):
از C# 8 به بعد، اگه متد محلی شما نیازی به دسترسی به متغیرهای متد بیرونی نداره، میتونید اون رو با کلمه کلیدی static تعریف کنید. این کار به کامپایلر کمک میکنه بهینهسازیهای بهتری انجام بده و جلوی دسترسیهای ناخواسته رو هم میگیره.
🤔 حرف حساب و تجربه شما
این تکنیکهای مدرن، ابزارهای شما برای نوشتن کدهایی هستن که هم کار میکنن و هم خوندنشون لذتبخشه.
شما از کدوم یکی از این قابلیتها بیشتر استفاده میکنید؟ آیا متدهای Expression-bodied جزو استایل کدنویسی شما هستن؟
🔖 هشتگها:
#CSharp #Programming #Developer #DotNet #CleanCode #ModernCSharp #BestPractices
🏛️ مونولیت ماژولار چیست؟ معماری هوشمندانهای که قبل از میکروسرویس باید بشناسیدهمیشه بحث بوده: سادگی و یکپارچگی مونولیت یا انعطافپذیری و قدرت میکروسرویس؟ اما اگه یه راه سومی وجود داشته باشه که بهترین مزایای هر دو رو داشته باشه چی؟
با معماری مونولیت ماژولار (Modular Monolith) آشنا بشید؛ رویکردی که سادگی مونولیت رو با نظم و مرزبندی میکروسرویس ترکیب میکنه.
1️⃣ مونولیت ماژولار به زبان ساده چیه؟ 🧱
تصور کنید به جای ساختن یه ساختمون غولپیکر یکتکه (مونولیت سنتی)، یا چندین ساختمون کاملاً جدا از هم (میکروسرویس)، شما یک مجتمع آپارتمانی بزرگ میسازید.
کل مجتمع یک واحد یکپارچه است، ولی داخلش به واحدهای مستقل و ایزوله با دیوارهای محکم تقسیم شده. این واحدها همون ماژولها هستن (مثلاً ماژول پرداخت، ماژول کاربران). هر ماژول کارکردهای مرتبط به خودش رو گروهبندی میکنه و مرزهای مشخصی داره.
💡نکته کلیدی: این واحدها (ماژولها) اتصال سستی (loosely coupled) با هم دارن و فقط از طریق یک API عمومی و مشخص با هم صحبت میکنن، نه اینکه در جزئیات پیادهسازی هم دخالت کنن.
انعطافپذیری در عمل: فرض کنید در فصل تعطیلات، ماژول رزرو شما نیاز به منابع بیشتری داره. در این معماری، شما میتونید موقتاً همون ماژول رو به صورت مستقل دیپلوی کنید و بعداً دوباره به مونولیت اصلی برش گردونید!
2️⃣ چرا "Monolith First"؟ (حتی به گفته بزرگان!) 💡
با اینکه میکروسرویسها خیلی محبوبن، پیچیدگیهای سیستمهای توزیعشده (Distributed Systems) رو به همراه دارن. برای همین، خیلی از متخصصان، از جمله مارتین فاولر، میگن:
"شما نباید یک پروژه جدید را با میکروسرویسها شروع کنید، حتی اگر مطمئن باشید که اپلیکیشن شما به اندازهای بزرگ خواهد شد که این کار ارزشش را داشته باشد."حتی گوگل هم در مقالات تحقیقاتی جدیدش، به ترند مونولیت ماژولار پیوسته. دلیل این امر، چالشهای ذاتی میکروسرویسهاست:
1️⃣ پرفورمنس: سربار شبکه و سریالسازی دادهها، عملکرد رو کاهش میده.
2️⃣ صحت: تضمین صحت یک سیستم توزیعشده بسیار دشواره.
3️⃣ مدیریت: شما باید چندین اپلیکیشن مختلف با چرخههای انتشار متفاوت رو مدیریت کنید.
4️⃣ بعدی APIهای منجمد: تغییر APIها بدون شکستن سرویسهای دیگه، سخت میشه.
5️⃣ سرعت توسعه: یک تغییر کوچک در یک سرویس، ممکنه نیازمند تغییر و هماهنگی در چندین سرویس دیگه باشه.
حالا که فهمیدیم مونولیت ماژولار چیه و چرا یک انتخاب هوشمندانهست، در قسمت بعدی این سری به صورت عمیق به مزایای مشخص اون میپردازیم و اون رو مستقیماً با میکروسرویسها مقایسه میکنیم.
🔖 هشتگها:
#SoftwareArchitecture #CSharp #DotNet #Monolith #Microservices #Developer #CleanCode
⚖️ دوئل معماری: مزایای مونولیت ماژولار در برابر میکروسرویسها
حالا بیاید ببینیم این معماری در عمل چه مزایای ملموسی داره و تفاوت اصلیش با میکروسرویسها چیه.
1️⃣ مهمترین مزایای مونولیت ماژولار ✅
🚀 استقرار سادهشده:
برخلاف میکروسرویسها که نیازمند زیرساخت و استراتژیهای پیچیده هستن، یک مونولیت ماژولار به سادگی و به عنوان یک واحد دیپلوی میشه.
⚡️ پرفورمنس بالا:
ارتباط بین ماژولها به صورت درون-فرآیندی (in-process) و مستقیمه. این یعنی هیچ تأخیر شبکه یا سربار سریالسازی دادهای که در میکروسرویسها وجود داره، اینجا نیست.
💻 سرعت توسعه بالا:
شما با یک پایگاه کد واحد سر و کار دارید. این کار دیباگ کردن، تست و تجربه کلی توسعه رو به شدت ساده و سریع میکنه.
🔗 مدیریت تراکنش آسان:
مدیریت تراکنشها در سیستمهای توزیعشده یه کابوس واقعیه. اما در مونولیت ماژولار، چون ماژولها میتونن از یک دیتابیس مشترک استفاده کنن، این کار خیلی سادهتره.
🛠️ پیچیدگی عملیاتی کمتر:
شما فقط یک اپلیکیشن رو مدیریت، مانیتور و دیپلوی میکنید، نه دهها سرویس مختلف رو.
🌱 مسیر هموار به سمت میکروسرویس:
اگه در آینده نیاز شد، به راحتی میتونید یه ماژول رو از ساختار اصلی جدا کنید و به یه سرویس مستقل تبدیلش کنید! این بزرگترین مزیت بلندمدت این معماریه.
2️⃣ مقایسه نهایی: مونولیت ماژولار در برابر میکروسرویسها
بزرگترین تفاوت در نحوه استقرار (Deployment) اونهاست. میکروسرویسها مرزهای منطقی داخل یه مونولیت ماژولار رو به مرزهای فیزیکی ارتقا میدن.
• مونولیت ماژولار به شما میده:
انسجام بالا، اتصال سست، کپسولهسازی داده و تمرکز بر کارکردهای بیزینسی.
•میکروسرویسها به شما میده:
تمام موارد بالا + دیپلوی مستقل، مقیاسپذیری مستقل و توانایی استفاده از پشتههای فناوری (tech stacks) مختلف برای هر سرویس.
3️⃣حرف آخر: نقل قول طلایی 🎯
همونطور که سایمون براون میگه:
"میکروسرویسها را به خاطر مزایایشان انتخاب کنید، نه به این دلیل که پایگاه کد مونولیت شما یک فاجعه است."
مونولیت ماژولار به شما اجازه میده ابتدا خانهی خود را مرتب کنید و بعداً تصمیم بگیرید که آیا واقعاً به چندین خانه جداگانه نیاز دارید یا نه.
🤔 نظر شما چیه؟
شما تو پروژههاتون تجربه کار با مونولیت ماژولار رو داشتید؟ به نظرتون بزرگترین مزیت یا چالش این معماری چیه؟[منبع]
🔖 هشتگها:
#SoftwareArchitecture #CSharp #DotNet #Monolith #Microservices #Developer #CleanCode
✨ ساخت آبجکت مثل یک حرفهای: قدرت Object Initializers در #C
یادتونه قدیما برای ساختن و مقداردهی یه آبجکت، باید چند خط کد پشت سر هم مینوشتیم؟ این روش هم طولانیه و هم ممکنه باعث بشه مقداردهی بعضی پراپرتیها رو فراموش کنیم.
سیشارپ یه راه حل خیلی شیک، مدرن و امن برای این کار داره: Object Initializers.
1️⃣ روش سنتی در برابر روش مدرن
فرض کنید این کلاس Bunny رو داریم:
public class Bunny
{
public string Name;
public bool LikesCarrots;
public bool LikesHumans;
public Bunny() {}
public Bunny(string n) => Name = n;
}
روش قدیمی و چند خطی: 👎
Bunny b1 = new Bunny();
b1.Name = "Bo";
b1.LikesCarrots = true;
b1.LikesHumans = false;
روش مدرن با Object Initializer: 👍
حالا با سینتکس {}، میتونیم تمام این کارها رو در یک دستور و به صورت خیلی خوانا انجام بدیم:
Bunny b2 = new Bunny
{
Name = "Bo",
LikesCarrots = true,
LikesHumans = false
};
این قابلیت حتی با سازندههایی که پارامتر دارن هم کار میکنه:
Bunny b3 = new Bunny("Bo")
{
LikesCarrots = true,
LikesHumans = false
};2️⃣ پشت صحنه چه خبره؟ (نکته حرفهای) ⚙️
شاید فکر کنید این سینتکس فقط یه خلاصه نویسیه، ولی کامپایلر پشت صحنه یه کار هوشمندانه برای امنیت در برابر خطاها (Exception Safety) انجام میده.
اون اول آبجکت رو تو یه متغیر موقت میسازه و بعد پراپرتیها رو ست میکنه. این کار تضمین میکنه که اگه وسط مقداردهی یکی از پراپرتیها خطایی رخ بده، شما هیچوقت با یه آبجکت نصفه و نیمه و ناقص مواجه نمیشید!
🔖 هشتگها:
#CSharp #Programming #Developer #DotNet #OOP #CleanCode #BestPractices
🔬 کالبدشکافی آبجکتها با Deconstructors در #C
تو پست قبلی دیدیم چطور با Object Initializers یه آبجکت رو به صورت شیک "بسازیم". حالا بیاید ببینیم چطور میتونیم یه آبجکت رو به همون زیبایی "بشکافیم" و اجزاش رو استخراج کنیم!
سیشارپ یه قابلیت مدرن و قدرتمند به اسم Deconstructor داره که دقیقاً برعکس سازنده (Constructor) عمل میکنه.
1️⃣ حالا Deconstructor چیست؟
این Deconstructor یه متد خاص به اسم Deconstruct هست که شما تو کلاستون تعریف میکنید. این متد، فیلدها و پراپرتیهای کلاس شما رو به مجموعهای از متغیرهای خروجی (out parameters) تبدیل میکنه.
public class Rectangle
{
public readonly float Width, Height;
public Rectangle(float width, float height)
{
Width = width;
Height = height;
}
// این متد Deconstructor ماست
public void Deconstruct(out float width, out float height)
{
width = Width;
height = Height;
}
}
2️⃣ جادوی سینتکس: کالبدشکافی در یک خط! ✨
حالا که متد Deconstruct رو داریم، #C به ما یه سینتکس فوقالعاده شیک و خوانا برای استفاده ازش میده:
var rect = new Rectangle(3, 4);
// جادو اینجا اتفاق میفته!
// این خط، متد Deconstruct رو صدا میزنه
(float width, float height) = rect;
Console.WriteLine($"Width: {width}, Height: {height}"); // خروجی: Width: 3, Height: 4
این سینتکس، کد شما رو به شدت تمیز و بیانگر میکنه.
3️⃣ ترفندهای خلاصهنویسی
این قابلیت چند تا ترفند برای خلاصهتر شدن هم داره:
استفاده از var:
// کامپایلر خودش نوعها رو تشخیص میده
var (width, height) = rect;
نادیده گرفتن با _ (Discard):
اگه فقط به یکی از مقادیر نیاز دارید، میتونید اون یکی رو با _ نادیده بگیرید:
// ما اینجا فقط به ارتفاع نیاز داریم
var (_, height) = rect;
تخصیص به متغیرهای موجود:
اگه متغیرها رو از قبل دارید، دیگه نیازی به تعریفشون نیست:
float w, h;
(w, h) = rect;
🤔 حرف حساب و تجربه شما
شما تا حالا از Deconstructors تو کدهاتون استفاده کردید؟ به نظرتون بهترین کاربرد این قابلیت کجاست؟
🔖 هشتگها:
#CSharp #Programming #Developer #DotNet #ModernCSharp #CleanCode #BestPractices
مدیریت متمرکز پکیجها (CPM) در NET. : یک بار برای همیشه! ✨
روزهایی رو به یاد میارم که مدیریت پکیجهای NuGet در چندین پروژه یک دردسر واقعی بود. میدونید چی میگم - یه سولوشن بزرگ رو باز میکنی و میبینی هر پروژه از یه نسخه متفاوت از همون پکیج استفاده میکنه. اصلاً جالب نیست! 😫
بذارید بهتون نشون بدم که چطور مدیریت متمرکز پکیجها (CPM) در NET. میتونه این مشکل رو یک بار برای همیشه حل کنه.
مشکلی که باید حل کنیم 💥
من اغلب با سولوشنهایی کار میکنم که پروژههای زیادی دارن. سولوشنهایی با ۳۰ یا بیشتر پروژه غیرمعمول نیستن. هر کدوم به پکیجهای مشابهی مثل Serilog یا Polly نیاز دارن. قبل از CPM، پیگیری نسخههای پکیج یه افتضاح بود:
🔹 یک پروژه از Serilog 4.1.0 استفاده میکرد.
🔹 دیگری از Serilog 4.0.2.
🔹 و یه جوری، سومی از Serilog 3.1.1!
نسخههای مختلف میتونن رفتار متفاوتی داشته باشن که منجر به باگهای عجیبی میشه که پیدا کردنشون سخته. من ساعتهای زیادی رو برای رفع مشکلات ناشی از عدم تطابق نسخهها تلف کردهام.
مدیریت متمرکز پکیجها چگونه کمک میکند؟ 🎮
CPM
رو مثل یک مرکز کنترل برای تمام نسخههای پکیجتون در نظر بگیرید. به جای تنظیم نسخهها در هر پروژه، اونها رو یک بار در یک جا تنظیم میکنید. بعد، فقط به پکیجی که میخواید استفاده کنید، بدون مشخص کردن نسخه، ارجاع میدید. به همین سادگی!
برای استفاده از مدیریت متمرکز پکیجها به این موارد نیاز دارید:
✅ NuGet نسخه 6.2 یا جدیدتر
✅ .NET SDK نسخه 6.0.300 یا جدیدتر
✅ اگر از ویژوال استودیو استفاده میکنید، نسخه 2022 17.2 یا جدیدتر
راهاندازی آن 📁
اول، یک فایل به نام Directory.Packages.props در پوشه اصلی سولوشن خود ایجاد کنید:
<Project>
<PropertyGroup>
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
</PropertyGroup>
<ItemGroup>
<PackageVersion Include="Newtonsoft.Json" Version="13.0.3" />
<PackageVersion Include="Serilog" Version="4.1.0" />
<PackageVersion Include="Polly" Version="8.5.0" />
</ItemGroup>
</Project>
در فایلهای پروژهتون، میتونید پکیجها رو با استفاده از PackageReference بدون نسخه لیست کنید:
<ItemGroup>
<PackageReference Include="Newtonsoft.Json" />
<PackageReference Include="AutoMapper" />
<PackageReference Include="Polly" />
</ItemGroup>
همین! حالا تمام پروژههای شما از یک نسخه پکیج یکسان استفاده خواهند کرد.
✨️کارهای باحالی که میتونید انجام بدید
نیاز به نسخهای متفاوت برای یک پروژه دارید؟ 🎯
مشکلی نیست! فقط این رو به فایل پروژهتون اضافه کنید:
<PackageReference Include="Serilog" VersionOverride="3.1.1" />
📍پراپرتی VersionOverride به شما اجازه میده نسخه خاصی که میخواید رو تعریف کنید.
یه پکیج رو تو همه پروژهها میخواید؟ 🌍
اگه پکیجهایی دارید که هر پروژهای بهشون نیاز داره، میتونید اونها رو سراسری کنید. یک GlobalPackageReference در فایل props خود تعریف کنید:
<ItemGroup>
<GlobalPackageReference Include="SonarAnalyzer.CSharp" Version="10.3.0.106239" />
</ItemGroup>
حالا هر پروژهای به طور خودکار این پکیج رو دریافت میکنه!
مهاجرت پروژههای موجود به CPM 🚚
1️⃣ فایل Directory.Packages.props رو در ریشه سولوشن ایجاد کنید.
2️⃣ تمام نسخههای پکیج رو از فایلهای .csproj خود به اونجا منتقل کنید.
3️⃣ ویژگی Version رو از عناصر PackageReference حذف کنید.
4️⃣ سولوشن خود را بیلد کرده و هرگونه تداخل نسخه رو برطرف کنید.
5️⃣ قبل از کامیت کردن، به طور کامل تست کنید.
همچنین یه ابزار CLI به نام CentralisedPackageConverter وجود داره که میتونید برای اتوماتیک کردن مهاجرت ازش استفاده کنید.
چه زمانی باید از CPM استفاده کنید؟ 🤔
من دلیل قانعکنندهای برای استفاده نکردن پیشفرض از این قابلیت نمیبینم.
من توصیه میکنم از CPM استفاده کنید وقتی:
• پروژههای زیادی دارید که پکیجهای مشترک دارن.
• از رفع باگهای مربوط به نسخه خسته شدهاید.
• میخواید مطمئن بشید همه از نسخههای یکسان استفاده میکنن.
جمعبندی 📝
نکات من برای موفقیت با مدیریت متمرکز پکیجها:
💡 وقتی CPM رو به یه سولوشن موجود اضافه میکنید، این کار رو در یک change/PR جداگانه انجام بدید.
💡 اگه نسخهای رو override میکنید، یه کامنت بذارید که دلیلش رو توضیح بده.
💡 نسخههای پکیج خود را به طور منظم برای آپدیتها چک کنید.
💡 فقط پکیجهایی رو سراسری کنید که واقعاً همه جا بهشون نیاز دارید.
🔖 هشتگها:
#CSharp #DotNet #NuGet #DependencyManagement #BestPractices #CleanCode #Developer #VisualStudio
📖 سری آموزشی کتاب C# 12 in a Nutshell
تو پست های قبلی با پراپرتیهای پایه آشنا شدیم. حالا وقتشه بریم سراغ چند تا تکنیک پیشرفتهتر و مدرن که به شما کنترل کامل روی دادههاتون میده و کدهاتون رو حرفهایتر میکنه.
در #C مدرن، شما میتونید مستقیماً به پراپرتیهای خودکار (auto-properties) مقدار اولیه بدید، دقیقاً مثل فیلدها. این کار کد رو خیلی تمیزتر و خواناتر میکنه و دیگه نیازی نیست برای مقداردهیهای ساده، حتماً سازنده (constructor) بنویسید.
گاهی وقتا میخواید یه پراپرتی از بیرون کلاس فقط-خواندنی باشه، ولی از داخل خود کلاس بتونید مقدارش رو تغییر بدید (مثلاً برای شمارندهها یا تغییر وضعیت داخلی). اینجاست که private set وارد میشه.
این یکی از مهمترین قابلیتهای #C مدرن برای ساخت آبجکتهای تغییرناپذیر (Immutable) هست.
پراپرتی init فقط موقع ساخت آبجکت (در سازنده یا با Object Initializer) قابل مقداردهیه و بعد از اون کاملاً قفل و فقط-خواندنی میشه.
اگه شما یه پراپرتی
این تکنیکها ابزارهای شما برای پیادهسازی اصل کپسولهسازی و ساختن کلاسهای امن، تغییرناپذیر و قابل نگهداری هستن.
✨ تکنیکهای حرفهای پراپرتی در #C: از private set تا init
تو پست های قبلی با پراپرتیهای پایه آشنا شدیم. حالا وقتشه بریم سراغ چند تا تکنیک پیشرفتهتر و مدرن که به شما کنترل کامل روی دادههاتون میده و کدهاتون رو حرفهایتر میکنه.
1️⃣ مقداردهی اولیه پراپرتیها (Property Initializers)
در #C مدرن، شما میتونید مستقیماً به پراپرتیهای خودکار (auto-properties) مقدار اولیه بدید، دقیقاً مثل فیلدها. این کار کد رو خیلی تمیزتر و خواناتر میکنه و دیگه نیازی نیست برای مقداردهیهای ساده، حتماً سازنده (constructor) بنویسید.
public class ServerConfig
{
// مقداردهی اولیه یک پراپرتی خواندنی-نوشتنی
public string IpAddress { get; set; } = "127.0.0.1";
// مقداردهی اولیه یک پراپرتی فقط-خواندنی
public int Port { get; } = 8080;
public ServerConfig(int port)
{
// شما همچنان میتونید مقدار پراپرتی فقط-خواندنی رو در سازنده هم تغییر بدید
Port = port;
}
}
2️⃣ کنترل دسترسی با private set
گاهی وقتا میخواید یه پراپرتی از بیرون کلاس فقط-خواندنی باشه، ولی از داخل خود کلاس بتونید مقدارش رو تغییر بدید (مثلاً برای شمارندهها یا تغییر وضعیت داخلی). اینجاست که private set وارد میشه.
public class Counter
{
public int Value { get; private set; } = 0;
public void Increment()
{
// از داخل کلاس قابل تغییر است
Value++;
}
}
// --- نحوه استفاده ---
var counter = new Counter();
counter.Increment(); // Value is now 1
// counter.Value = 10; // ❌ خطای زمان کامپایل! از بیرون قابل تغییر نیست
3️⃣ انقلاب تغییرناپذیری با init (از 9 #C) 🔒
این یکی از مهمترین قابلیتهای #C مدرن برای ساخت آبجکتهای تغییرناپذیر (Immutable) هست.
پراپرتی init فقط موقع ساخت آبجکت (در سازنده یا با Object Initializer) قابل مقداردهیه و بعد از اون کاملاً قفل و فقط-خواندنی میشه.
public class Note
{
public int Pitch { get; init; } = 20;
public int Duration { get; init; } = 100;
}
// --- نحوه استفاده ---
// موقع ساخت، با Object Initializer قابل تغییره
var note = new Note { Pitch = 50 };
// بعد از ساخته شدن، دیگه نمیشه تغییرش داد
// note.Pitch = 200; // ❌ خطای زمان کامپایل!
نکته کلیدی برای کتابخانهنویسها: ⚠️
اگه شما یه پراپرتی
init جدید به کلاستون اضافه کنید، کدهای قدیمی که از کتابخونه شما استفاده میکنن، نمیشکنن\! ولی اگه یه پارامتر جدید (حتی اختیاری) به سازنده اضافه کنید، اون کدها دچار Binary Breaking Change میشن و باید دوباره کامپایل بشن. این باعث میشه init برای تکامل APIها خیلی امنتر باشه.🤔 حرف حساب و تجربه شما
این تکنیکها ابزارهای شما برای پیادهسازی اصل کپسولهسازی و ساختن کلاسهای امن، تغییرناپذیر و قابل نگهداری هستن.
🔖 هشتگها:
#CSharp #DotNet #OOP #Properties #CleanCode #Immutability
📖 سری آموزشی کتاب C# 12 in a Nutshell
این الگو که توش فقط پارامترهای ورودی به فیلدها اختصاص داده میشن، خیلی رایجه. خبر خوب اینه که از 12 #C، با Primary Constructors (سازندههای اصلی) میتونیم این کار رو خیلی شیک و کوتاه انجام بدیم!
این یه سینتکس جدیده که به شما اجازه میده پارامترهای سازنده اصلی رو مستقیماً بعد از اسم کلاس و داخل پرانتز تعریف کنید. کامپایلر به صورت خودکار یک سازنده بر اساس این پارامترها میسازه.
روش قدیمی: 👎
روش مدرن با Primary Constructor: 👍
پارامترها در کل کلاس قابل دسترسن: برخلاف سازنده معمولی که پارامترهاش فقط داخل همون بلوک زنده هستن، پارامترهای Primary Constructor در تمام بدنه کلاس قابل دسترسی هستن.
• سازنده اصلی: اگه سازندههای دیگهای تو کلاس داشته باشید، باید با : this(...) سازنده اصلی (Primary) رو صدا بزنن.
• حذف سازنده پیشفرض: با تعریف سازنده اصلی، دیگه سازنده پیشفرض بدون پارامتر به صورت خودکار ساخته نمیشه.
رکوردها هم Primary Constructor دارن، ولی با یه تفاوت بزرگ: کامپایلر برای رکوردها، به صورت پیشفرض برای هر پارامتر، یه پراپرتی public init-only هم میسازه.
اما برای classها این اتفاق نمیفته و پارامترها به صورت private باقی میمونن (مگر اینکه خودتون به صورت دستی یک پراپرتی عمومی براشون بسازید).
این قابلیت برای سناریوهای ساده عالیه، ولی محدودیتهایی هم داره: شما نمیتونید منطق اضافهای (مثل اعتبارسنجی) رو مستقیم به خود سازنده اصلی اضافه کنید و برای این کارها باید از سازندههای سنتی استفاده کنید.
✨ خداحافظی با کدهای تکراری: معرفی Primary Constructors در 12 #Cاز نوشتن این همه کد تکراری تو سازندهها خسته شدید؟
public MyClass(string name) { this.name = name; }این الگو که توش فقط پارامترهای ورودی به فیلدها اختصاص داده میشن، خیلی رایجه. خبر خوب اینه که از 12 #C، با Primary Constructors (سازندههای اصلی) میتونیم این کار رو خیلی شیک و کوتاه انجام بدیم!
1️⃣ Primary Constructor چیست و چطور کار میکند؟
این یه سینتکس جدیده که به شما اجازه میده پارامترهای سازنده اصلی رو مستقیماً بعد از اسم کلاس و داخل پرانتز تعریف کنید. کامپایلر به صورت خودکار یک سازنده بر اساس این پارامترها میسازه.
روش قدیمی: 👎
class Person
{
string firstName, lastName; // تعریف فیلدها
public Person(string firstName, string lastName) // تعریف سازنده
{
this.firstName = firstName; // اختصاص به فیلدها
this.lastName = lastName;
}
public void Print() => Console.WriteLine(firstName + " " + lastName);
}
روش مدرن با Primary Constructor: 👍
class Person(string firstName, string lastName)
{
public void Print() => Console.WriteLine(firstName + " " + lastName);
}
// استفاده ازش هم مثل قبل سادست
Person p = new Person("Alice", "Jones");
p.Print(); // Alice Jones
2️⃣ قوانین و نکات کلیدی ⚖️
پارامترها در کل کلاس قابل دسترسن: برخلاف سازنده معمولی که پارامترهاش فقط داخل همون بلوک زنده هستن، پارامترهای Primary Constructor در تمام بدنه کلاس قابل دسترسی هستن.
• سازنده اصلی: اگه سازندههای دیگهای تو کلاس داشته باشید، باید با : this(...) سازنده اصلی (Primary) رو صدا بزنن.
• حذف سازنده پیشفرض: با تعریف سازنده اصلی، دیگه سازنده پیشفرض بدون پارامتر به صورت خودکار ساخته نمیشه.
3️⃣ کلاس با Primary Constructor در برابر record 🆚
رکوردها هم Primary Constructor دارن، ولی با یه تفاوت بزرگ: کامپایلر برای رکوردها، به صورت پیشفرض برای هر پارامتر، یه پراپرتی public init-only هم میسازه.
اما برای classها این اتفاق نمیفته و پارامترها به صورت private باقی میمونن (مگر اینکه خودتون به صورت دستی یک پراپرتی عمومی براشون بسازید).
4️⃣ محدودیتها: کی ازش استفاده نکنیم؟ ⚠️
این قابلیت برای سناریوهای ساده عالیه، ولی محدودیتهایی هم داره: شما نمیتونید منطق اضافهای (مثل اعتبارسنجی) رو مستقیم به خود سازنده اصلی اضافه کنید و برای این کارها باید از سازندههای سنتی استفاده کنید.
🔖 هشتگها:
#CSharp #DotNet #CSharp12 #Developer #OOP #CleanCode
📖 سری آموزشی کتاب C# 12 in a Nutshell
تا حالا شده یه فایل کد اونقدر بزرگ بشه که پیدا کردن یه متد توش عذابآور باشه؟ یا بخواید کدی که خودتون نوشتید رو از کدی که به صورت خودکار توسط یه ابزار (مثل دیزاینر) تولید شده، جدا کنید؟
سیشارپ برای این کار یه راه حل عالی و تمیز داره: کلمه کلیدی partial.
یک کلاس در چند فایل کلمه کلیدی partial به شما اجازه میده تعریف یک کلاس، struct یا اینترفیس رو در چند فایل مختلف تقسیم کنید. کامپایلر موقع کامپایل، تمام این تیکهها رو به هم میچسبونه و به عنوان یک کلاس واحد در نظر میگیره.
مهمترین کاربرد: جداسازی کدهای دستنویس شما از کدهای اتوماتیک.
فایل ۱ (اتوماتیک): PaymentForm.g.cs
فایل ۲ (دستنویس): PaymentForm.cs
این قابلیت به شما اجازه میده در یک بخش از کلاس (معمولاً کد اتوماتیک)، یک "قلاب" یا تعریف متد بدون بدنه بذارید. بعداً در بخش دیگه کلاس (کد دستنویس)، میتونید اون متد رو پیادهسازی کنید.
💡نکته جادویی: اگه شما هیچ پیادهسازیای براش ارائه ندید، کامپایلر هم تعریف و هم تمام فراخوانیهای اون متد رو از کد نهایی حذف میکنه! این یعنی هیچ هزینه پرفورمنسی نداره.
فایل ۱ (اتوماتیک):
فایل ۲ (دستنویس):
این نسخه جدیدتر، برای کار با Source Generatorها طراحی شده. در این حالت، شما تعریف متد رو مینویسید و انتظار دارید که Source Generator بدنه اون رو تولید کنه. این متدها دیگه اختیاری نیستن و باید پیادهسازی بشن و میتونن خروجی و پارامتر out هم داشته باشن.
🏗 تقسیم کدهای بزرگ با partial در #C: کلاسها و متدهای چندتکه
تا حالا شده یه فایل کد اونقدر بزرگ بشه که پیدا کردن یه متد توش عذابآور باشه؟ یا بخواید کدی که خودتون نوشتید رو از کدی که به صورت خودکار توسط یه ابزار (مثل دیزاینر) تولید شده، جدا کنید؟
سیشارپ برای این کار یه راه حل عالی و تمیز داره: کلمه کلیدی partial.
1️⃣ کلاسهای Partial:
یک کلاس در چند فایل کلمه کلیدی partial به شما اجازه میده تعریف یک کلاس، struct یا اینترفیس رو در چند فایل مختلف تقسیم کنید. کامپایلر موقع کامپایل، تمام این تیکهها رو به هم میچسبونه و به عنوان یک کلاس واحد در نظر میگیره.
مهمترین کاربرد: جداسازی کدهای دستنویس شما از کدهای اتوماتیک.
فایل ۱ (اتوماتیک): PaymentForm.g.cs
// این بخش توسط یک ابزار ساخته شده
public partial class PaymentForm
{
// ... کنترلهای دیزاینر و کدهای اتوماتیک
}
فایل ۲ (دستنویس): PaymentForm.cs
// این بخش رو شما مینویسید
public partial class PaymentForm
{
// ... منطق و ایونتهای مربوط به فرم
}
2️⃣ متدهای Partial: قلابهای جادویی که ناپدید میشن! 🎩
این قابلیت به شما اجازه میده در یک بخش از کلاس (معمولاً کد اتوماتیک)، یک "قلاب" یا تعریف متد بدون بدنه بذارید. بعداً در بخش دیگه کلاس (کد دستنویس)، میتونید اون متد رو پیادهسازی کنید.
💡نکته جادویی: اگه شما هیچ پیادهسازیای براش ارائه ندید، کامپایلر هم تعریف و هم تمام فراخوانیهای اون متد رو از کد نهایی حذف میکنه! این یعنی هیچ هزینه پرفورمنسی نداره.
فایل ۱ (اتوماتیک):
public partial class PaymentForm
{
public void Submit()
{
// یه قلاب برای اعتبارسنجی قبل از ارسال
ValidatePayment(this.Amount);
// ...
}
// تعریف متد partial (بدون بدنه)
partial void ValidatePayment(decimal amount);
}
فایل ۲ (دستنویس):
public partial class PaymentForm
{
// پیادهسازی متد partial
partial void ValidatePayment(decimal amount)
{
if (amount > 1000)
{
// ... منطق اعتبارسنجی ...
}
}
}
3️⃣ متدهای Partial توسعهیافته (از 9 #C) 🚀
این نسخه جدیدتر، برای کار با Source Generatorها طراحی شده. در این حالت، شما تعریف متد رو مینویسید و انتظار دارید که Source Generator بدنه اون رو تولید کنه. این متدها دیگه اختیاری نیستن و باید پیادهسازی بشن و میتونن خروجی و پارامتر out هم داشته باشن.
🔖 هشتگها:
#CSharp #Programming #DotNet #CleanCode #Partial
📖 سری آموزشی کتاب C# 12 in a Nutshell
تا حالا شده اسم یه پراپرتی رو تو یه رشته بنویسید (مثلاً برای Exceptionها یا INotifyPropertyChanged) و بعداً که اسم اون پراپرتی رو عوض میکنید، یادتون بره اون رشته رو هم آپدیت کنید و کل برنامه به باگ بخوره؟
به این رشتهها میگن "رشتههای جادویی" (Magic Strings) و یکی از منابع اصلی باگهای پنهان هستن. #C برای حل این مشکل، یه اپراتور خیلی ساده و قدرتمند داره.
اپراتور nameof اسم هرچیزی (متغیر، متد، کلاس، پراپرتی و...) رو در زمان کامپایل به یه رشته تبدیل میکنه.
مزیت اصلیش چیه؟ چک شدن در زمان کامپایل! ✅
اگه شما اسم اون متغیر یا پراپرتی رو عوض کنید (Refactor)، ویژوال استودیو به صورت خودکار تمام nameofهای مربوط به اون رو هم آپدیت میکنه و دیگه هیچوقت کد شما به خاطر یه رشته قدیمی، به باگ نمیخوره.
2️⃣ نحوه استفاده
برای متغیرهای محلی:
برای اعضای یک تایپ:
🪄 خداحافظی با رشتههای جادویی: قدرت nameof در #C
تا حالا شده اسم یه پراپرتی رو تو یه رشته بنویسید (مثلاً برای Exceptionها یا INotifyPropertyChanged) و بعداً که اسم اون پراپرتی رو عوض میکنید، یادتون بره اون رشته رو هم آپدیت کنید و کل برنامه به باگ بخوره؟
به این رشتهها میگن "رشتههای جادویی" (Magic Strings) و یکی از منابع اصلی باگهای پنهان هستن. #C برای حل این مشکل، یه اپراتور خیلی ساده و قدرتمند داره.
1️⃣ راه حل: اپراتور nameof
اپراتور nameof اسم هرچیزی (متغیر، متد، کلاس، پراپرتی و...) رو در زمان کامپایل به یه رشته تبدیل میکنه.
مزیت اصلیش چیه؟ چک شدن در زمان کامپایل! ✅
اگه شما اسم اون متغیر یا پراپرتی رو عوض کنید (Refactor)، ویژوال استودیو به صورت خودکار تمام nameofهای مربوط به اون رو هم آپدیت میکنه و دیگه هیچوقت کد شما به خاطر یه رشته قدیمی، به باگ نمیخوره.
2️⃣ نحوه استفاده
برای متغیرهای محلی:
int userCount = 123;
string variableName = nameof(userCount); // مقدار: "userCount"
برای اعضای یک تایپ:
// برای اعضای استاتیک و غیراستاتیک کار میکنه
string lengthPropName = nameof(StringBuilder.Length); // مقدار: "Length"
// برای گرفتن اسم کامل
string fullName = nameof(StringBuilder) + "." + nameof(StringBuilder.Length);
// "StringBuilder.Length"
🔖 هشتگها:
#CleanCode #Refactoring
📖 سری آموزشی کتاب C# 12 in a Nutshell
چطور میتونیم برای کلاسهامون یه "قرارداد" تعریف کنیم و بگیم "هر کلاسی که میخواد با من کار کنه، باید این قابلیتها رو داشته باشه"؟
ابزار اصلی ما برای این کار در دنیای شیءگرایی، اینترفیس (Interface) هست. اینترفیسها ستون فقرات معماریهای تمیز، انعطافپذیر و تستپذیر هستن.
اینترفیس فقط رفتار (Behavior) رو تعریف میکنه، نه وضعیت (State). یعنی فقط امضای متدها و پراپرتیها رو داره، نه فیلدهای داده و نه بدنه پیادهسازی برای اعضاش.
تفاوتهای کلیدی با کلاس:
🔹 فقط توابع (متد، پراپرتی، ایونت، ایندکسر) را تعریف میکند، نه فیلدها.
🔹 اعضایش به صورت پیشفرض public و abstract هستند.
🔹 یک کلاس میتواند چندین اینترفیس را پیادهسازی کند (برخلاف وراثت از کلاس که فقط یکیه).
مثال (اینترفیس IEnumerator):
وقتی یه کلاس، اینترفیسی رو پیادهسازی میکنه، قول میده که برای تمام اعضای اون اینترفیس، یک پیادهسازی public ارائه بده.
حالا میتونید یک نمونه از Countdown رو در متغیری از نوع IEnumerator بریزید:
حالا اگه یه کلاس، دو تا اینترفیس رو پیادهسازی کنه که متدهایی با اسم یکسان ولی امضای متفاوت دارن، چی میشه؟ یا اگه بخوایم یه متد از اینترفیس رو از دید عمومی کلاس مخفی کنیم؟
اینجا پای پیادهسازی صریح (Explicit Implementation) به میدون باز میشه. در این حالت، شما اسم اینترفیس رو قبل از اسم متد میارید.
مثال (حل تداخل):
نکته حیاتی: ⚠️ متدی که به صورت صریح پیادهسازی شده، دیگه به صورت عمومی در دسترس نیست. برای صدا زدنش، باید اول آبجکت رو به اون اینترفیس خاص کست کنید:
اینترفیسها، اساس طراحیهای ماژولار، تستپذیر و مبتنی بر اصول SOLID هستن.
📜 قراردادهای کدنویسی در #C: راهنمای کامل اینترفیسها (Interfaces)
چطور میتونیم برای کلاسهامون یه "قرارداد" تعریف کنیم و بگیم "هر کلاسی که میخواد با من کار کنه، باید این قابلیتها رو داشته باشه"؟
ابزار اصلی ما برای این کار در دنیای شیءگرایی، اینترفیس (Interface) هست. اینترفیسها ستون فقرات معماریهای تمیز، انعطافپذیر و تستپذیر هستن.
1️⃣ اینترفیس چیست؟ یک قرارداد، بدون پیادهسازی
اینترفیس فقط رفتار (Behavior) رو تعریف میکنه، نه وضعیت (State). یعنی فقط امضای متدها و پراپرتیها رو داره، نه فیلدهای داده و نه بدنه پیادهسازی برای اعضاش.
تفاوتهای کلیدی با کلاس:
🔹 فقط توابع (متد، پراپرتی، ایونت، ایندکسر) را تعریف میکند، نه فیلدها.
🔹 اعضایش به صورت پیشفرض public و abstract هستند.
🔹 یک کلاس میتواند چندین اینترفیس را پیادهسازی کند (برخلاف وراثت از کلاس که فقط یکیه).
مثال (اینترفیس IEnumerator):
public interface IEnumerator
{
bool MoveNext();
object Current { get; }
void Reset();
}
2️⃣ پیادهسازی اینترفیس: امضای قرارداد
وقتی یه کلاس، اینترفیسی رو پیادهسازی میکنه، قول میده که برای تمام اعضای اون اینترفیس، یک پیادهسازی public ارائه بده.
internal class Countdown : IEnumerator
{
int count = 11;
public bool MoveNext() => count-- > 0;
public object Current => count;
public void Reset() { throw new NotSupportedException(); }
}
حالا میتونید یک نمونه از Countdown رو در متغیری از نوع IEnumerator بریزید:
IEnumerator e = new Countdown();
while (e.MoveNext())
{
Console.Write(e.Current + " "); // 10 9 8 7 6 5 4 3 2 1 0
}
3️⃣ جادوی پیادهسازی صریح (Explicit Implementation) 🎭
حالا اگه یه کلاس، دو تا اینترفیس رو پیادهسازی کنه که متدهایی با اسم یکسان ولی امضای متفاوت دارن، چی میشه؟ یا اگه بخوایم یه متد از اینترفیس رو از دید عمومی کلاس مخفی کنیم؟
اینجا پای پیادهسازی صریح (Explicit Implementation) به میدون باز میشه. در این حالت، شما اسم اینترفیس رو قبل از اسم متد میارید.
مثال (حل تداخل):
interface I1 { void Foo(); }
interface I2 { int Foo(); }
public class Widget : I1, I2
{
public void Foo() // پیادهسازی پیشفرض برای I1
{
Console.WriteLine("Widget's implementation of I1.Foo");
}
// پیادهسازی صریح برای حل تداخل با I1.Foo
int I2.Foo()
{
Console.WriteLine("Widget's implementation of I2.Foo");
return 42;
}
}نکته حیاتی: ⚠️ متدی که به صورت صریح پیادهسازی شده، دیگه به صورت عمومی در دسترس نیست. برای صدا زدنش، باید اول آبجکت رو به اون اینترفیس خاص کست کنید:
Widget w = new Widget();
w.Foo(); // I1.Foo صدا زده میشه
((I1)w).Foo(); // I1.Foo صدا زده میشه
((I2)w).Foo(); // I2.Foo صدا زده میشه
🤔 حرف حساب و تجربه شما
اینترفیسها، اساس طراحیهای ماژولار، تستپذیر و مبتنی بر اصول SOLID هستن.
🔖 هشتگها:
#CSharp #DotNet #OOP #Interface #CleanCode
📖 سری آموزشی کتاب C# 12 in a Nutshell
یکی از اساسیترین سوالات در طراحی شیءگرا اینه: کی باید از یه class و وراثت استفاده کنیم و کی باید بریم سراغ interface؟
این فقط یه انتخاب سینتکسی نیست، یه تصمیم مهم معماریه. بیاید با یه قانون ساده و یه مثال عالی، این موضوع رو برای همیشه روشن کنیم.
به عنوان یک قانون کلی و کاربردی:
از class و وراثت استفاده کن:
وقتی تایپهای شما به صورت طبیعی، پیادهسازی مشترکی (shared implementation) دارند. یعنی کدهای مشترکی بینشون هست که میخواید از تکرارش جلوگیری کنید.
از interface استفاده کن:
وقتی تایپهای شما رفتار مشترکی (shared behavior) دارند، ولی هر کدوم پیادهسازی مستقل (independent implementation) و متفاوتی از اون رفتار رو ارائه میدن.
فرض کنید میخوایم موجودات مختلف رو مدلسازی کنیم.
مشکل (استفاده فقط از کلاس): 👎
یه "عقاب" هم "پرنده" است، هم "موجود پرنده" و هم "گوشتخوار". چون #C وراثت چندگانه از کلاسها رو پشتیبانی نمیکنه، کد زیر کامپایل نمیشه!
راه حل (ترکیب کلاس و اینترفیس): 👍
حالا قانون طلایی رو به کار میبریم. "پرنده" بودن احتمالاً یه سری پیادهسازی و ویژگی مشترک داره، پس
این مثال نشون میده که چطور با انتخاب درست بین کلاس و اینترفیس، میتونید ساختارهای پیچیده و در عین حال تمیز و انعطافپذیری طراحی کنید.
🏛 کلاس در برابر اینترفیس: کدام را و چه زمانی انتخاب کنیم؟
یکی از اساسیترین سوالات در طراحی شیءگرا اینه: کی باید از یه class و وراثت استفاده کنیم و کی باید بریم سراغ interface؟
این فقط یه انتخاب سینتکسی نیست، یه تصمیم مهم معماریه. بیاید با یه قانون ساده و یه مثال عالی، این موضوع رو برای همیشه روشن کنیم.
1️⃣ قانون طلایی: پیادهسازی مشترک در برابر رفتار مشترک ⚖️
به عنوان یک قانون کلی و کاربردی:
از class و وراثت استفاده کن:
وقتی تایپهای شما به صورت طبیعی، پیادهسازی مشترکی (shared implementation) دارند. یعنی کدهای مشترکی بینشون هست که میخواید از تکرارش جلوگیری کنید.
از interface استفاده کن:
وقتی تایپهای شما رفتار مشترکی (shared behavior) دارند، ولی هر کدوم پیادهسازی مستقل (independent implementation) و متفاوتی از اون رفتار رو ارائه میدن.
2️⃣ مثال عملی: دنیای حیوانات 🦅
فرض کنید میخوایم موجودات مختلف رو مدلسازی کنیم.
مشکل (استفاده فقط از کلاس): 👎
یه "عقاب" هم "پرنده" است، هم "موجود پرنده" و هم "گوشتخوار". چون #C وراثت چندگانه از کلاسها رو پشتیبانی نمیکنه، کد زیر کامپایل نمیشه!
abstract class Animal {}
abstract class Bird : Animal {}
abstract class FlyingCreature : Animal {}
abstract class Carnivore : Animal {}
// ❌ خطای کامپایل! یک کلاس نمیتونه از چند کلاس ارثبری کنه
class Eagle : Bird, FlyingCreature, Carnivore {}راه حل (ترکیب کلاس و اینترفیس): 👍
حالا قانون طلایی رو به کار میبریم. "پرنده" بودن احتمالاً یه سری پیادهسازی و ویژگی مشترک داره، پس
Bird یه class باقی میمونه. اما "پرواز کردن" یا "گوشتخوار بودن"، رفتارهایی هستن که موجودات مختلف (مثل حشره و پرنده) به شکلهای کاملاً متفاوتی انجام میدن. پس اینها بهترین کاندیدا برای اینترفیس هستن!// اینترفیسها فقط "رفتار" رو تعریف میکنن
interface IFlyingCreature { }
interface ICarnivore { }
abstract class Animal {}
// کلاسها "پیادهسازی" مشترک رو ارائه میدن
abstract class Bird : Animal {}
// ✅ حالا درسته!
// عقاب از کلاس Bird ارث میبره و دو اینترفیس رو پیادهسازی میکنه
class Eagle : Bird, IFlyingCreature, ICarnivore {}
🤔 حرف حساب و تجربه شما
این مثال نشون میده که چطور با انتخاب درست بین کلاس و اینترفیس، میتونید ساختارهای پیچیده و در عین حال تمیز و انعطافپذیری طراحی کنید.
🔖 هشتگها:
#CSharp #DotNet #OOP #SoftwareArchitecture #Interface #CleanCode
Forwarded from Brain bytes
### E) Event-Driven Paradigm
Flow controlled by events rather than sequential commands.
Used in UI, messaging systems, reactive pipelines.
---
### F) Asynchronous Paradigm
Handle I/O or long-running tasks without blocking threads.
Combines
---
### G) Reactive (Optional Extension)
Treat data as streams (e.g. with IObservable): react declaratively to change over time.
---
## 4) How C# Programming Model Enables These Paradigms
| Paradigm | Model Elements (C#) |
|---------------|---------------------|
| Imperative | loops, mutable locals, branching (
| OOP |
| Functional | lambdas, delegates (
| Declarative | LINQ query syntax, attributes, expression trees |
| Event-Driven |
| Asynchronous |
| Reactive | observables (via libraries), continuation chains |
C# is multi-paradigm: mix domain modeling (OOP) + data transformations (functional/declarative) + async I/O + events.
---
## 5) Choosing a Paradigm (Quick Guide)
| Situation | Best Fit |
|-----------|----------|
| Rich domain entities, lifecycle | OOP |
| Data querying/filtering | Declarative (LINQ) |
| Composable transformations, testability | Functional style |
| UI interactions, user input | Event-Driven |
| Network / file / database latency | Asynchronous |
| Simple scripts / procedural tasks | Imperative |
| Live data streams / continuous updates | Reactive |
Often you combine several in one solution.
---
## 6) Summary
Paradigm = How you think and structure the solution conceptually.
Programming Model = The language/runtime mechanisms enabling those structures.
C# provides a unified model supporting multiple paradigms seamlessly.
---
## 7) Glossary (English Term + Persian Meaning)
| Term | Persian |
|------|--------|
| Programming Paradigm | پارادایم برنامهنویسی / سبک مفهومی |
| Programming Model | مدل برنامهنویسی / مکانیزم اجرایی |
| Imperative | دستوری |
| Declarative | اعلامی / деклараتی |
| Object-Oriented (OOP) | شیءگرا |
| Encapsulation | کپسولهسازی |
| Inheritance | ارثبری |
| Polymorphism | چندریختی |
| Abstraction | انتزاع |
| Class | کلاس |
| Object | شیء |
| Method | متد |
| State | حالت |
| Functional Programming | برنامهنویسی تابعی |
| Pure Function | تابع خالص |
| Side Effect | عارضهٔ جانبی |
| Immutability | تغییرناپذیری |
| Lambda Expression | عبارت لامبدا |
| Delegate | نماینده (تابع اشارهای) |
| LINQ | چارچوب کوئری یکپارچه |
| Query Syntax | نحوهٔ نگارش کوئری |
| Expression Tree | درخت بیان |
| Event | رویداد |
| Event-Driven | رویدادمحور |
| Asynchronous / Async | ناهمگام |
| Concurrency | همزمانی |
| Task | وظیفهٔ ناهمگام |
| CancellationToken | توکن لغو |
| Record | رکورد (نوع دادهٔ مختصر) |
| Virtual | مجازی (قابل بازنویسی) |
| Override | بازنویسی |
| Abstract | انتزاعی |
| Interface | اینترفیس / قرارداد |
| Reactive | واکنشی |
| Multi-Paradigm | چندپارادایمی |
| API | رابط برنامهنویسی کاربردی |
| Design Pattern | الگوی طراحی |
| Refactoring | بازآرایی کد |
| Maintainability | نگهداشتپذیری |
| Scalability | مقیاسپذیری |
| Deferred Execution | اجرای مؤخر |
| DSL | زبان دامنهمحور |
| Idempotent | ایدمپوتنت (تکرار بدون تغییر نتیجه) |
---
## 8) Tags
#programming #paradigm #programming_model #CSharp #OOP #Functional #Declarative #LINQ #Async #EventDriven #Reactive #DotNet #SoftwareDesign #CleanCode #BrainBytes #Architecture #Coding #Developer
Flow controlled by events rather than sequential commands.
public class Button
{
public event Action? Click;
public void OnClick() => Click?.Invoke();
}
var button = new Button();
button.Click += () => Console.WriteLine("Button clicked!");
button.OnClick();
Used in UI, messaging systems, reactive pipelines.
---
### F) Asynchronous Paradigm
Handle I/O or long-running tasks without blocking threads.
public async Task<string> FetchAsync()
{
using var http = new HttpClient();
return await http.GetStringAsync("https://example.com");
}
var data = await FetchAsync();
Console.WriteLine(data);
Combines
async/await with Task for clearer concurrency.---
### G) Reactive (Optional Extension)
Treat data as streams (e.g. with IObservable): react declaratively to change over time.
---
## 4) How C# Programming Model Enables These Paradigms
| Paradigm | Model Elements (C#) |
|---------------|---------------------|
| Imperative | loops, mutable locals, branching (
if, switch) || OOP |
class, interface, abstract, virtual, override, access modifiers || Functional | lambdas, delegates (
Func<>, Action), LINQ, record types || Declarative | LINQ query syntax, attributes, expression trees |
| Event-Driven |
event, delegates, EventHandler, UI frameworks || Asynchronous |
async, await, Task, CancellationToken, ValueTask || Reactive | observables (via libraries), continuation chains |
C# is multi-paradigm: mix domain modeling (OOP) + data transformations (functional/declarative) + async I/O + events.
---
## 5) Choosing a Paradigm (Quick Guide)
| Situation | Best Fit |
|-----------|----------|
| Rich domain entities, lifecycle | OOP |
| Data querying/filtering | Declarative (LINQ) |
| Composable transformations, testability | Functional style |
| UI interactions, user input | Event-Driven |
| Network / file / database latency | Asynchronous |
| Simple scripts / procedural tasks | Imperative |
| Live data streams / continuous updates | Reactive |
Often you combine several in one solution.
---
## 6) Summary
Paradigm = How you think and structure the solution conceptually.
Programming Model = The language/runtime mechanisms enabling those structures.
C# provides a unified model supporting multiple paradigms seamlessly.
---
## 7) Glossary (English Term + Persian Meaning)
| Term | Persian |
|------|--------|
| Programming Paradigm | پارادایم برنامهنویسی / سبک مفهومی |
| Programming Model | مدل برنامهنویسی / مکانیزم اجرایی |
| Imperative | دستوری |
| Declarative | اعلامی / деклараتی |
| Object-Oriented (OOP) | شیءگرا |
| Encapsulation | کپسولهسازی |
| Inheritance | ارثبری |
| Polymorphism | چندریختی |
| Abstraction | انتزاع |
| Class | کلاس |
| Object | شیء |
| Method | متد |
| State | حالت |
| Functional Programming | برنامهنویسی تابعی |
| Pure Function | تابع خالص |
| Side Effect | عارضهٔ جانبی |
| Immutability | تغییرناپذیری |
| Lambda Expression | عبارت لامبدا |
| Delegate | نماینده (تابع اشارهای) |
| LINQ | چارچوب کوئری یکپارچه |
| Query Syntax | نحوهٔ نگارش کوئری |
| Expression Tree | درخت بیان |
| Event | رویداد |
| Event-Driven | رویدادمحور |
| Asynchronous / Async | ناهمگام |
| Concurrency | همزمانی |
| Task | وظیفهٔ ناهمگام |
| CancellationToken | توکن لغو |
| Record | رکورد (نوع دادهٔ مختصر) |
| Virtual | مجازی (قابل بازنویسی) |
| Override | بازنویسی |
| Abstract | انتزاعی |
| Interface | اینترفیس / قرارداد |
| Reactive | واکنشی |
| Multi-Paradigm | چندپارادایمی |
| API | رابط برنامهنویسی کاربردی |
| Design Pattern | الگوی طراحی |
| Refactoring | بازآرایی کد |
| Maintainability | نگهداشتپذیری |
| Scalability | مقیاسپذیری |
| Deferred Execution | اجرای مؤخر |
| DSL | زبان دامنهمحور |
| Idempotent | ایدمپوتنت (تکرار بدون تغییر نتیجه) |
---
## 8) Tags
#programming #paradigm #programming_model #CSharp #OOP #Functional #Declarative #LINQ #Async #EventDriven #Reactive #DotNet #SoftwareDesign #CleanCode #BrainBytes #Architecture #Coding #Developer
🧠 چه زمانی از Value Objectها استفاده کنیم؟
من از Value Objectها برای حل مشکل primitive obsession و کپسولهسازی (encapsulation) قوانین دامنه (domain invariants) استفاده میکنم.
کپسولهسازی بخش مهمی از هر مدل دامنه است. شما نباید بتوانید یک Value Object را در حالت نامعتبر ایجاد کنید.
Value Object
ها همچنین type safety را فراهم میکنند. به امضای متد زیر دقت کنید:
public interface IPricingService
{
decimal Calculate(Apartment apartment, DateOnly start, DateOnly end);
}
حالا به امضای متد زیر نگاه کنید که در آن از Value Objectها استفاده شده است. میتوانید ببینید که این نسخه از IPricingService بسیار واضحتر (explicit) است. همچنین از مزیت type safety برخوردار میشوید. هنگام کامپایل کد، Value Objectها احتمال بروز خطا را کاهش میدهند.
public interface IPricingService
{
PricingDetails Calculate(Apartment apartment, DateRange period);
}
در ادامه چند نکته دیگر را ببینید که باید هنگام تصمیمگیری برای استفاده از Value Objectها در نظر بگیرید:
• پیچیدگی قوانین (invariants): اگر نیاز به اعمال قوانین پیچیده دارید، استفاده از Value Object را در نظر بگیرید.
• تعداد مقادیر اولیه (primitives): زمانی که تعداد زیادی مقدار اولیه وجود دارد، استفاده از Value Object منطقی است.
• تعداد تکرارها: اگر نیاز دارید قوانین را فقط در چند نقطه از کد اعمال کنید، میتوانید بدون Value Object هم آن را مدیریت کنید.
💾 ذخیرهسازی Value Objectها با EF Core
Value Object
ها بخشی از موجودیتهای دامنه (domain entities) هستند و باید در پایگاه داده ذخیره شوند.
در اینجا نحوه استفاده از EF Owned Types و Complex Types برای ذخیرهسازی Value Objectها آورده شده است.
🧱 Owned Types
Owned Type
ها را میتوان با فراخوانی متد OwnsOne در هنگام پیکربندی موجودیت تنظیم کرد.
این کار به EF اعلام میکند که باید Value Objectهای Address و Price را در همان جدول موجودیت Apartment ذخیره کند.
Value Object
ها با ستونهای اضافی در جدول apartments نمایش داده میشوند.
public void Configure(EntityTypeBuilder<Apartment> builder)
{
builder.ToTable("apartments");
builder.OwnsOne(property => property.Address);
builder.OwnsOne(property => property.Price, priceBuilder =>
{
priceBuilder.Property(money => money.Currency)
.HasConversion(
currency => currency.Code,
code => Currency.FromCode(code));
});
}
چند نکته در مورد Owned Typeها:
• Owned Typeها دارای یک کلید پنهان هستند.
• از نوع اختیاری (nullable) پشتیبانی نمیکنند.
• مجموعههای متعلق پشتیبانی میشوند و با OwnsMany قابل پیکربندی هستند.
•Table splitting
به شما اجازه میدهد Owned Typeها را در جدول جداگانه ذخیره کنید.
🧩 Complex Types
ویژگی جدیدی در EF هستند که در 8 NET. در دسترساند.
آنها با مقدار کلید (key value) شناسایی یا پیگیری نمیشوند.
اینها باید بخشی از نوع موجودیت (entity type) باشند.Complex Type ها برای نمایش Value Objectها در EF مناسبتر هستند.
در اینجا نحوه پیکربندی Address به عنوان یک Complex Type را میبینید:
public void Configure(EntityTypeBuilder<Apartment> builder)
{
builder.ToTable("apartments");
builder.ComplexProperty(property => property.Address);
}
چند محدودیت برای Complex Typeها وجود دارد:
🔹️ از مجموعهها (collections) پشتیبانی نمیکنند.
🔹️ از مقادیر اختیاری (nullable values) پشتیبانی نمیکنند.
🧾 نتیجهگیری
Value Object
ها به شما کمک میکنند تا یک مدل دامنه غنی طراحی کنید.
میتوانید از آنها برای حل مشکل primitive obsession و کپسولهسازی قوانین دامنه استفاده کنید.Value Objectها با جلوگیری از ایجاد اشیای دامنه نامعتبر، احتمال خطا را کاهش میدهند.
شما میتوانید برای نمایش Value Objectها از record یا کلاس پایه ValueObject استفاده کنید.انتخاب بین آنها باید بر اساس نیازها و پیچیدگی دامنه شما باشد.
من معمولاً بهصورت پیشفرض از record استفاده میکنم مگر زمانی که به ویژگیهای خاص کلاس پایه نیاز داشته باشم.
برای مثال، کلاس پایه زمانی مفید است که بخواهید اجزای برابری (equality components) را کنترل کنید.
🔖هشتگها:
#DomainDrivenDesign #ValueObject #CleanCode #EntityVsValueObject #ImmutableObjects