📖 سری آموزشی کتاب 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