C# Geeks (.NET)
334 subscribers
128 photos
1 video
98 links
Download Telegram
🥊 دوئل حافظه در #C :
Value Types در برابر Reference Types


تا حالا شده یه متغیر رو جایی عوض کنی و ببینی یه جای دیگه هم ناخواسته تغییر کرده؟ یا برعکس، انتظار داشتی تغییر کنه ولی نکرده؟ ریشه ۹۰٪ این مشکلات، درک نکردن تفاوت بین Value Type و Reference Type هست.

این بحث فقط یه تئوری خشک و خالی نیست، بلکه کلید نوشتن کدهای بدون باگ و قابل پیش‌بینیه. بزن بریم این راز بزرگ رو کشف کنیم!

Value Types: کپی برابر اصل

فکر کنید Value Type مثل یه تیکه کاغذه. وقتی اون رو به متغیر دیگه‌ای میدین، یه کپی کامل ازش گرفته میشه. حالا هر بلایی سر اون کپی بیاد، کاغذ اصلی شما صحیح و سالمه!

شامل چه چیزایی میشه؟ تمام انواع عددی (int, double و...)، bool، char و مهم‌تر از همه، structها.
بیاین با مثال Point که به صورت struct تعریف شده ببینیم:

public struct Point { public int X, Y; }

Point p1 = new Point();
p1.X = 7;

// این خط، یک کپی کامل از p1 در p2 ایجاد می‌کنه
Point p2 = p1;

// حالا p1 و p2 دو موجودیت کاملاً جدا در حافظه هستن
Console.WriteLine($"p1.X: {p1.X}, p2.X: {p2.X}");
// p1.X: 7
// p2.X: 7

// اگه p1 رو تغییر بدیم، هیچ تأثیری روی p2 نداره
p1.X = 9;

Console.WriteLine($"p1.X: {p1.X}, p2.X: {p2.X}");
// p1.X: 9
// p2.X: 7

همونطور که می‌بینید، p2 زندگی خودشو داره و از تغییر p1 خبردار هم نمیشه!


Reference Types: یک آدرس، چند نفر

حالا فکر کنید Reference Type مثل کلید یه خونه‌ست. شما خودِ خونه رو به کسی نمیدین، فقط یه کلید ازش کپی می‌کنید و بهش میدین. هر کسی که کلید رو داشته باشه، می‌تونه بره داخل و دکوراسیون رو عوض کنه و این تغییر برای همه اونایی که کلید دارن، قابل مشاهده‌ست!

شامل چه چیزایی میشه؟ تمام classها، string، آرایه‌ها، delegateها و interfaceها.
حالا همون مثال Point رو این بار به صورت class تعریف می‌کنیم:

public class Point { public int X, Y; }

// ...

Point p1 = new Point();
p1.X = 7;

// این خط، فقط آدرس (رفرنس) p1 رو در p2 کپی می‌کنه
// حالا هر دو به یک آبجکت در حافظه اشاره دارن
Point p2 = p1;

Console.WriteLine($"p1.X: {p1.X}, p2.X: {p2.X}");
// p1.X: 7
// p2.X: 7

// اگه p1 رو تغییر بدیم، چون p2 هم به همون خونه نگاه می‌کنه، اونم تغییر رو می‌بینه
p1.X = 9;

Console.WriteLine($"p1.X: {p1.X}, p2.X: {p2.X}");
// p1.X: 9,
// p2.X: 9

اینجا دیگه p1 و p2 شریک جرم هستن! تغییر روی یکی، روی اون یکی هم تأثیر میذاره.

🔖 هشتگ‌ها : #MemoryManagement
🧠 کالبدشکافی حافظه در C# Stack در برابر Heap


وقتی یه متغیر تعریف می‌کنید، دقیقاً کجا میره؟ چرا بعضی متغیرها بلافاصله بعد از خروج از متد از بین میرن ولی بعضی دیگه باقی می‌مونن؟

جواب این سوال‌ها تو دو تا از مهم‌ترین مفاهیم کامپیوتر نهفته‌ست: Stack (پشته) و Heap (هیپ). درک این دو، شما رو از یه کدنویس ساده به یه توسعه‌دهنده واقعی تبدیل می‌کنه.

1️⃣پشته (Stack) :

حافظه‌ی سریع و منظم فکر کنید Stack مثل یه دسته بشقابه که روی هم چیدید. آخرین بشقابی که میذارید، اولین بشقابیه که برمیدارید (LIFO: Last-In, First-Out).

چی توش ذخیره میشه؟ این بخش از حافظه برای ذخیره‌ی متغیرهای محلی و پارامترهای متدها استفاده میشه. بیشتر Value Type ها اینجا زندگی می‌کنن.

چطور کار می‌کنه؟ با هر بار صدا زدن یک متد، حافظه مورد نیاز اون متد بالای پشته قرار می‌گیره و با تمام شدن متد، اون بخش از حافظه بلافاصله آزاد میشه. این کار فوق‌العاده سریعه.

static int Factorial (int x) 
{
if (x == 0) return 1;
return x * Factorial (x - 1);
}

هر بار که Factorial خودشو صدا می‌زنه، یه int x جدید روی پشته ساخته میشه و با برگشتن جواب، اون int از روی پشته برداشته میشه.

2️⃣هیپ (Heap) :

هیپ مثل یه انبار بزرگه. هر وقت یه آبجکت جدید می‌سازید (new)، CLR یه جای خالی تو این انبار پیدا می‌کنه، آبجکت رو اونجا میذاره و یه "آدرس" یا "رفرنس" به شما برمی‌گردونه.

چی توش ذخیره میشه؟ تمام آبجکت‌ها (نمونه‌های Reference Type) اینجا زندگی می‌کنن.

چطور کار می‌کنه؟ این حافظه توسط یه رفتگر هوشمند به اسم Garbage Collector (GC) مدیریت میشه. GC هر چند وقت یکبار میاد و آبجکت‌هایی که دیگه هیچ رفرنسی بهشون اشاره نمی‌کنه (آبجکت‌های یتیم) رو از حافظه پاک می‌کنه تا فضا آزاد بشه.

StringBuilder ref1 = new StringBuilder("obj1");
Console.WriteLine(ref1);
// بعد از این خط، دیگه هیچ رفرنسی به آبجکت "obj1" اشاره نمی‌کنه
// و آماده پاک شدن توسط GC میشه.

StringBuilder ref2 = new StringBuilder("obj2");
StringBuilder ref3 = ref2;
// اینجا با اینکه ref2 دیگه استفاده نمیشه، چون ref3 هنوز به "obj2"
// اشاره می‌کنه، این آبجکت زنده می‌مونه!
Console.WriteLine(ref3);


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

🤔 پس Value Typeها دقیقاً کجا زندگی می‌کنند؟
این سوال مهمیه! قانونش اینه: Value Typeها هرجا که تعریف بشن، همونجا زندگی می‌کنن.

اگه به عنوان یه متغیر محلی تو یه متد تعریف بشن ⟵ روی Stack.

اگه به عنوان یه فیلد داخل یه کلاس (که خودش یه Reference Type هست) تعریف بشن ⟵ همراه با اون آبجکت روی Heap!

حرف حساب و درک عمیق‌تر
درک تفاوت Stack و Heap فقط یه بحث تئوری نیست؛ روی پرفورمنس، مدیریت حافظه و حتی نحوه طراحی کلاس‌های شما تأثیر مستقیم داره.

این توضیح چقدر به درک بهتر شما از رفتار Value Type و Reference Type کمک کرد؟ آیا نکته‌ای در مورد Stack و Heap بود که براتون تازگی داشته باشه؟

نظراتتون رو کامنت کنید! 👇
[پاتوق گیک های #C]

🔖 هشتگ‌ها :
#CSharp
#MemoryManagement
#DotNet #Stack #Heap
📖 سری آموزشی کتاب C# 12 in a Nutshell
👻 مبحث پیشرفته #C: فاینالایزرها (~Finalizers) و آخرین کلمات یک آبجکت


ما معمولاً نگران از بین بردن آبجکت‌ها در #C نیستیم، چون Garbage Collector (GC) این کار رو به صورت خودکار برامون انجام میده. اما گاهی وقتا یه آبجکت، منابعی خارج از کنترل دات‌نت (unmanaged resources) مثل دستگیره‌های فایل یا اتصالات شبکه رو مدیریت می‌کنه.

اینجا پای فاینالایزر (Finalizer) به میدون باز میشه.

1️⃣ فاینالایزر چیست؟

فاینالایزر، یه متد خاص در کلاسه که درست قبل از اینکه Garbage Collector حافظه‌ی اون آبجکت رو پاک کنه، به صورت خودکار صدا زده میشه. این آخرین شانس آبجکته که منابع مدیریت‌نشده‌ی خودش رو آزاد کنه.

سینتکس فاینالایزر، اسم کلاسه که قبلش یه علامت مد (~) اومده:
class MyClass
{
~MyClass()
{
// کد پاک‌سازی منابع در اینجا قرار می‌گیرد
Console.WriteLine("Finalizing object!");
}
}

💡نکته فنی: این سینتکس در واقع یه میانبر شیک در #C برای override کردن متد Finalize از کلاس Object هست.

2️⃣ چرا تقریباً هیچوقت نباید فاینالایزر بنویسید؟ (مهم!) ⚠️


در ۹۹.۹٪ مواقع، شما به عنوان یک توسعه‌دهنده #C نباید مستقیماً فاینالایزر بنویسید. دلیلش اینه:

• ضربه به پرفورمنس: آبجکت‌هایی که فاینالایزر دارن، فرآیند پاک‌سازی حافظه توسط GC رو پیچیده و کند می‌کنن.

• غیرقطعی بودن: شما هیچ کنترلی روی اینکه فاینالایزر دقیقاً چه زمانی اجرا میشه، ندارید. ممکنه خیلی دیرتر از چیزی که انتظار دارید، اجرا بشه.

راه حل صحیح و مدرن برای مدیریت منابع، پیاده‌سازی اینترفیس IDisposable و استفاده از دستور using هست که قبلاً در موردش صحبت کردیم.

3️⃣ پس کی به درد می‌خوره؟ (تنها کاربرد منطقی) 💡

تنها کاربرد منطقی فاینالایزر، به عنوان یه مکانیسم پشتیبان یا Safety Net هست.

یعنی شما کلاس خودتون رو IDisposable می‌کنید و انتظار دارید که کاربر همیشه متد Dispose() رو (معمولاً با using) صدا بزنه. اما برای محکم‌کاری، یه فاینالایزر هم می‌نویسید که اگه کاربر یادش رفت Dispose() رو صدا بزنه، فاینالایزر به عنوان آخرین امید، اون منابع رو آزاد کنه تا از نشت منابع (resource leaks) جلوگیری بشه.

🤔 حرف حساب و تجربه شما
فاینالایزرها مثل دکمه Eject صندلی خلبان هستن؛ امیدوارید هیچوقت لازم نشه ازش استفاده کنید، ولی خوبه که بدونید وجود داره.

🔖 هشتگ‌ها:
#CSharp #DotNet #MemoryManagement #GarbageCollector
📖 سری آموزشی کتاب C# 12 in a Nutshell

👑 پدر همه تایپ‌ها: 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
📖 سری آموزشی کتاب 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