C# Geeks (.NET)
334 subscribers
128 photos
1 video
98 links
Download Telegram
ساختن رشته‌ها در #C :
+ در برابر $ (الحاق یا درون‌یابی؟)

همه‌ی ما یه زمانی اینجوری رشته می‌ساختیم:
 string s = "User: " + name + " | Age: " + age;

این کد کار می‌کنه، ولی آیا بهترین، خواناترین و بهینه‌ترین راهه؟

بیاید دو روش اصلی ساختن رشته در سی‌‌شارپ رو با تمام جزئیاتشون کالبدشکافی کنیم و ببینیم کی باید از کدوم استفاده کرد.

🔗 روش سنتی: الحاق با + و تله‌ی پرفورمنس
این ساده‌ترین روشه. اپراتور + دو تا رشته رو به هم می‌چسبونه. اگه یکی از مقادیر رشته نباشه، #C به صورت خودکار متد ()ToString رو روش صدا می‌زنه.

string s = "User ID: " + 5; // خروجی: "User ID: 5"


اما تله بزرگ کجاست؟
یادتونه تو پست "رازهای string" گفتیم که stringها تغییرناپذیر (Immutable) هستن؟ این یعنی هر بار که از + استفاده می‌کنید، #C یه رشته کاملاً جدید در حافظه میسازه و قبلی‌ها رو دور می‌ریزه.

اگه این کار رو تو یه حلقه انجام بدید، دارید رسماً حافظه رو به آتیش می‌کشید و برنامه‌تون رو به شدت کند می‌کنید!

راه حل حرفه‌ای (برای موارد پیچیده)

برای ساختن رشته‌های داینامیک در حلقه‌ها، همیشه از کلاس System.Text.StringBuilder استفاده کنید که بعداً مفصل در موردش صحبت می‌کنیم.

💲 روش مدرن و حرفه‌ای: درون‌یابی با $ (String Interpolation)
این روش که با گذاشتن $ قبل از رشته فعال میشه، خواناترین و قدرتمندترین راه برای ساختن رشته‌هاست. شما می‌تونید متغیرها و عبارات #C رو مستقیم داخل رشته و بین آکولاد {} بنویسید.

int x = 4;
Console.WriteLine($"A square has {x} sides."); // خروجی: A square has 4 sides


قدرت‌های پنهان درون‌یابی:

فرمت‌دهی 🎨: می‌تونید خروجی رو همونجا با گذاشتن : و یه "فرمت استرینگ" کنترل کنید. مثلاً برای نمایش عدد در مبنای هگزادسیمال:

string s = $"255 in hex is {byte.MaxValue:X2}"; // X2 یعنی هگزادسیمال با ۲ رقم
// خروجی: "255 in hex is FF"

عبارات پیچیده 🤯: اگه عبارتتون پیچیده‌ست
یا خودش : داره (مثل اپراتور سه‌تایی)، کل عبارت رو داخل پرانتز بذارید:

bool b = true;
Console.WriteLine($"The answer is {(b ? 1 : 0)}");


رشته‌های ثابت (از C# 10) 💎: اگه تمام مقادیر داخل $ ثابت باشن، خود رشته هم می‌تونه const باشه:

const string greeting = "Hello";
const string message = $"{greeting}, world";


رشته‌های چند خطی (از C# 11) 📄: رشته‌های درون‌یابی می‌تونن چند خطی هم باشن که کار رو خیلی راحت‌تر می‌کنه:

string s = $"This interpolation spans {1 + 1} lines";


🤔 حرف حساب و انتخاب شما
برای چسبوندن دو یا سه تا رشته ساده ⟵ + کار راه اندازه.

برای هر حالت دیگه‌ای، به خصوص وقتی متغیرها رو داخل رشته میارید ⟵ $ (درون‌یابی) انتخاب اول و آخر شماست. خوانا، قدرتمند و مدرن.

شما تو کدهاتون بیشتر از کدوم روش استفاده می‌کنید؟ آیا تا حالا با مشکل پرفورمنس به خاطر استفاده زیاد از + تو حلقه‌ها برخورد کردید؟ یا چه ترفند باحالی برای فرمت کردن رشته‌ها با $ بلدید؟

🔖 هشتگ‌ها :
#CSharp
#String
#DotNet
#Performance
🛡 ابزار حرفه‌ای #C :
بهینه‌سازی پرفورمنس با پارامتر in

فرض کنید یه struct خیلی بزرگ دارید (مثلاً با کلی فیلد). وقتی این struct رو به یه متد پاس میدید، #C یه کپی کامل ازش میسازه که می‌تونه روی پرفورمنس تأثیر منفی بذاره.

حالا اگه نخوایم این کپی‌کاری پرهزینه انجام بشه، ولی در عین حال مطمئن باشیم که متد، مقدار اصلی رو به هیچ وجه تغییر نمیده، چیکار کنیم؟
✨️اینجاست که کلمه کلیدی in وارد میدان میشه.


‌حالا in چیست؟ رفرنسِ فقط-خواندنی (Read-Only)
کلمه کلیدی in به کامپایلر میگه:

"این پارامتر رو بر اساس رفرنس بفرست تا از کپی کردنش جلوگیری بشه، ولی داخل متد، باهاش مثل یه مقدار فقط-خواندنی (Read-Only) رفتار کن و اجازه هیچ تغییری بهش نده."

🧨پس in دو تا کار خفن رو با هم انجام میده:
1️⃣افزایش پرفورمنس: از کپی شدن structهای بزرگ جلوگیری می‌کنه.


2️⃣تضمین ایمنی: به شما اطمینان میده که متد، داده‌ی اصلی شما رو دستکاری نمی‌کنه.


// فرض کنید این یک استراکت خیلی بزرگه
public readonly struct BigStruct
{
// ... کلی فیلد و پراپرتی
}

void ProcessData(in BigStruct data)
{
// این کد کامپایل نمیشه چون پارامتر 'data' فقط-خواندنی است
// data = new BigStruct(); // Compile-time error!

// ولی می‌تونیم ازش بخونیم و استفاده کنیم
Console.WriteLine("Processing data...");
}

// --- نحوه استفاده ---
var myBigData = new BigStruct();

// با 'in' به متد پاس میدیم تا از کپی شدن جلوگیری کنیم
ProcessData(in myBigData);

// اگه ابهامی در اورلود متد نباشه، بدون 'in' هم میشه صداش زد
ProcessData(myBigData);


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

فهمیدیم in یکی از اون ابزارهای تخصصی‌تره که نشون میده شما به پرفورمنس و جزئیات اهمیت میدید. این کلمه کلیدی، قدرت ref (جلوگیری از کپی) و ایمنی pass-by-value (عدم تغییر مقدار اصلی) رو با هم ترکیب می‌کنه.

شما تا حالا از پارامتر in برای بهینه‌سازی کدهاتون استفاده کردید؟ یا اصلاً از وجودش خبر داشتید؟

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

🔖 هشتگ‌ها:
#CSharp
#Performance
#CodingTips
SQL تولید شده:
SELECT u.id, u.date, u.note, u.user_id
FROM user_notes AS u
WHERE u.date < @date OR (u.date = @date AND u.id <= @lastId)
ORDER BY u.date DESC, u.id DESC
LIMIT @limit;


💡توجه کنید که هیچ OFFSET در کوئری وجود ندارد. ما مستقیماً بر اساس cursor به دنبال ردیف‌ها می‌گردیم که کارآمدتر است.

محدودیت‌های Cursor Pagination:


• اگر کاربران نیاز به تغییر داینامیک فیلدهای مرتب‌سازی داشته باشند، پیاده‌سازی به شدت پیچیده می‌شود.

• کاربران نمی‌توانند به یک شماره صفحه خاص بپرند.

• پیاده‌سازی صحیح آن پیچیده‌تر است.

بررسی پلن‌های اجرای SQL 📊

من پلن‌های اجرا را برای هر دو مقایسه کردم. برای صفحه‌ای در عمق دیتابیس (آفست ۹۰۰,۰۰۰):

زمان اجرای Offset Pagination: 704.217 ms 🐢

زمان اجرای Cursor Pagination: 40.993 ms 🚀

یک بهبود عملکرد ۱۷ برابری با cursor pagination!

افزودن ایندکس برای Cursor Pagination 🔑

من همچنین تأثیر ایندکس‌ها را روی cursor pagination تست کردم. یک ایندکس ترکیبی روی فیلدهای Date و Id ایجاد کردم.
نتیجه اولیه کندتر بود! اما با استفاده از مقایسه تاپل (tuple comparison) در SQL:
WHERE (u.date, u.id) <= (@date, @lastId)

زمان اجرا به 0.668 ms کاهش یافت! ⚡️

برای ترجمه این به EF Core، می‌توانید از EF.Functions.LessThanOrEqual که یک ValueTuple را به عنوان آرگومان می‌پذیرد، استفاده کنید.

خلاصه 📝

با اینکه offset pagination ساده‌تر است، در مقیاس بالا دچار افت عملکرد شدید می‌شود. Cursor pagination عملکرد ثابتی را حفظ می‌کند و برای فیدهای real-time و infinite scroll عالی است.

🤔چه زمانی از کدام استفاده کنیم؟


🔹 برای APIهای حساس به عملکرد، فیدهای real-time یا infinite scroll از ⟵ Cursor pagination

🔹 برای اینترفیس‌های ادمین، مجموعه داده‌های کوچک، یا زمانی که به تعداد کل صفحات نیاز دارید ⟵ Offset pagination.

🔖 هشتگ‌ها:
#CSharp #DotNet #Pagination #Performance #SystemDesign
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
📖 سری آموزشی کتاب C# 12 in a Nutshell
🧱 راهنمای کامل 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ها: سازنده‌های گیج‌کننده و 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
خلاصه 📝

درک اپلیکیشن‌های مدرن، به خصوص توزیع‌شده، می‌تواند واقعاً گیج‌کننده باشد. OpenTelemetry مانند داشتن دید اشعه ایکس 👁 به سیستم شماست.

در حالی که افزودن OpenTelemetry نیاز به مقداری کار اولیه دارد، آن را یک سرمایه‌گذاری در نظر بگیرید. این سرمایه‌گذاری زمانی که مشکلات بروز می‌کنند، به شدت نتیجه می‌دهد. به جای حدس و گمان‌های آشفته، شما داده‌های دقیقی برای تمرکز سریع بر روی مشکلات دارید.

🔖 هشتگ‌ها:
#OpenTelemetry #Distributed_Tracing
#Performance #MemoryManagement
Entity Framework Extensions 💎

آیا می‌توانیم بهتر از SqlBulkCopy عمل کنیم؟
شاید، حداقل نتایج بنچمارک من نشان می‌دهد که می‌توانیم.

یک کتابخانهٔ فوق‌العاده دیگر به نام Entity Framework Extensions وجود دارد. این فقط یک کتابخانهٔ bulk insert نیست - بنابراین توصیه می‌کنم حتماً بررسی شود. با این حال، امروز آن را برای bulk insert استفاده خواهیم کرد.

برای استفادهٔ ما، متد BulkInsertOptimizedAsync گزینهٔ عالی است. می‌توانیم کالکشن آبجکت‌ها را ارسال کنیم و یک SQL bulk insert انجام خواهد شد. همچنین برخی بهینه‌سازی‌ها زیر هود انجام می‌دهد تا عملکرد بهتر شود.
using var context = new ApplicationDbContext();

await context.BulkInsertOptimizedAsync(GetUsers());

عملکرد آن فوق‌العاده است: ⚡️🔥
EF Core - Entity Framework Extensions، برای ۱۰۰ کاربر: ۱.۸۶ ms
EF Core - Entity Framework Extensions، برای ۱,۰۰۰ کاربر: ۶.۹ ms
EF Core - Entity Framework Extensions، برای ۱۰,۰۰۰ کاربر: ۶۶ ms
EF Core - Entity Framework Extensions، برای ۱۰۰,۰۰۰ کاربر: ۶۳۶ ms
EF Core - Entity Framework Extensions، برای ۱,۰۰۰,۰۰۰ کاربر: ۷,۱۰۶ ms


جمع‌بندی 🎯

کار SqlBulkCopy برای حداکثر سرعت و سادگی بهترین است. 👑
با این حال، Entity Framework Extensions عملکرد فوق‌العاده‌ای ارائه می‌دهند و هم‌زمان سهولت استفاده‌ای که EF Core دارد را حفظ می‌کنند. 💎⚡️

بهترین گزینه به نیازهای خاص پروژهٔ شما بستگی دارد:
فقط عملکرد مهم است؟ SqlBulkCopy راه حل شماست. 🔥

به سرعت عالی و توسعهٔ آسان نیاز دارید؟ EF Core انتخاب هوشمندانه است. ⚡️

به دنبال تعادل بین عملکرد و سهولت استفاده هستید؟ Entity Framework Extensions ⚖️

تصمیم با شماست که بهترین گزینه برای پروژهٔ خودتان کدام است.

امیدوارم مفید بوده باشد. 🙌

🔖هشتگ‌ها:
#EFCore #Dapper #SqlBulkCopy #BulkExtensions #EntityFrameworkExtensions #Performance #CSharp #Database #DotNet #Programming #BulkInsert