📖 سری آموزشی کتاب C# 12 in a Nutshell
وقتی کلاسهای فرزند میخوان آبجکت بسازن، چه اتفاقی پشت صحنه میفته؟ امروز به عمیقترین قوانین ساخت و ساز در سلسلهمراتب وراثت شیرجه میزنیم.
base
به شما اجازه میده از داخل کلاس فرزند، به اعضای کلاس پدر دسترسی داشته باشید. دو کاربرد اصلی داره:
صدا زدن عضو بازنویسی شده:
اگه متدی رو override کردید، با base میتونید به پیادهسازی اصلی در کلاس پدر دسترسی پیدا کنید.
صدا زدن سازنده پدر:
با سینتکس : base(...) صراحتاً سازنده مورد نظرتون در کلاس پدر رو صدا میزنید.
• قانون اول: سازنده کلاس پدر همیشه قبل از سازنده کلاس فرزند اجرا میشه.
• قانون دوم (تله مهم): اگه شما : base(...) رو ننویسید، #C به صورت خودکار سعی میکنه سازنده بدون پارامتر پدر رو صدا بزنه. اگه کلاس پدر سازنده بدون پارامتر نداشته باشه، خطای زمان کامپایل میگیرید!
جایگزین مدرن ✨
گاهی وقتا زنجیره سازندهها خیلی طولانی میشه. از #C 11 به بعد، میتونید پراپرتیها یا فیلدهایی رو با کلمه کلیدی required مشخص کنید. این یعنی هر کسی که از کلاس شما آبجکت میسازه، مجبوره که این اعضا رو با استفاده از Object Initializer مقداردهی کنه.
💡نکته: برای اینکه به یک سازنده اجازه بدید این قانون رو دور بزنه، میتونید از اتریبیوت
وقتی یه آبجکت فرزند ساخته میشه، ترتیب اجرای دقیق عملیات به این صورته:
فاز اول (از فرزند به پدر):
•فیلدهای کلاس فرزند مقداردهی اولیه میشن.
• آرگومانهای پاس داده شده به base(...) ارزیابی میشن.
• این روند تا بالاترین کلاس در سلسلهمراتب ادامه پیدا میکنه.
فاز دوم (از پدر به فرزند):
• حالا بدنهی سازندهها، از کلاس پدر شروع شده و به سمت کلاس فرزند اجرا میشن.
مثال کتاب برای درک بهتر این ترتیب:
🤔 حرف حساب و تجربه شما
درک این جزئیات، شما رو به یک متخصص واقعی در طراحی کلاسهای شیءگرا تبدیل میکنه.
🏛 کالبدشکافی وراثت در #C: سازندهها، required و ترتیب اجرا
وقتی کلاسهای فرزند میخوان آبجکت بسازن، چه اتفاقی پشت صحنه میفته؟ امروز به عمیقترین قوانین ساخت و ساز در سلسلهمراتب وراثت شیرجه میزنیم.
1️⃣ کلیدواژه base: صحبت با کلاس پدر
base
به شما اجازه میده از داخل کلاس فرزند، به اعضای کلاس پدر دسترسی داشته باشید. دو کاربرد اصلی داره:
صدا زدن عضو بازنویسی شده:
اگه متدی رو override کردید، با base میتونید به پیادهسازی اصلی در کلاس پدر دسترسی پیدا کنید.
public override decimal Liability => base.Liability + Mortgage;
صدا زدن سازنده پدر:
با سینتکس : base(...) صراحتاً سازنده مورد نظرتون در کلاس پدر رو صدا میزنید.
public class Subclass : Baseclass
{
public Subclass(int x) : base(x) { }
}
2️⃣ قوانین سازندهها در وراثت ⚖️
• قانون اول: سازنده کلاس پدر همیشه قبل از سازنده کلاس فرزند اجرا میشه.
• قانون دوم (تله مهم): اگه شما : base(...) رو ننویسید، #C به صورت خودکار سعی میکنه سازنده بدون پارامتر پدر رو صدا بزنه. اگه کلاس پدر سازنده بدون پارامتر نداشته باشه، خطای زمان کامپایل میگیرید!
3️⃣ required members (از C# 11):
جایگزین مدرن ✨
گاهی وقتا زنجیره سازندهها خیلی طولانی میشه. از #C 11 به بعد، میتونید پراپرتیها یا فیلدهایی رو با کلمه کلیدی required مشخص کنید. این یعنی هر کسی که از کلاس شما آبجکت میسازه، مجبوره که این اعضا رو با استفاده از Object Initializer مقداردهی کنه.
public class Asset
{
public required string Name;
}
// ✅ درسته
Asset a1 = new Asset { Name = "House" };
// ❌ خطای زمان کامپایل! چون Name مقداردهی نشده
Asset a2 = new Asset();
💡نکته: برای اینکه به یک سازنده اجازه بدید این قانون رو دور بزنه، میتونید از اتریبیوت
[SetsRequiredMembers] استفاده کنید.4️⃣ ترتیب دقیق ساخت (Deep Dive) 🔬
وقتی یه آبجکت فرزند ساخته میشه، ترتیب اجرای دقیق عملیات به این صورته:
فاز اول (از فرزند به پدر):
•فیلدهای کلاس فرزند مقداردهی اولیه میشن.
• آرگومانهای پاس داده شده به base(...) ارزیابی میشن.
• این روند تا بالاترین کلاس در سلسلهمراتب ادامه پیدا میکنه.
فاز دوم (از پدر به فرزند):
• حالا بدنهی سازندهها، از کلاس پدر شروع شده و به سمت کلاس فرزند اجرا میشن.
مثال کتاب برای درک بهتر این ترتیب:
public class B
{
int x = 1; // مرحله ۳: این اجرا میشه
public B(int x)
{
// مرحله ۴: بدنه سازنده پدر اجرا میشه
}
}
public class D : B
{
int y = 1; // مرحله ۱: این اول از همه اجرا میشه
public D(int x)
: base(x + 1) // مرحله ۲: آرگومان base ارزیابی میشه
{
// مرحله ۵: بدنه سازنده فرزند در آخر اجرا میشه
}
}
🤔 حرف حساب و تجربه شما
درک این جزئیات، شما رو به یک متخصص واقعی در طراحی کلاسهای شیءگرا تبدیل میکنه.
🔖 هشتگها:
#OOP #Inheritance #BestPractices #Constructor
C# Geeks (.NET)
الگوی CQRS به روشی که از ابتدا باید میبود 🚀 📢 MediatR در حال تجاری شدن است. جیمی بوگارد اعلام کرد که MediatR برای شرکتهای بالاتر از یک اندازه مشخص، یک مدل لایسنس تجاری اتخاذ خواهد کرد. برای بسیاری از تیمها، این یک محرک برای ارزیابی مجدد استفاده از آن…
پست بالا رو حتما برسی کنید🤍
✨️Here is how to implement CQRS in the application.
📌Free Clean Architecture Template(Link)
✨️Here is how to implement CQRS in the application.
📌Free Clean Architecture Template(Link)
Enumها به عنوان رشته در EF Core:
کدی خواناتر برای دیتابیس شما 📜
وقتی از Enumها در Entity Framework Core استفاده میکنید، به صورت پیشفرض به شکل عدد (0, 1, 2) در دیتابیس ذخیره میشن. این کار از نظر پرفورمنس خوبه، ولی وقتی مستقیم به دیتابیس نگاه میکنید، این عددها هیچ معنایی ندارن! 🧐
اما یه راه حل خیلی ساده و تمیز برای افزایش خوانایی و قابلیت نگهداری دیتابیس وجود داره: ذخیره کردن Enumها به صورت رشته.
جادوی HasConversion ✨
با استفاده از متد HasConversion در Fluent API، میتونید به راحتی به EF Core بگید که مقادیر Enum رو به جای عدد، به صورت نام رشتهای اونها ذخیره کنه.
1️⃣ Enum شما:
public enum OrderStatus
{
Pending,
Completed,
Cancelled
}
2️⃣ انتیتی شما:
public class Order
{
public int Id { get; set; }
public OrderStatus Status { get; set; }
}
3️⃣ پیکربندی در DbContext:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Order>()
.Property(o => o.Status)
.HasConversion<string>(); // تمام جادو اینجاست!
}
حالت پیشفرض (بدون HasConversion): 👎
| Id | Status |
| :-- | :--- |
| 1 | 0 |
| 2 | 1 |
حالت جدید (با HasConversion): 👍
| Id | Status |
| :-- | :--- |
| 1 | "Pending" |
| 2 | "Completed" |
🤔 حرف حساب و تجربه شما
این تغییر کوچیک، دیباگ کردن و کار مستقیم با دیتابیس رو خیلی راحتتر میکنه. با اینکه ذخیرهسازی به صورت عدد کمی بهینهتره، اما در اکثر پروژهها، خوانایی بالاتر Enum به صورت رشته، ارزشش رو داره.
</Link>
🔖 هشتگها:
#EntityFrameworkCore #EFCore #Database
6️⃣ نکته برای بهبود مهارتهای نرم به عنوان یک مهندس نرمافزار 💡
1️⃣ خودآگاهی (Self-Awareness)
بازخورد گرفتن از منتور، همکاران و مدیر، یک ابزار عالی برای ساختن خودآگاهیه.
2️⃣ ارتباطات (Communication)
ارتباط واضح و موثر، برای موفقیت در توسعه نرمافزار حیاتیه.
🔹 اطلاعات پیچیده رو به بخشهای کوچیکتر تقسیم کنید.
🔹 از روشهای ارتباطی مناسب استفاده کنید (چت، تماس، جلسه).
🔹 مخاطب خودتون رو در نظر بگیرید و پیامتون رو متناسب با اونها تنظیم کنید.
3️⃣ گوش دادن فعال (Active Listening)
این بخش ضروری از ارتباطه و فقط به معنی شنیدن نیست، بلکه درک کردن پیامه.
🔹 سوالات شفافکننده بپرسید.
🔹 تماس چشمی برقرار کنید.
🔹 چیزی که شنیدید رو خلاصه کنید تا مطمئن بشید درست فهمیدید.
🔹 قبل از اینکه سریع جواب بدید، یکی دو ثانیه مکث کنید.
4️⃣ ایجاد همدلی (Empathy)
همدلی یعنی توانایی درک احساسات دیگران. ایجاد همدلی با کاربران به شما کمک میکنه نیازهاشون رو بهتر بفهمید.
🔹 تحقیقات کاربر انجام بدید.
🔹 خودتون رو جای اونها بذارید! 👟
🔹 بازخوردهای مشتریان رو برای درک بهتر، مرور کنید.
🔹 از ابزارهای آنالیتیکس برای مشاهده رفتار مشتری استفاده کنید.
5️⃣ همکاری (Collaboration)
کار تیمی موثر برای موفقیت پروژهها حیاتیه. حتی اگه تنها دولوپر تیم باشید، باز هم باید با بقیه ذینفعان همکاری کنید!
🔹 تنوع رو بپذیرید! 🧑🤝🧑
🔹 فرهنگ همکاری رو تقویت کنید.
🔹 روی ایجاد اعتماد و احترام تمرکز کنید.
🔹 مسئولیتپذیر باشید و در مورد "شکستها" شفاف باشید.
6️⃣ سازگاری (Adaptability)
پذیرای تغییر اولویتها و تطبیق با نیازمندیهای جدید باشید. من نمیتونم چیزهای زیادی رو به شما قول بدم، جز اینکه اولویتها همیشه تغییر خواهند کرد.
🔹 بپذیرید که تغییر، اجتنابناپذیره. 🔄
🔹 انعطافپذیری رو با نیازهای بیزینس متعادل کنید.
🔹 پذیرای بازخورد و انتقاد سازنده باشید.
🔹 روی راههای موفقیت تمرکز کنید، نه روی موانع. 🚀
string link ="Link";
📖 سری آموزشی کتاب C# 12 in a Nutshell
در دنیای شیءگرایی، دو تا از قدرتمندترین مفاهیم، Overloading (چند متد با اسم یکسان) و Polymorphism (چندریختی) هستن. اما وقتی این دو با هم روبرو میشن، چه اتفاقی میفته؟ کامپایلر چطور تصمیم میگیره کدوم متد رو اجرا کنه؟
جواب این سوال، یکی از ظریفترین و مهمترین نکات #C هست که درک عمیق شما از زبان رو نشون میده.
فرض کنید این کلاسها و دو متد Foo رو داریم که override نشدن، بلکه overload شدن:
قانون اینه: انتخاب بین متدهای Overload شده، در زمان کامپایل و بر اساس نوع متغیر شما اتفاق میفته، نه نوع واقعی آبجکتی که در زمان اجرا داخل اون متغیره.
این رفتار با override کردن کاملاً متفاوته!
حالا اگه بخوایم این تصمیم رو به زمان اجرا موکول کنیم تا بر اساس نوع واقعی آبجکت تصمیم گرفته بشه، میتونیم متغیر رو به dynamic کست کنیم. این کار به DLR (Dynamic Language Runtime) میگه که در زمان اجرا، بهترین متد رو پیدا کنه.
🤔 حرف حساب و تجربه شما
این تفاوت ظریف، نشون میده که Overloading یک نوع چندریختی در زمان کامپایل (Static Polymorphism) هست، در حالی که override کردن، چندریختی در زمان اجرا (Dynamic Polymorphism) رو پیاده میکنه.
⚔️ دوئل Overloading و Polymorphism: کدام متد اجرا میشود؟
در دنیای شیءگرایی، دو تا از قدرتمندترین مفاهیم، Overloading (چند متد با اسم یکسان) و Polymorphism (چندریختی) هستن. اما وقتی این دو با هم روبرو میشن، چه اتفاقی میفته؟ کامپایلر چطور تصمیم میگیره کدوم متد رو اجرا کنه؟
جواب این سوال، یکی از ظریفترین و مهمترین نکات #C هست که درک عمیق شما از زبان رو نشون میده.
1️⃣ صحنه نبرد: دو متد Overload شده
فرض کنید این کلاسها و دو متد Foo رو داریم که override نشدن، بلکه overload شدن:
public class Asset { }
public class House : Asset { }
static void Foo(Asset a) => Console.WriteLine("Foo(Asset) called");
static void Foo(House h) => Console.WriteLine("Foo(House) called");2️⃣ قانون بازی: انتخاب در زمان کامپایل! ⚖️
قانون اینه: انتخاب بین متدهای Overload شده، در زمان کامپایل و بر اساس نوع متغیر شما اتفاق میفته، نه نوع واقعی آبجکتی که در زمان اجرا داخل اون متغیره.
این رفتار با override کردن کاملاً متفاوته!
House h = new House();
// اینجا نوع متغیر h در زمان کامپایل House است، پس کامپایلر Foo(House) رو انتخاب میکنه.
Foo(h);
// خروجی: Foo(House) called
Asset a = new House();
// مهم! اینجا با اینکه آبجکت داخل a از نوع House است،
// اما نوع متغیر a در زمان کامپایل Asset است!
// پس کامپایلر، Foo(Asset) رو انتخاب میکنه.
Foo(a);
// خروجی: Foo(Asset) called
3️⃣ راه فرار: استفاده از dynamic 🚀
حالا اگه بخوایم این تصمیم رو به زمان اجرا موکول کنیم تا بر اساس نوع واقعی آبجکت تصمیم گرفته بشه، میتونیم متغیر رو به dynamic کست کنیم. این کار به DLR (Dynamic Language Runtime) میگه که در زمان اجرا، بهترین متد رو پیدا کنه.
Asset a = new House();
Foo((dynamic)a);
// خروجی: Foo(House) called
🤔 حرف حساب و تجربه شما
این تفاوت ظریف، نشون میده که Overloading یک نوع چندریختی در زمان کامپایل (Static Polymorphism) هست، در حالی که override کردن، چندریختی در زمان اجرا (Dynamic Polymorphism) رو پیاده میکنه.
🔖 هشتگها:
#DotNet #OOP #Polymorphism #Overloading
5️⃣ قابلیت EF Core که باید بدانید 💡
بیایید صادق باشیم. همه ما میلیونها کار روی سرمان ریخته و شیرجه عمیق در هر گوشه و کنار EF Core ممکن است در بالای لیست اولویتهای شما نباشد.
اما قضیه این است: EF Core قدرتمند است و دانستن چند ویژگی کلیدی میتواند زمان و خستگی زیادی را از شما بگیرد.
بنابراین، من شما را با تک تک ویژگیهای EF Core بمباران نخواهم کرد.
در عوض، من پنج مورد ضروری را که واقعاً باید بدانید، دستچین کردهام.
ما موارد زیر را بررسی خواهیم کرد:
1️⃣ Query Splitting
- بهترین دوست جدید دیتابیس شما
2️⃣ Bulk Updates and Deletes
- کارایی به توان دو
3️⃣ Raw SQL Queries
- وقتی نیاز دارید یاغی شوید
4️⃣ Query Filters
- برای تمیز و مرتب نگه داشتن همه چیز
5️⃣ Eager Loading
- چون تنبلی همیشه هم خوب نیست
بزن بریم!
1️⃣ Query Splitting (تقسیم کوئری) ✂️
تقسیم کوئری یکی از آن ویژگیهای EF Core است که به ندرت به آن نیاز دارید. تا اینکه یک روز، به آن نیاز پیدا میکنید. Query splitting در سناریوهایی که چندین کالکشن را eager load میکنید، مفید است. این به ما کمک میکند تا از مشکل انفجار کارتزین (cartesian explosion) جلوگیری کنیم.
Department department =
context.Departments
.Include(d => d.Teams)
.Include(d => d.Employees)
.Where(d => d.Id == departmentId)
.AsSplitQuery() // جادو اینجاست
.First();
با AsSplitQuery، EF Core برای هر navigation کالکشن، یک کوئری SQL اضافی اجرا خواهد کرد. با این حال، مراقب باشید که بیش از حد از آن استفاده نکنید.
2️⃣ Bulk Updates and Deletes (آپدیتها و حذفهای دستهای) ⚡️
EF Core 7
دو API جدید برای انجام آپدیتها و حذفهای دستهای اضافه کرد، ExecuteUpdate و ExecuteDelete. آنها به شما اجازه میدهند تعداد زیادی از ردیفها را در یک رفت و برگشت به دیتابیس به طور موثر آپدیت کنید.
// افزایش ۵٪ حقوق تمام کارمندان فروش در یک کوئری
context.Employees
.Where(e => e.Department == "Sales")
.ExecuteUpdate(s => s.SetProperty(e => e.Salary, e => e.Salary * 1.05m));
// حذف تمام سبدهای خرید قدیمیتر از یک سال در یک کوئری
context.Carts
.Where(o => o.CreatedOn < DateTime.Now.AddYears(-1))
.ExecuteDelete();
این کار بدون بارگذاری انتیتیها در حافظه، مستقیماً دستور UPDATE یا DELETE را در دیتابیس اجرا میکند.
3️⃣ Raw SQL Queries (کوئریهای SQL خام) 👨💻
EF Core 8
یک ویژگی جدید اضافه کرد که به ما اجازه میدهد انواع داده مپنشده را با SQL خام کوئری بزنیم.
public class ProductSummary
{
public int ProductId { get; set; }
public string ProductName { get; set; }
public decimal TotalSales { get; set; }
}
var productSummaries = await context.Database
.SqlQuery<ProductSummary>(
$"""
SELECT p.ProductId, p.ProductName, SUM(oi.Quantity * oi.UnitPrice) AS TotalSales
FROM Products p ...
""")
.ToListAsync();
متد SqlQuery یک IQueryable برمیگرداند، که به شما اجازه میدهد کوئریهای SQL خام را با LINQ ترکیب کنید.
4️⃣ Query Filters (فیلترهای کوئری) 🔍
فیلترهای کوئری مانند دستورات WHERE قابل استفاده مجدد هستند که میتوانید به انتیتیهای خود اعمال کنید. این فیلترها به طور خودکار به کوئریهای LINQ هر زمان که انتیتیهای نوع مربوطه را بازیابی میکنید، اضافه میشوند.
کاربردهای رایج:
• حذف منطقی (Soft Deletes)
• چند-مستأجری (Multi-tenancy)
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
// _currentTenantId بر اساس درخواست/زمینه فعلی تنظیم میشود
modelBuilder.Entity<Product>().HasQueryFilter(p => p.TenantId == _currentTenantId);
}
// حالا، کوئریها به طور خودکار بر اساس مستأجر فیلتر میشوند
var productsForCurrentTenant = context.Products.ToList();
5️⃣ Eager Loading (بارگذاری مشتاقانه) ⏬
بارگذاری مشتاقانه قابلیتی در EF Core است که به شما اجازه میدهد انتیتیهای مرتبط را به همراه انتیتی اصلی خود در یک کوئری دیتابیس واحد بارگذاری کنید.
internal sealed class
VerifyEmail(AppDbContext context)
{
public async Task<bool> Handle(Guid tokenId)
{
EmailVerificationToken? token = await context.EmailVerificationTokens
.Include(e => e.User) // User مرتبط را همزمان لود کن
.FirstOrDefaultAsync(e => e.Id == tokenId);
// ...
}
}
EF Core
یک کوئری SQL واحد تولید میکند که جداول EmailVerificationToken و User را join میکند.
خلاصه 📝
پس، این هم از این! پنج ویژگی EF Core که، صراحتاً، نمیتوانید از ندانستنشان شانه خالی کنید. به یاد داشته باشید، تسلط بر EF Core زمان میبرد، اما این ویژگیها یک پایه محکم برای ساختن فراهم میکنند.
یک توصیه دیگر این است که عمیقاً درک کنید دیتابیس شما چگونه کار میکند. تسلط بر SQL همچنین به شما اجازه میدهد بیشترین ارزش را از EF Core بدست آورید.
🔖 هشتگها:
#EntityFrameworkCore #EFCore #Performance #Database #SQL
💡 تکنیک Clean Code: جایگزینی if های پیچیده با متدهای گویا
شرطهای if پیچیده، خوانایی کد را به شدت پایین میآورند. 😫
وقتی چند شرط مجزا با هم ترکیب میشوند، درک منطق در نگاه اول حتی سختتر هم میشود.
اما میتوانیم این مشکل را با یک بازآرایی (refactoring) ساده حل کنیم.
راه حل: ↙️
منطق شرط را به یک متد یا پراپرتی با اسم گویا منتقل کنید.
👍🏻حالا نام توصیفی متد، توضیح میدهد که شرط چیست.
به یاد داشته باشید: درک زبان طبیعی برای انسانها، همیشه راحتتر از درک کد است.
📖 سری آموزشی کتاب C# 12 in a Nutshell
تا حالا شده بخواید یه کالکشن بسازید که بتونه هر نوع دادهای رو تو خودش نگه داره، از int و bool گرفته تا string و کلاسهای خودتون؟
این کار به لطف پدر همه تایپها در داتنت، یعنی System.Object، ممکنه. اما این قابلیت، یه راز عملکردی مهم به اسم Boxing و Unboxing رو تو دل خودش داره.
در #C، هر نوع دادهای، چه Value Type (مثل int) و چه Reference Type (مثل string)، به صورت پنهان از کلاس System.Object ارثبری میکنه. این یعنی شما میتونید هر متغیری رو به یه متغیر از نوع object تبدیل کنید (Upcast).
مثال (یک Stack همهکاره):
سوال اینجاست: چطور یه int که Value Type هست، میتونه مثل یه object که Reference Type هست رفتار کنه؟ با جادوی Boxing و Unboxing.
• Boxing (بستهبندی):
وقتی شما یه Value Type (مثل int) رو داخل یه متغیر object میریزید، CLR یه "جعبه" روی هیپ (Heap) میسازه، کپی از مقدار شما رو داخل اون میذاره و رفرنس اون جعبه رو به شما میده.
• Unboxing (باز کردن بسته):
وقتی میخواید مقدار رو از اون جعبه در بیارید، عملیات برعکس یعنی Unboxing اتفاق میفته که نیاز به کست صریح داره.
Unboxing
باید به نوع دقیق و اصلی انجام بشه. اگه سعی کنید یه int باکس شده رو به long آنباکس کنید، با خطای InvalidCastException مواجه میشید.
نکته حیاتی: Boxing یه کپی از مقدار شما رو میسازه. این یعنی اگه بعداً متغیر اصلی رو تغییر بدید، مقدار داخل جعبه دستنخورده باقی میمونه!
دونستن مفهوم Boxing و Unboxing برای درک پرفورمنس در #C حیاتیه، چون این عملیاتها هزینه حافظه و پردازش دارن. به همین دلیله که جنریکها (Generics) اختراع شدن تا جلوی این اتفاق رو بگیرن (که بعداً بهش میرسیم).
👑 پدر همه تایپها: object و راز Boxing/Unboxing در #C
تا حالا شده بخواید یه کالکشن بسازید که بتونه هر نوع دادهای رو تو خودش نگه داره، از int و bool گرفته تا string و کلاسهای خودتون؟
این کار به لطف پدر همه تایپها در داتنت، یعنی System.Object، ممکنه. اما این قابلیت، یه راز عملکردی مهم به اسم Boxing و Unboxing رو تو دل خودش داره.
1️⃣ object: جد بزرگ همه!
در #C، هر نوع دادهای، چه Value Type (مثل int) و چه Reference Type (مثل string)، به صورت پنهان از کلاس System.Object ارثبری میکنه. این یعنی شما میتونید هر متغیری رو به یه متغیر از نوع object تبدیل کنید (Upcast).
مثال (یک Stack همهکاره):
public class Stack
{
int position;
object[] data = new object[10];
public void Push(object obj) => data[position++] = obj;
public object Pop() => data[--position];
}
// --- نحوه استفاده ---
var stack = new Stack();
stack.Push("hello");
stack.Push(123); // حتی int!
stack.Push(false); // حتی bool!
// موقع خروج، باید Downcast کنیم
int myNumber = (int)stack.Pop(); // 123
string myString = (string)stack.Pop(); // "hello"
2️⃣ جادوی پشت پرده: Boxing و Unboxing 🎁
سوال اینجاست: چطور یه int که Value Type هست، میتونه مثل یه object که Reference Type هست رفتار کنه؟ با جادوی Boxing و Unboxing.
• Boxing (بستهبندی):
وقتی شما یه Value Type (مثل int) رو داخل یه متغیر object میریزید، CLR یه "جعبه" روی هیپ (Heap) میسازه، کپی از مقدار شما رو داخل اون میذاره و رفرنس اون جعبه رو به شما میده.
• Unboxing (باز کردن بسته):
وقتی میخواید مقدار رو از اون جعبه در بیارید، عملیات برعکس یعنی Unboxing اتفاق میفته که نیاز به کست صریح داره.
int x = 9;
object obj = x; // Boxing: مقدار x در یک جعبه روی هیپ کپی میشود
int y = (int)obj; // Unboxing: مقدار از جعبه کپی شده و به y ریخته میشود
3️⃣ تلهها و نکات مهم ⚠️ تله InvalidCastException:
Unboxing
باید به نوع دقیق و اصلی انجام بشه. اگه سعی کنید یه int باکس شده رو به long آنباکس کنید، با خطای InvalidCastException مواجه میشید.
object obj = 9; // این یک int باکس شده است
// long l = (long)obj; // ❌ InvalidCastException!
تله کپی شدن مقدار:
نکته حیاتی: Boxing یه کپی از مقدار شما رو میسازه. این یعنی اگه بعداً متغیر اصلی رو تغییر بدید، مقدار داخل جعبه دستنخورده باقی میمونه!
int i = 3;
object boxed = i; // یک کپی از 3 در boxed قرار گرفت
i = 5; // تغییر i اصلی، تأثیری روی مقدار باکس شده ندارد
Console.WriteLine(boxed); // خروجی: 3
🤔 حرف حساب و تجربه شما
دونستن مفهوم Boxing و Unboxing برای درک پرفورمنس در #C حیاتیه، چون این عملیاتها هزینه حافظه و پردازش دارن. به همین دلیله که جنریکها (Generics) اختراع شدن تا جلوی این اتفاق رو بگیرن (که بعداً بهش میرسیم).
🔖 هشتگها:
#OOP #MemoryManagement #Boxing
پیادهسازی حذف نرم (Soft Delete) با EF Core 💾
حذف کردن یا نکردن، مسئله این است. 🤔
روش سنتی برای حذف اطلاعات در پایگاه داده، از طریق "حذف سخت" (hard delete) است. یک حذف سخت، یک رکورد را برای همیشه از جدول پایگاه داده پاک میکند. اگرچه این روش ساده به نظر میرسد، اما ریسک قابل توجهی دارد: وقتی دادهای حذف شد، برای همیشه از بین رفته است. 💥
به جای حذف فیزیکی یک رکورد، در "حذف نرم" (soft delete) آن را به عنوان حذف شده علامتگذاری میکنیم، معمولاً با قرار دادن یک فلگ مانند IsDeleted به مقدار true. رکورد در پایگاه داده باقی میماند، اما به طور موثر از کوئریهای عادی برنامه پنهان میشود. 🛡️
امروز، به جزئیات نحوه پیادهسازی حذف نرم با استفاده از EF Core خواهیم پرداخت. در مورد فیلترهای کوئری سراسری بحث خواهیم کرد، روشهای کارآمد برای مدیریت دادههای حذف نرم شده را بررسی میکنیم و مزایا و معایب آن را میسنجیم. 🚀
حذف نرم (Soft Delete) چیست؟ 🧐
حذف نرم یک استراتژی پایداری داده است که از حذف دائمی رکوردها از پایگاه داده شما جلوگیری میکند. به جای حذف داده از پایگاه داده، یک فلگ روی رکورد تنظیم میشود که آن را به عنوان "حذف شده" مشخص میکند.
این رویکرد به برنامه اجازه میدهد تا در کوئریهای عادی این رکوردها را نادیده بگیرد. با این حال، در صورت لزوم میتوانید این رکوردها را بازیابی کنید. حذف نرم همچنین زمانی کاربردی است که بخواهید قیود کلید خارجی (foreign key constraints) را حفظ کنید. حذف نرم یک عملیات "غیرمخرب" است، در مقابل حذف سخت که در آن دادهها به طور کامل حذف میشوند. 🔄
یک حذف سخت از دستور DELETE در SQL استفاده میکند:
DELETE FROM bookings.Reviews
WHERE Id = @BookingId;
از طرف دیگر، یک حذف نرم از دستور UPDATE استفاده میکند:
UPDATE bookings.Reviews
SET IsDeleted = 1, DeletedOnUtc = @UtcNow
WHERE Id = @BookingId;
دادهها هنوز در پایگاه داده وجود دارند و این عملیات قابل بازگشت است.
اما باید به خاطر داشته باشید که هنگام کوئری گرفتن از پایگاه داده، دادههای حذف نرم شده را فیلتر کنید:
SELECT *
FROM bookings.Reviews
WHERE IsDeleted = 0;
بیایید ببینیم چگونه میتوانیم حذف نرم را با EF Core پیادهسازی کنیم. 👨💻
حذف نرم با استفاده از رهگیرها (Interceptors) در EF Core ⚙️
رهگیرهای EF Core یک مکانیزم قدرتمند برای رهگیری و تغییر عملیات پایگاه داده فراهم میکنند. به عنوان مثال، میتوانید عملیات ذخیره تغییرات (saving changes) را برای پیادهسازی قابلیت حذف نرم رهگیری کنید.
بیایید یک اینترفیس نشانگر ISoftDeletable برای نمایش موجودیتهای قابل حذف نرم ایجاد کنیم:
public interface ISoftDeletable
{
bool IsDeleted { get; set; }
DateTime? DeletedOnUtc { get; set; }
}
موجودیتهایی که باید از حذف نرم پشتیبانی کنند، این اینترفیس را پیادهسازی خواهند کرد. شما باید مایگریشن مربوطه را برای ایجاد این ستونها در پایگاه داده اعمال کنید.
مولفه بعدی که نیاز داریم یک SaveChangesInterceptor است که به ما اجازه میدهد به متد SavingChangesAsync (یا SavingChanges) متصل شویم. ما میتوانیم به ChangeTracker دسترسی پیدا کنیم و به دنبال ورودیهایی بگردیم که ISoftDeletable را پیادهسازی کرده و برای حذف شدن علامتگذاری شدهاند. این موضوع را میتوانیم با بررسی اینکه وضعیت موجودیت (entity state) برابر با EntityState.Deleted است، تشخیص دهیم.
وقتی موجودیتهایی را که برای حذف علامتگذاری شدهاند پیدا کردیم، در یک حلقه آنها را پیمایش کرده و وضعیتشان را به EntityState.Modified تغییر میدهیم. همچنین باید مقادیر مربوط به پراپرتیهای IsDeleted و DeletedOnUtc را تنظیم کنید. این کار باعث میشود EF به جای عملیات DELETE، یک عملیات UPDATE ایجاد کند.
این رویکرد تضمین میکند که تمام عملیات حذف در سراسر برنامه از سیاست حذف نرم پیروی میکنند.
شما باید SoftDeleteInterceptor را با تزریق وابستگی (dependency injection) ثبت کرده و آن را با ApplicationDbContext پیکربندی کنید.
برای اطمینان از اینکه رکوردهای حذف نرم شده به طور خودکار از کوئریها حذف میشوند، میتوانیم از فیلترهای کوئری سراسری (global query filters) در EF Core استفاده کنیم. ما میتوانیم فیلترهای کوئری را با استفاده از متد OnModelCreating روی موجودیتها اعمال کنیم تا رکوردهای علامتگذاری شده به عنوان حذف شده، به طور خودکار مستثنی شوند. این ویژگی نوشتن کوئریها را به شدت ساده میکند.
در اینجا نحوه پیکربندی فیلتر کوئری حذف نرم آمده است:
یک محدودیت این است که شما نمیتوانید بیش از یک فیلتر کوئری برای هر موجودیت پیکربندی کنید.
با این حال، گاهی اوقات لازم است که رکوردهای حذف نرم شده را به صراحت در کوئریها لحاظ کنید. شما میتوانید این کار را با استفاده از متد IgnoreQueryFilters انجام دهید.
برای بهبود عملکرد کوئری، به ویژه در جداولی که تعداد زیادی رکورد حذف نرم شده دارند، میتوانید از ایندکسهای فیلتر شده (filtered indexes) استفاده کنید. یک ایندکس فیلتر شده فقط شامل رکوردهایی است که معیارهای مشخص شده را برآورده میکنند. این کار اندازه ایندکس را کاهش داده و زمان اجرای کوئری را برای عملیاتی که رکوردهای فیلتر شده را مستثنی میکنند، بهبود میبخشد. اکثر پایگاه دادههای محبوب از ایندکسهای فیلتر شده پشتیبانی میکنند.
در اینجا نحوه پیکربندی یک ایندکس فیلتر شده با EF Core آمده است:
public sealed class SoftDeleteInterceptor : SaveChangesInterceptor
{
public override ValueTask<InterceptionResult<int>> SavingChangesAsync(
DbContextEventData eventData,
InterceptionResult<int> result,
CancellationToken cancellationToken = default)
{
if (eventData.Context is null)
{
return base.SavingChangesAsync(
eventData, result, cancellationToken);
}
IEnumerable<EntityEntry<ISoftDeletable>> entries =
eventData
.Context
.ChangeTracker
.Entries<ISoftDeletable>()
.Where(e => e.State == EntityState.Deleted);
foreach (EntityEntry<ISoftDeletable> softDeletable in entries)
{
softDeletable.State = EntityState.Modified;
softDeletable.Entity.IsDeleted = true;
softDeletable.Entity.DeletedOnUtc = DateTime.UtcNow;
}
return base.SavingChangesAsync(eventData, result, cancellationToken);
}
}
این رویکرد تضمین میکند که تمام عملیات حذف در سراسر برنامه از سیاست حذف نرم پیروی میکنند.
شما باید SoftDeleteInterceptor را با تزریق وابستگی (dependency injection) ثبت کرده و آن را با ApplicationDbContext پیکربندی کنید.
services.AddSingleton<SoftDeleteInterceptor>();
services.AddDbContext<ApplicationDbContext>(
(sp, options) => options
.UseSqlServer(connectionString)
.AddInterceptors(
sp.GetRequiredService<SoftDeleteInterceptor>()));
فیلتر کردن خودکار دادههای حذف نرم شده 🔍
برای اطمینان از اینکه رکوردهای حذف نرم شده به طور خودکار از کوئریها حذف میشوند، میتوانیم از فیلترهای کوئری سراسری (global query filters) در EF Core استفاده کنیم. ما میتوانیم فیلترهای کوئری را با استفاده از متد OnModelCreating روی موجودیتها اعمال کنیم تا رکوردهای علامتگذاری شده به عنوان حذف شده، به طور خودکار مستثنی شوند. این ویژگی نوشتن کوئریها را به شدت ساده میکند.
در اینجا نحوه پیکربندی فیلتر کوئری حذف نرم آمده است:
public sealed class ApplicationDbContext(
DbContextOptions<UsersDbContext> options) : DbContext(options)
{
public DbSet<Review> Reviews { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Review>().HasQueryFilter(r => !r.IsDeleted);
}
}
یک محدودیت این است که شما نمیتوانید بیش از یک فیلتر کوئری برای هر موجودیت پیکربندی کنید.
با این حال، گاهی اوقات لازم است که رکوردهای حذف نرم شده را به صراحت در کوئریها لحاظ کنید. شما میتوانید این کار را با استفاده از متد IgnoreQueryFilters انجام دهید.
dbContex.Reviews
.IgnoreQueryFilters()
.Where(r => r.ApartmentId == apartmentId)
.ToList();
کوئریهای سریعتر با استفاده از ایندکس فیلتر شده (Filtered Index) 🚀
برای بهبود عملکرد کوئری، به ویژه در جداولی که تعداد زیادی رکورد حذف نرم شده دارند، میتوانید از ایندکسهای فیلتر شده (filtered indexes) استفاده کنید. یک ایندکس فیلتر شده فقط شامل رکوردهایی است که معیارهای مشخص شده را برآورده میکنند. این کار اندازه ایندکس را کاهش داده و زمان اجرای کوئری را برای عملیاتی که رکوردهای فیلتر شده را مستثنی میکنند، بهبود میبخشد. اکثر پایگاه دادههای محبوب از ایندکسهای فیلتر شده پشتیبانی میکنند.
در اینجا نحوه پیکربندی یک ایندکس فیلتر شده با EF Core آمده است:
public sealed class ApplicationDbContext(
DbContextOptions<UsersDbContext> options) : DbContext(options)
{
public DbSet<Review> Reviews { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Review>().HasQueryFilter(r => !r.IsDeleted);
modelBuilder.Entity<Review>()
.HasIndex(r => r.IsDeleted)
.HasFilter("IsDeleted = 0");
}
}
متد HasFilter فیلتر SQL را برای رکوردهایی که در ایندکس قرار خواهند گرفت، میپذیرد.
شما همچنین میتوانید یک ایندکس فیلتر شده را با استفاده از SQL ایجاد کنید:
شما میتوانید از طریق مستندات، اطلاعات بیشتری در مورد ایندکسهای فیلتر شده کسب کنید.
ارزشش را دارد که فکر کنید آیا اصلاً به حذف نرم رکوردها نیاز دارید یا خیر.
در سیستمهای سازمانی (enterprise)، شما معمولاً به "حذف" داده فکر نمیکنید. مفاهیم تجاری وجود دارند که شامل حذف داده نمیشوند. چند مثال عبارتند از: لغو یک سفارش، بازپرداخت یک پرداخت، یا باطل کردن یک فاکتور. این عملیاتهای "مخرب" سیستم را به حالت قبلی بازمیگردانند. اما از دیدگاه تجاری، شما واقعاً در حال حذف داده نیستید. 💼
حذف نرم در صورتی مفید است که خطر حذف تصادفی وجود داشته باشد. این قابلیت به شما امکان میدهد رکوردهای حذف نرم شده را به راحتی بازیابی کنید.
در هر صورت، در نظر بگیرید که آیا حذف نرم از دیدگاه تجاری منطقی است یا خیر.
حذف نرم یک شبکه ایمنی ارزشمند برای بازیابی اطلاعات ارائه میدهد و میتواند ردیابی دادههای تاریخی را بهبود بخشد.
با این حال، ارزیابی اینکه آیا این روش واقعاً با نیازمندیهای خاص برنامه شما همخوانی دارد، بسیار مهم است. عواملی مانند اهمیت بازیابی دادههای حذف شده، نیازهای ممیزی (auditing) و مقررات صنعت خود را در نظر بگیرید. ایجاد یک ایندکس فیلتر شده میتواند عملکرد کوئری را در جداول دارای رکوردهای حذف نرم شده بهبود بخشد.
اگر تصمیم گرفتید که حذف نرم برای شما مناسب است، EF Core ابزارهای لازم را برای یک پیادهسازی روان و ساده فراهم میکند.
شما همچنین میتوانید یک ایندکس فیلتر شده را با استفاده از SQL ایجاد کنید:
CREATE INDEX IX_Reviews_IsDeleted
ON bookings.Reviews (IsDeleted)
WHERE IsDeleted = 0;
شما میتوانید از طریق مستندات، اطلاعات بیشتری در مورد ایندکسهای فیلتر شده کسب کنید.
آیا واقعاً به حذف نرم نیاز دارید؟ 🤔
ارزشش را دارد که فکر کنید آیا اصلاً به حذف نرم رکوردها نیاز دارید یا خیر.
در سیستمهای سازمانی (enterprise)، شما معمولاً به "حذف" داده فکر نمیکنید. مفاهیم تجاری وجود دارند که شامل حذف داده نمیشوند. چند مثال عبارتند از: لغو یک سفارش، بازپرداخت یک پرداخت، یا باطل کردن یک فاکتور. این عملیاتهای "مخرب" سیستم را به حالت قبلی بازمیگردانند. اما از دیدگاه تجاری، شما واقعاً در حال حذف داده نیستید. 💼
حذف نرم در صورتی مفید است که خطر حذف تصادفی وجود داشته باشد. این قابلیت به شما امکان میدهد رکوردهای حذف نرم شده را به راحتی بازیابی کنید.
در هر صورت، در نظر بگیرید که آیا حذف نرم از دیدگاه تجاری منطقی است یا خیر.
نکات کلیدی (Takeaway) 📌
حذف نرم یک شبکه ایمنی ارزشمند برای بازیابی اطلاعات ارائه میدهد و میتواند ردیابی دادههای تاریخی را بهبود بخشد.
با این حال، ارزیابی اینکه آیا این روش واقعاً با نیازمندیهای خاص برنامه شما همخوانی دارد، بسیار مهم است. عواملی مانند اهمیت بازیابی دادههای حذف شده، نیازهای ممیزی (auditing) و مقررات صنعت خود را در نظر بگیرید. ایجاد یک ایندکس فیلتر شده میتواند عملکرد کوئری را در جداول دارای رکوردهای حذف نرم شده بهبود بخشد.
اگر تصمیم گرفتید که حذف نرم برای شما مناسب است، EF Core ابزارهای لازم را برای یک پیادهسازی روان و ساده فراهم میکند.
🔖 هشتگها:
#EntityFrameworkCore #EFCore #SoftDelete #Database #DataPersistence #SQL
📖 سری آموزشی کتاب C# 12 in a Nutshell
چطور #C جلوی خطاهای مربوط به نوع داده رو میگیره؟ این زبان از یه سیستم امنیتی دو لایه استفاده میکنه: بررسی استاتیک در زمان کامپایل و بررسی در زمان اجرا.
امروز میخوایم با این دو لایه امنیتی و ابزارهای بازرسیشون آشنا بشیم.
این اولین خط دفاعی شماست. کامپایلر #C قبل از اینکه حتی برنامه رو اجرا کنید، کد شما رو بررسی میکنه تا مطمئن بشه که شما نوعهای داده رو به درستی استفاده کردید. این کار جلوی یه عالمه باگ رو در نطفه خفه میکنه.
بعضی وقتا کامپایلر نمیتونه از همه چیز مطمئن باشه (مثلاً موقع Downcasting). اینجا CLR (محیط اجرای داتنت) وارد عمل میشه.
هر آبجکتی که روی هیپ ساخته میشه، یه "توکن نوع" با خودش داره. CLR در زمان اجرا، از این توکن برای چک کردن صحت عملیات کستینگ استفاده میکنه.
برای اینکه خودمون اطلاعات یه نوع رو بدست بیاریم، دو تا ابزار اصلی داریم:
🔹️ GetType() (برای زمان اجرا):
این یه متده که روی یک نمونه (instance) از آبجکت صدا زده میشه و نوع دقیق اون آبجکت رو در زمان اجرا به ما میده.
🔹️ typeof (برای زمان کامپایل):
این یه اپراتوره که اسم یه نوع (Type) رو میگیره و آبجکت System.Type مربوط به اون رو در زمان کامپایل مشخص میکنه.
این سیستم بررسی نوع دو مرحلهای، یکی از دلایل اصلی قدرت و امنیت زبان #C هست.
🛡 بازرسی هویت در #C :
Type Checking در زمان کامپایل و اجرا
چطور #C جلوی خطاهای مربوط به نوع داده رو میگیره؟ این زبان از یه سیستم امنیتی دو لایه استفاده میکنه: بررسی استاتیک در زمان کامپایل و بررسی در زمان اجرا.
امروز میخوایم با این دو لایه امنیتی و ابزارهای بازرسیشون آشنا بشیم.
1️⃣ بررسی استاتیک (Static Type Checking): نگهبان زمان کامپایل 👮♂️
این اولین خط دفاعی شماست. کامپایلر #C قبل از اینکه حتی برنامه رو اجرا کنید، کد شما رو بررسی میکنه تا مطمئن بشه که شما نوعهای داده رو به درستی استفاده کردید. این کار جلوی یه عالمه باگ رو در نطفه خفه میکنه.
// کامپایلر اینجا جلوی شما رو میگیره و اجازه نمیده برنامه ساخته بشه
// چون نمیتونید یه رشته رو تو یه متغیر int بریزید.
int x = "5"; // ❌ خطای زمان کامپایل!
2️⃣ بررسی در زمان اجرا (Runtime Type Checking): نگهبان CLR 🏃♂️
بعضی وقتا کامپایلر نمیتونه از همه چیز مطمئن باشه (مثلاً موقع Downcasting). اینجا CLR (محیط اجرای داتنت) وارد عمل میشه.
هر آبجکتی که روی هیپ ساخته میشه، یه "توکن نوع" با خودش داره. CLR در زمان اجرا، از این توکن برای چک کردن صحت عملیات کستینگ استفاده میکنه.
object y = "5";
// در زمان اجرا، CLR میبینه که آبجکت داخل y یک string است، نه int
// و یک خطای InvalidCastException پرتاب میکنه.
int z = (int)y; // 💥 خطای زمان اجرا!
3️⃣ ابزارهای بازرسی: GetType در برابر typeof 🔎
برای اینکه خودمون اطلاعات یه نوع رو بدست بیاریم، دو تا ابزار اصلی داریم:
🔹️ GetType() (برای زمان اجرا):
این یه متده که روی یک نمونه (instance) از آبجکت صدا زده میشه و نوع دقیق اون آبجکت رو در زمان اجرا به ما میده.
🔹️ typeof (برای زمان کامپایل):
این یه اپراتوره که اسم یه نوع (Type) رو میگیره و آبجکت System.Type مربوط به اون رو در زمان کامپایل مشخص میکنه.
public class Point { public int X, Y; }
// ...
Point p = new Point();
// GetType روی نمونه کار میکنه
Console.WriteLine(p.GetType().Name); // خروجی: Point
// typeof روی خودِ نوع کار میکنه
Console.WriteLine(typeof(Point).Name); // خروجی: Point
// میتونیم نتایج رو با هم مقایسه کنیم
Console.WriteLine(p.GetType() == typeof(Point)); // خروجی: Trueاین سیستم بررسی نوع دو مرحلهای، یکی از دلایل اصلی قدرت و امنیت زبان #C هست.
🔖 هشتگها:
#DotNet #OOP #BestPractices #TypeSystem
📖 سری آموزشی کتاب C# 12 in a Nutshell
تا حالا شده یه آبجکت از کلاس خودتون رو Console.WriteLine کنید و با یه خروجی بیمعنی مثل MyProject.Panda مواجه بشید؟ 😑
راه حل این مشکل، بازنویسی (override) کردن یکی از مهمترین متدهای کلاس object یعنی ToStringهست.
ToString()
یه متد virtual در کلاس System.Object هست. وظیفهش اینه که یه نمایش متنی و خوانا از آبجکت شما برگردونه. این متد به خصوص موقع دیباگ کردن و لاگ انداختن فوقالعاده به درد میخوره، چون به شما اجازه میده وضعیت داخلی آبجکت رو به راحتی ببینید.
کافیه تو کلاس خودتون، متد ToString رو override کنید و هر رشتهای که دوست دارید و وضعیت آبجکت رو بهتر توصیف میکنه، برگردونید.
اگه
یه نکته خیلی مهم در مورد پرفورمنس: وقتی ToString رو مستقیماً روی یه Value Type (مثل int) صدا میزنید، هیچ عمل Boxing اتفاق نمیفته. Boxing (که هزینه پرفورمنس داره) فقط زمانی رخ میده که شما اول اون Value Type رو به object کست کنید.
عادت کردن به override کردن ToString در تمام کلاسهاتون، یکی از بهترین کارهاییه که میتونید برای خودتون (و همتیمیهاتون در آینده) انجام بدید.
🖨 چاپ زیبا با ()ToString: معرفی کردن آبجکتهای شما به دنیا
تا حالا شده یه آبجکت از کلاس خودتون رو Console.WriteLine کنید و با یه خروجی بیمعنی مثل MyProject.Panda مواجه بشید؟ 😑
راه حل این مشکل، بازنویسی (override) کردن یکی از مهمترین متدهای کلاس object یعنی ToStringهست.
1️⃣ ToString() چیست و چرا مهمه؟
ToString()
یه متد virtual در کلاس System.Object هست. وظیفهش اینه که یه نمایش متنی و خوانا از آبجکت شما برگردونه. این متد به خصوص موقع دیباگ کردن و لاگ انداختن فوقالعاده به درد میخوره، چون به شما اجازه میده وضعیت داخلی آبجکت رو به راحتی ببینید.
2️⃣ چطور ToString رو بازنویسی (Override) کنیم؟
کافیه تو کلاس خودتون، متد ToString رو override کنید و هر رشتهای که دوست دارید و وضعیت آبجکت رو بهتر توصیف میکنه، برگردونید.
public class Panda
{
public string Name;
// بازنویسی متد ToString
public override string ToString() => Name;
}
// --- نحوه استفاده ---
Panda p = new Panda { Name = "Petey" };
// Console.WriteLine به صورت خودکار ToString() رو روی آبجکتها صدا میزنه
Console.WriteLine(p);
// خروجی:
// Petey
اگه
ToString رو بازنویسی نکنید، خروجی پیشفرض، اسم کامل تایپ خواهد بود.3️⃣ نکته فنی: ToString و تلهی Boxing ⚠️
یه نکته خیلی مهم در مورد پرفورمنس: وقتی ToString رو مستقیماً روی یه Value Type (مثل int) صدا میزنید، هیچ عمل Boxing اتفاق نمیفته. Boxing (که هزینه پرفورمنس داره) فقط زمانی رخ میده که شما اول اون Value Type رو به object کست کنید.
int x = 1;
// ✅ بدون Boxing: بهینه و سریع
string s1 = x.ToString();
// ❌ با Boxing: هزینه پرفورمنس داره
object box = x;
string s2 = box.ToString();
🤔 حرف حساب و تجربه شما
عادت کردن به override کردن ToString در تمام کلاسهاتون، یکی از بهترین کارهاییه که میتونید برای خودتون (و همتیمیهاتون در آینده) انجام بدید.
🔖 هشتگها:
#DotNet #OOP #ToString
Forwarded from thisisnabi.dev [Farsi]
به نظر من، بهینه سازی اینجوریه که هرچقدر شما جلوتر میرید باید زمان بیشتری صرف تغییرات کمتر کنید.
به زبان ساده میشه گفت همون قانون ۸۰/۲۰ یا پارتو اینجا صدق میکنه؛ یعنی معمولاً ۸۰ درصد نتیجه رو توی ۲۰ درصد تلاش اولیه میشه گرفت. اما وقتی جلوتر میری، برای همون چند درصد باقیمونده باید کلی زمان و انرژی بیشتری بذاری تا تغییر کوچیکی اتفاق بیفته.
به زبان ساده میشه گفت همون قانون ۸۰/۲۰ یا پارتو اینجا صدق میکنه؛ یعنی معمولاً ۸۰ درصد نتیجه رو توی ۲۰ درصد تلاش اولیه میشه گرفت. اما وقتی جلوتر میری، برای همون چند درصد باقیمونده باید کلی زمان و انرژی بیشتری بذاری تا تغییر کوچیکی اتفاق بیفته.
بررسیهای سلامت (Health Checks) در ASP.NET Core برای مانیتورینگ اپلیکیشنهای شما 🩺
همه ما میخواهیم اپلیکیشنهای قوی و قابل اعتماد بسازیم که بتوانند به طور نامحدود مقیاسپذیر باشند و هر تعداد درخواستی را مدیریت کنند.
اما با افزایش پیچیدگی سیستمهای توزیعشده و معماریهای میکروسرویس، مانیتور کردن سلامت اپلیکیشنهای ما به طور فزایندهای دشوارتر میشود.
حیاتی است که شما سیستمی برای دریافت بازخورد سریع از سلامت اپلیکیشن خود داشته باشید.
اینجاست که health checks وارد میشوند.
بررسیهای سلامت راهی برای مانیتور و تأیید سلامت کامپوننتهای مختلف یک اپلیکیشن فراهم میکنند، از جمله:
🔹 دیتابیسها
🔹 APIها
🔹 کشها
🔹 سرویسهای خارجی
در این مقاله به شما نشان خواهم داد:
🔹 Health checks چه هستند
🔹 افزودن یک health check سفارشی
🔹 استفاده از کتابخانههای health check موجود
🔹 سفارشیسازی فرمت پاسخ health checks
بیایید ببینیم چگونه health checks را در ASP.NET Core پیادهسازی کنیم.
Health Checks چه هستند؟ 🤔
یک مکانیزم پیشگیرانه برای مانیتورینگ و تأیید سلامت و در دسترس بودن یک اپلیکیشن در ASP.NET Core هستند.
در اینجا پیکربندی اولیه آمده است که سرویسهای health check را ثبت کرده و HealthCheckMiddleware را برای پاسخگویی در URL مشخص شده اضافه میکند.
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddHealthChecks();
var app = builder.Build();
app.MapHealthChecks("/health");
app.Run();
health check
یک مقدار HealthStatus را برمیگرداند که سلامت سرویس را نشان میدهد.
سه مقدار متمایز HealthStatus وجود دارد:
🟢 HealthStatus.Healthy
🟡 HealthStatus.Degraded
🔴 HealthStatus.Unhealthy
شما میتوانید از HealthStatus برای نشان دادن حالتهای مختلف اپلیکیشن خود استفاده کنید.
برای مثال، اگر اپلیکیشن کندتر از حد انتظار عمل میکند، میتوانید HealthStatus.Degraded را برگردانید.
افزودن Health Checks سفارشی 👨🔧
شما میتوانید health checks سفارشی را با پیادهسازی اینترفیس IHealthCheck ایجاد کنید.
برای مثال، میتوانید یک چک برای بررسی در دسترس بودن دیتابیس SQL خود پیادهسازی کنید.
مهم است که از یک کوئری استفاده کنید که بتواند به سرعت در دیتابیس کامل شود، مانند SELECT 1.
در اینجا یک مثال از پیادهسازی health check سفارشی در کلاس SqlHealthCheck آمده است:
public class SqlHealthCheck : IHealthCheck
{
private readonly string _connectionString;
public SqlHealthCheck(IConfiguration configuration)
{
_connectionString = configuration.GetConnectionString("Database");
}
public async Task<HealthCheckResult> CheckHealthAsync(
HealthCheckContext context,
CancellationToken cancellationToken = default)
{
try
{
using var sqlConnection = new SqlConnection(_connectionString);
await sqlConnection.OpenAsync(cancellationToken);
using var command = sqlConnection.CreateCommand();
command.CommandText = "SELECT 1";
await command.ExecuteScalarAsync(cancellationToken);
return HealthCheckResult.Healthy();
}
catch(Exception ex)
{
return HealthCheckResult.Unhealthy(
context.Registration.FailureStatus,
exception: ex);
}
}
}
پس از پیادهسازی health check سفارشی، باید آن را ثبت کنید.
فراخوانی قبلی به AddHealthChecks اکنون به این شکل میشود:
builder.Services.AddHealthChecks()
.AddCheck<SqlHealthCheck>("custom-sql", HealthStatus.Unhealthy);
ما به آن یک نام سفارشی میدهیم و مشخص میکنیم که کدام وضعیت به عنوان نتیجه شکست در
HealthCheckContext.Registration.FailureStatus
استفاده شود.
اما یک لحظه بایستید و فکر کنید.
آیا میخواهید برای هر سرویس خارجی که دارید، یک health check سفارشی را خودتان پیادهسازی کنید؟
البته که نه! یک راه حل بهتر وجود دارد.
استفاده از کتابخانههای Health Check موجود 📚
قبل از اینکه برای همه چیز یک health check سفارشی پیادهسازی کنید، ابتدا باید ببینید آیا از قبل کتابخانه موجودی وجود دارد یا نه.
در ریپازیتوری AspNetCore.Diagnostics.HealthChecks میتوانید مجموعه وسیعی از پکیجهای health check برای سرویسها و کتابخانههای پرکاربرد را پیدا کنید.
در اینجا فقط چند مثال آمده است:
SQL Server -
AspNetCore.HealthChecks.SqlServer
Postgres -
AspNetCore.HealthChecks.Npgsql
Redis -
AspNetCore.HealthChecks.Redis
RabbitMQ -
AspNetCore.HealthChecks.RabbitMQ
در اینجا نحوه افزودن health checks برای PostgreSQL و RabbitMQ آمده است:
builder.Services.AddHealthChecks()
.AddCheck<SqlHealthCheck>("custom-sql", HealthStatus.Unhealthy);
.AddNpgSql(pgConnectionString)
.AddRabbitMQ(rabbitConnectionString)
فرمتدهی پاسخ Health Checks 🎨
بهطور پیشفرض، endpointی که وضعیت health check شما را برمیگرداند، یک مقدار رشتهای را که نماینده یک HealthStatus است، برمیگرداند.
این عملی نیست اگر چندین health check پیکربندی کرده باشید، زیرا میخواهید وضعیت سلامت را به صورت جداگانه برای هر سرویس مشاهده کنید.
بدتر از آن، اگر یکی از سرویسها در حال شکست باشد، کل پاسخ Unhealthy برمیگرداند و شما نمیدانید چه چیزی باعث مشکل شده است.
شما میتوانید این مشکل را با ارائه یک ResponseWriter حل کنید، و یک نمونه موجود در کتابخانه AspNetCore.HealthChecks.UI.Client وجود دارد.
پکیج NuGet را نصب کنید:
Install-Package AspNetCore.HealthChecks.UI.Client
و باید فراخوانی به MapHealthChecks را کمی بهروز کنید تا از ResponseWriter این کتابخانه استفاده کند:
app.MapHealthChecks(
"/health",
new HealthCheckOptions
{
ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse
});
پس از این تغییرات، پاسخ از endpoint health check به این شکل خواهد بود:
{
"status": "Unhealthy",
"totalDuration": "00:00:00.3285211",
"entries": {
"npgsql": { "status": "Healthy", ... },
"rabbitmq": { "status": "Healthy", ... },
"custom-sql": { "status": "Unhealthy", ... }
}
}نکات پایانی 📝
مانیتورینگ اپلیکیشن برای ردیابی در دسترس بودن، استفاده از منابع و تغییرات عملکرد در اپلیکیشن شما مهم است.
مانیتور کردن سلامت اپلیکیشنهای ASP.NET Core شما با ارائه health checks برای سرویسهایتان آسان است.
شما میتوانید تصمیم بگیرید health checks سفارشی را پیادهسازی کنید، اما ابتدا در نظر بگیرید که آیا راهحلهای موجود وجود دارد یا نه.
📖 سری آموزشی کتاب C# 12 in a Nutshell
در #C، ما دو نوع اصلی برای ساختن تایپهای خودمون داریم: class و struct. اکثر ما به صورت پیشفرض از class استفاده میکنیم، اما دونستن اینکه struct چیه و کی باید ازش استفاده کنیم، یه مهارت کلیدیه که کد شما رو بهینهتر و خواناتر میکنه.
دو تا تفاوت بنیادی وجود داره که همه چیز از اونها نشأت میگیره:
این مهمترین تفاوته. struct یک Value Type هست؛ یعنی وقتی اون رو به یه متغیر دیگه یا یه متد پاس میدید، کل آبجکت کپی میشه. class یک Reference Type هست؛ یعنی فقط رفرنس (آدرس حافظه) اون کپی میشه.
استراکت ها از وراثت پشتیبانی نمیکنن. شما نمیتونید یه struct بسازید که از یه struct یا class دیگه ارثبری کنه (البته همهشون به صورت پنهان از System.ValueType ارث میبرن). به همین دلیل، اعضای struct نمیتونن virtual یا abstract باشن.
قانون کلی اینه: وقتی تایپ شما بیشتر شبیه به یک "مقدار" ساده هست تا یک "موجودیت" پیچیده با هویت خاص، struct انتخاب خوبیه.
از این چکلیست استفاده کنید:
✅ برای تایپهای کوچک و داده-محور: مثل Point (نقطه)، Color (رنگ)، یا یک زوج مرتب KeyValuePair.
✅ وقتی تغییرناپذیری (Immutability) مهمه: چون structها کپی میشن، تغییر یک نمونه روی بقیه تأثیر نمیذاره و این باعث امنیت بیشتر کد میشه.
✅ وقتی پرفورمنس خیلی مهمه: ساختن structها (مخصوصاً در آرایههای بزرگ) هزینه حافظه کمتری داره چون سربار آبجکتهای روی هیپ رو ندارن و باعث کاهش فشار روی Garbage Collector میشن.
برای اینکه structهای خودتون رو امنتر و واقعاً تغییرناپذیر کنید، از 8 #C به بعد میتونید از کلمه کلیدی readonly قبل از تعریف struct استفاده کنید. این به کامپایلر میگه که تمام فیلدهای این struct باید readonly باشن.
این کار هم "نیت" شما رو برای ساختن یک تایپ تغییرناپذیر نشون میده و هم به کامپایلر اجازه بهینهسازیهای بیشتری رو میده.
استراکت ها ابزارهای قدرتمندی برای بهینهسازی و نوشتن کدهای خوانا هستن، به شرطی که درست و به جا ازشون استفاده بشه.
🧱 راهنمای کامل struct در #C: کی و چرا از آن استفاده کنیم؟
در #C، ما دو نوع اصلی برای ساختن تایپهای خودمون داریم: class و struct. اکثر ما به صورت پیشفرض از class استفاده میکنیم، اما دونستن اینکه struct چیه و کی باید ازش استفاده کنیم، یه مهارت کلیدیه که کد شما رو بهینهتر و خواناتر میکنه.
1️⃣ struct در برابر class: دوئل اصلی
دو تا تفاوت بنیادی وجود داره که همه چیز از اونها نشأت میگیره:
Value Type در برابر Reference Type: 📜
این مهمترین تفاوته. struct یک Value Type هست؛ یعنی وقتی اون رو به یه متغیر دیگه یا یه متد پاس میدید، کل آبجکت کپی میشه. class یک Reference Type هست؛ یعنی فقط رفرنس (آدرس حافظه) اون کپی میشه.
عدم پشتیبانی از وراثت: 🚫
استراکت ها از وراثت پشتیبانی نمیکنن. شما نمیتونید یه struct بسازید که از یه struct یا class دیگه ارثبری کنه (البته همهشون به صورت پنهان از System.ValueType ارث میبرن). به همین دلیل، اعضای struct نمیتونن virtual یا abstract باشن.
2️⃣ چه زمانی باید از struct استفاده کنیم؟
قانون کلی اینه: وقتی تایپ شما بیشتر شبیه به یک "مقدار" ساده هست تا یک "موجودیت" پیچیده با هویت خاص، struct انتخاب خوبیه.
از این چکلیست استفاده کنید:
✅ برای تایپهای کوچک و داده-محور: مثل Point (نقطه)، Color (رنگ)، یا یک زوج مرتب KeyValuePair.
✅ وقتی تغییرناپذیری (Immutability) مهمه: چون structها کپی میشن، تغییر یک نمونه روی بقیه تأثیر نمیذاره و این باعث امنیت بیشتر کد میشه.
✅ وقتی پرفورمنس خیلی مهمه: ساختن structها (مخصوصاً در آرایههای بزرگ) هزینه حافظه کمتری داره چون سربار آبجکتهای روی هیپ رو ندارن و باعث کاهش فشار روی Garbage Collector میشن.
3️⃣ readonly struct: بهترین شیوه مدرن 🛡
برای اینکه structهای خودتون رو امنتر و واقعاً تغییرناپذیر کنید، از 8 #C به بعد میتونید از کلمه کلیدی readonly قبل از تعریف struct استفاده کنید. این به کامپایلر میگه که تمام فیلدهای این struct باید readonly باشن.
این کار هم "نیت" شما رو برای ساختن یک تایپ تغییرناپذیر نشون میده و هم به کامپایلر اجازه بهینهسازیهای بیشتری رو میده.
public readonly struct Point
{
// تمام فیلدها باید readonly باشن
public readonly int X;
public readonly int Y;
public Point(int x, int y)
{
X = x;
Y = y;
}
// متدهایی که وضعیت رو تغییر نمیدن رو هم میتونید readonly مشخص کنید
public readonly double DistanceFromOrigin()
{
return Math.Sqrt(X * X + Y * Y);
}
}
🤔 حرف حساب و تجربه شما
استراکت ها ابزارهای قدرتمندی برای بهینهسازی و نوشتن کدهای خوانا هستن، به شرطی که درست و به جا ازشون استفاده بشه.
🔖 هشتگها:
#OOP #Struct #Performance
📖 سری آموزشی کتاب C# 12 in a Nutshell
تو پست قبلی، با اصول اولیه و کاربردی structها آشنا شدیم. امروز وقتشه که کلاه غواصی رو سرمون کنیم و به دو تا از عمیقترین و تخصصیترین مباحث مربوط به structها شیرجه بزنیم: رفتار عجیب سازندهها و قابلیت ref struct.
این یکی از گیجکنندهترین بخشهای کار با structهاست. یک struct همیشه یک سازنده پیشفرض بدون پارامتر داره که تمام فیلدها رو صفر میکنه (همون default).
حالا اگه شما خودتون یه سازنده بدون پارامتر بنویسید (که از 10 #C به بعد ممکنه)، اون سازنده پیشفرض حذف نمیشه و هنوز از راههای دیگهای مثل ساختن آرایه، قابل دسترسه!
این کد رو ببینید تا کامل متوجه بشید:
توصیه حرفهای: بهترین کار اینه که
این یه قابلیت خیلی خاص و پیشرفته برای بهینهسازیهای سطح پایینه. یه ref struct، نوعی از struct هست که کامپایلر تضمین میکنه فقط و فقط روی Stack زندگی کنه و هرگز به Heap منتقل نشه.
چرا این خوبه؟ چون به ما اجازه میده با حافظه Stack به صورت خیلی بهینه کار کنیم و از فشار روی Garbage Collector کم کنیم، مثل کاری که <Span<T انجام میده.
محدودیتهای ref struct:
چون ref struct هرگز نباید روی هیپ قرار بگیره، محدودیتهای زیر رو داره:
🚫 نمیتونه عضو یک class باشه.
🚫 نمیتونه عنصر یک آرایه باشه.
🚫 نمیتونه Boxed بشه (به object تبدیل بشه).
🚫 نمیتونه اینترفیس پیادهسازی کنه.
🚫 نمیتونه در متدهای async استفاده بشه.
🤔 حرف حساب و تجربه شما
این دو مفهوم، نهایت عمق و قدرت structها در #C رو نشون میدن.
🚀 شیرجه عمیق در structها: سازندههای گیجکننده و ref struct
تو پست قبلی، با اصول اولیه و کاربردی structها آشنا شدیم. امروز وقتشه که کلاه غواصی رو سرمون کنیم و به دو تا از عمیقترین و تخصصیترین مباحث مربوط به structها شیرجه بزنیم: رفتار عجیب سازندهها و قابلیت ref struct.
1️⃣ تلهی سازندهها: دوگانگی new() و default 🤯
این یکی از گیجکنندهترین بخشهای کار با structهاست. یک struct همیشه یک سازنده پیشفرض بدون پارامتر داره که تمام فیلدها رو صفر میکنه (همون default).
حالا اگه شما خودتون یه سازنده بدون پارامتر بنویسید (که از 10 #C به بعد ممکنه)، اون سازنده پیشفرض حذف نمیشه و هنوز از راههای دیگهای مثل ساختن آرایه، قابل دسترسه!
این کد رو ببینید تا کامل متوجه بشید:
struct Point
{
int x = 1;
int y;
// سازنده سفارشی بدون پارامتر
public Point() => y = 1;
}
// --- نتایج عجیب ---
// سازنده صریح و سفارشی ما صدا زده میشه
Point p1 = new Point();
Console.WriteLine($"p1: ({p1.x}, {p1.y})");
// خروجی: p1: (1, 1)
// سازنده پیشفرضِ صفرکننده صدا زده میشه
Point p2 = default;
Console.WriteLine($"p2: ({p2.x}, {p2.y})");
// خروجی: p2: (0, 0)
// آرایهها هم از سازنده پیشفرض و صفرکننده استفاده میکنن
Point[] points = new Point[1];
Console.WriteLine($"points[0]: ({points[0].x}, {points[0].y})");
// خروجی: points[0]: (0, 0)
توصیه حرفهای: بهترین کار اینه که
structهاتون رو جوری طراحی کنید که حالت پیشفرض و صفر شدهشون، یک حالت معتبر و قابل استفاده باشه.2️⃣ ref struct: زندگی فقط روی Stack! ⚡️
این یه قابلیت خیلی خاص و پیشرفته برای بهینهسازیهای سطح پایینه. یه ref struct، نوعی از struct هست که کامپایلر تضمین میکنه فقط و فقط روی Stack زندگی کنه و هرگز به Heap منتقل نشه.
چرا این خوبه؟ چون به ما اجازه میده با حافظه Stack به صورت خیلی بهینه کار کنیم و از فشار روی Garbage Collector کم کنیم، مثل کاری که <Span<T انجام میده.
محدودیتهای ref struct:
چون ref struct هرگز نباید روی هیپ قرار بگیره، محدودیتهای زیر رو داره:
🚫 نمیتونه عضو یک class باشه.
🚫 نمیتونه عنصر یک آرایه باشه.
🚫 نمیتونه Boxed بشه (به object تبدیل بشه).
🚫 نمیتونه اینترفیس پیادهسازی کنه.
🚫 نمیتونه در متدهای async استفاده بشه.
🤔 حرف حساب و تجربه شما
این دو مفهوم، نهایت عمق و قدرت structها در #C رو نشون میدن.
🔖 هشتگها:
#AdvancedCSharp #Struct #Performance #MemoryManagement