حافظه در داتنت چطور کار میکند؟
وقتی شما یک برنامه مینویسید، مثلاً در C#، برنامهتان نیاز به فضایی در حافظه (RAM) دارد تا دادهها رو
ذخیره
کند.
داتنت دو نوع حافظه اصلی دارد:
داتنت دو نوع حافظه اصلی دارد:
- Stack: برای دادههای کوچک و موقت (مثل متغیرهای محلی)
- Heap: برای دادههای بزرگ و طولانیمدت (مثل اشیا و آرایهها)
int number = 10; // روی Stack ذخیره میشود
Person person = new Person(); // شیء جدید در Heap ساخته میشود
Garbage Collector (GC) چیست؟
وقتی شما یک شیء جدید در Heap میسازید، حافظه اختصاص داده میشود. اما اگر این شیء دیگر مورد نیاز نباشد،
باید
حافظه آزاد شود. اینجا GC وارد عمل میشود!
GC مثل یک خدمتکار هوشمند است که:
GC مثل یک خدمتکار هوشمند است که:
- حافظهی استفادهنشده را خودکار آزاد میکند.
- از Memory Leak (نشت حافظه) جلوگیری میکند.
- برنامهنویس را درگیر مدیریت دستی حافظه نمیکند (برخلاف زبانهایی مثل C++).
GC چطور کار میکند؟
GC در سه مرحله اصلی کار میکند:
-
Mark (علامتگذاری)
- GC تمام اشیاء موجود در Heap را بررسی میکند.
- هر شیء که در حال استفاده است (مثلاً توسط متغیرهای فعال ارجاع داده شده) را Mark میکند.
- اشیاء بدون ارجاع (Unreferenced) به عنوان زباله در نظر گرفته میشوند.
-
Sweep (حذف زبالهها)
- اشیاء بدون استفاده (Unreferenced) از حافظه پاک میشوند.
-
Compact (فشردهسازی)
- حافظهی آزاد شده را منظم میکند تا فضای خالی به هم پیوسته باشد.
نسلهای حافظه (Generations)
برای بهینهسازی، GC از سه نسل (Generation) استفاده میکند:
✅ Gen 0 بیشترین پاکسازی را دارد (چون اشیاء موقت زیادی دارد).
- Gen 0: اشیاء جدید و موقت (مثل متغیرهای داخل یک متد).
- Gen 1: اشیایی که از Gen 0 جان سالم به در بردهاند.
- Gen 2: اشیاء قدیمی و طولانیمدت (مثل Singletonها).
✅ Gen 0 بیشترین پاکسازی را دارد (چون اشیاء موقت زیادی دارد).
چرا GC عالی است؟
- دیگر نیازی به delete یا free نیست (برخلاف C++).
- حافظه بهصورت خودکار مدیریت میشود.
- از Memory Leak جلوگیری میکند.
⚠️ GC همیشه فوراً حافظه را آزاد نمیکند (مگر وقتی واقعاً نیاز باشد یا حافظه کم شود).
مثال ساده از GC در عمل
void CreateObjects()
{
// شیء جدید در هیپ ساخته میشود
Person person1 = new Person();
// ارجاع جدید به همان شیء
Person person2 = person1;
// حالا person1 را null میکنیم، اما شیء هنوز در حافظه است (چون person2 ارجاع دارد)
person1 = null;
// حالا person2 هم null میشود، شیء دیگر ارجاعی ندارد
person2 = null;
// حالا GC میتواند این شیء را پاک کند (البته نه لزوماً بلافاصله!)
}
چه موقع GC اجرا میشود؟
- وقتی حافظه هیپ پر شود.
- وقتی برنامه در حالت Idle (بیکار) باشد.
- وقتی به صورت دستی فراخوانی شود (GC.Collect).
منابع مدیریتشده (Managed) vs منابع غیرمدیریتشده (Unmanaged)
-
Managed Resources: اشیایی که داتنت مدیریت میکند (مثل List<>، string، کلاسهای معمولی).
- اینها را GC خودکار پاک میکند و نیازی به کار خاصی ندارید.
-
Unmanaged Resources: منابعی که خارج از کنترل داتنت هستند (مثل فایلها، دیتابیس اتصالات،
Socketها).
- اینها باید دستی آزاد شوند، در غیر این صورت باعث نشت حافظه (Memory Leak) میشوند.
چرا Dispose مهم است؟
وقتی از منابع غیرمدیریتشده استفاده میکنید، باید حتماً آنها را بسته (Release) کنید، وگرنه:
❌ حافظه مصرف شده آزاد نمیشود (Memory Leak).
❌ ممکن است فایلها قفل شوند و برنامههای دیگر نتوانند از آنها استفاده کنند.
❌ اتصالات دیتابیس باز میمانند و پس از مدتی سرور دیتابیس از کار میافتد!
✅ راهحل: استفاده از IDisposable و الگوی Dispose.
❌ حافظه مصرف شده آزاد نمیشود (Memory Leak).
❌ ممکن است فایلها قفل شوند و برنامههای دیگر نتوانند از آنها استفاده کنند.
❌ اتصالات دیتابیس باز میمانند و پس از مدتی سرور دیتابیس از کار میافتد!
✅ راهحل: استفاده از IDisposable و الگوی Dispose.
IDisposable چیست؟
این یک اینترفیس است که فقط یک متد دارد:
مثال: کلاس FileStream (استفاده از Dispose)
public interface IDisposable
{
void Dispose();
}
هر کلاسی که این اینترفیس را پیادهسازی کند، باید منابع غیرمدیریتشده را در Dispose آزاد کند.
مثال: کلاس FileStream (استفاده از Dispose)
using (FileStream file = File.Open("test.txt", FileMode.Open))
{
// کار با فایل
} // اینجا به طور خودکار file.Dispose() فراخوانی میشود، حتی اگر خطایی رخ دهد!
یا به صورت دستی:
FileStream file = null;
try
{
file = File.Open("test.txt", FileMode.Open);
// کار با فایل
}
finally
{
file?.Dispose(); // مطمئن میشویم فایل حتما بسته میشود
}
using چطور کار میکند؟
وقتی شما کدی مثل این مینویسید:
using (var resource = new SomeDisposableResource())
{
// کار با resource
} // اینجا Dispose() به صورت خودکار فراخوانی میشود
کامپایلر C# آن را به این شکل تبدیل میکند:
var resource = new SomeDisposableResource();
try
{
// کار با resource
}
finally
{
resource.Dispose(); // حتی اگر خطا رخ دهد، Dispose فراخوانی میشود
}
نکات کلیدی درباره using:
- فقط برای اشیاء IDisposable کار میکند (مثل FileStream, SqlConnection, StreamReader).
- حتی اگر داخل بلوک using خطا (Exception) رخ دهد، Dispose() فراخوانی میشود.
- معادل try-finally است، اما کد را تمیزتر و خواناتر میکند.
using (var file = File.OpenRead("data.txt"))
using (var reader = new StreamReader(file))
{
string content = reader.ReadToEnd();
} // اول reader.Dispose() و سپس file.Dispose() فراخوانی میشود
CIL و CLI چیست؟
وقتی با زبانهای داتنت مثل C#، F# یا VB کد مینویسیم و Build میگیریم، کد مستقیم باینری نمیشود. اول
توسط کامپایلر تبدیل میشود به CIL.
CIL یک زبان میانی است که مستقل از سیستمعامل است و خروجی مشترک همه زبانهای داتنت است.
CLI یا Common Language Infrastructure یک استاندارد است که تعریف میکند:
CIL یک زبان میانی است که مستقل از سیستمعامل است و خروجی مشترک همه زبانهای داتنت است.
CLI یا Common Language Infrastructure یک استاندارد است که تعریف میکند:
- CIL چطور باشد
- Assembly چطور ساخته شود
- Metadata چطور ذخیره شود
- Typeها چه قوانینی داشته باشند
CLR و CTS چیست؟
برای اجرای استاندارد CLI، نیاز به یک محیط داریم که این قوانین را اجرا کند. اینجاست که CLR وارد میشود.
CLR (Common Language Runtime) پیادهسازی مایکروسافت از CLI است و در زمان Runtime فعال میشود.
وظایف CLR:
CLR (Common Language Runtime) پیادهسازی مایکروسافت از CLI است و در زمان Runtime فعال میشود.
وظایف CLR:
- Load کردن Assembly
- تبدیل CIL به کد Native با JIT
- مدیریت Garbage Collection و حافظه
- مدیریت Threadها
- Exception Handling
- نوع دادهها چطور تعریف شوند
- کلاسها و ارثبری چطور باشند
- Value و Reference Type چطور باشند
تفاوت NET و NET Framework چیست؟
- NET Framework قدیمیتره و فقط روی ویندوز اجرا میشه. پروژههاش معمولاً Windows Forms، WPF یا ASP.NET Web Forms هستند.
- NET (یا NET Core / NET 5 به بعد) نسخه جدید و Cross-Platformه. روی ویندوز، لینوکس و مک کار میکنه و برای وب، دسکتاپ، موبایل و حتی Cloud مناسبه.
تفاوت NET و C# چیست؟
- C# یک زبان برنامهنویسیه، یعنی چیزی که ما مینویسیم.
- NET پلتفرم اجرای برنامههاست، شامل CLR، کتابخانهها و ابزارهای استاندارد.
تفاوت Value Type و Reference Type
1- Value Type
- دادههاش مستقیم داخل متغیر ذخیره میشن، معمولاً روی Stack.
- وقتی یک Value Type رو کپی میکنی، یک نسخه جدا درست میشه، تغییر یکی روی دیگری اثری نمیذاره.
- همه Value Typeها sealed هستن، یعنی نمیتونی ازشون ارثبری کنی.
- Value Type معمولاً نمیتونه Null داشته باشه مگر Nullable باشه (int?).
- مثالها: int, double, bool, struct, enum
- آدرس (Reference) داخل متغیر ذخیره میشه و خود داده روی Heap قرار داره.
- وقتی یک Reference Type رو کپی میکنی، هر دو متغیر به همون شی Heap اشاره میکنن؛ تغییر یکی روی دیگری اثر میذاره.
- Reference Type میتونه Null باشه.
- مثالها: class, string, array, object
Heap و Stack چی هستن؟
Stack
- حافظهایه که سریع و مرتب کار میکنه.
- وقتی متغیری روی Stack ساخته میشه، مقدارش دقیقاً همونجا ذخیره میشه.
- مدیریتش خودکار و سادهه: وقتی از scope خارج میشه، خودکار پاک میشه.
- به همین خاطر Value Typeها معمولاً روی Stack میرن.
- نیازی به Garbage Collector نداره، چون ترتیب مشخصه و خودش آزاد میشه.
- حافظهایه که برای دادههای Reference Type استفاده میشه.
- دادهها روی Heap ذخیره میشن و متغیرها فقط آدرسشون رو نگه میدارن.
- مدیریتش پیچیدهتره چون اشیا ممکنه هنوز بهشون اشاره باشه یا نباشه.
- به همین خاطر CLR Garbage Collector اومده تا اشیای بلااستفاده رو پیدا کنه و پاک کنه.
- Heap انعطافپذیرتره و میتونه دادههای بزرگ یا با عمر طولانی رو نگه داره، ولی کندتره نسبت به Stack.
Boxing و Unboxing
Boxing: وقتی یک Value Type (مثل int یا struct) رو تبدیل میکنیم به Reference Type (object) یا میذاریم
تو یک ساختار که Reference میخواد، مثل Array of Object.
Unboxing: وقتی دوباره اون Reference Type رو برمیگردونیم به Value Type اصلی خودش.
Unboxing: وقتی دوباره اون Reference Type رو برمیگردونیم به Value Type اصلی خودش.
int x = 5; // Value Type
object o = x; // Boxing
int y = (int)o; // Unboxing
short a = 1; // Value Type
object w = a; // Boxing
int b = (int)w; // this throw an exception. boxed value type must unbox to it's type (short)
string y = "abc";
object z = y; // this is not unboxing. because string is not a value type
چه کاربرد هایی داره؟
- وقتی میخوای Value Typeها رو داخل Collectionهای قدیمی مثل ArrayList یا Hashtable ذخیره کنی که فقط Object قبول میکنه.
- وقتی نیاز داری یه Value Type رو به متدی که Reference Type میگیره بفرستی.
- در کل، وقتی Value Type بخواد نقش Reference Type رو بازی کنه، از Boxing استفاده میشه.
- Boxing: باعث ایجاد یک شیء روی Heap میشه، یعنی حافظه اضافه مصرف میکنه و کمی کندتره نسبت به استفاده مستقیم Value Type.
- Unboxing: نیاز به چک کردن نوع و کپی کردن داده داره، پس باز هم هزینه پردازشی داره.
- زیاد استفاده کردنش میتونه Performance برنامه رو پایین بیاره، مخصوصاً تو لوپهای سنگین.
انواع ارور
1- Compile-time Error (ارور زمان کامپایل)
- وقتی کد رو Build میگیری، کامپایلر میفهمه.
- معمولاً مربوط به Syntax اشتباه، نوع داده اشتباه، متد یا کلاس پیدا نشده هست.
int x = "hello"; // ارور: نوع داده اشتباه
2- Runtime Error (ارور زمان اجرا)
- برنامه کامپایل میشه ولی موقع اجرا کرش میکنه.
- معمولاً مربوط به دستکاری دادههای غیرمجاز، Null Reference، تقسیم بر صفر، Index خارج از محدوده هست.
int[] arr = new int[3];
Console.WriteLine(arr[5]); // ارور زمان اجرا
3- Logical Error (ارور منطقی)
- برنامه کامپایل و اجرا میشه، اما نتیجه اشتباهه.
- معمولاً مربوط به اشتباه در الگوریتم یا محاسبات هست.
int x = 5, y = 10;
int sum = x - y; // اشتباه، قصد داشتیم جمع کنیم
یونیت تست چه ارورهایی رو میتونه بگیره؟
- Compile-time Error: نه، یونیت تست کد رو نمیتونه کامپایل کنه.
- Runtime Error: بله، تستها میتونن این ارورها رو با Assert.Throws و بررسی Exception شناسایی کنن.
- Logical Error: بله، دقیقاً برای این طراحی شدن. با Assert.AreEqual(expected, actual) میتونی ببینی خروجی با چیزی که انتظار داری همخوانی داره یا نه.
تو پروژههات چطور Exception Handling انجام میدی؟
در پروژههای Production، همه جا try-catch نمیذاریم. لایههای پایین مثل Service یا Repository
Exceptionها رو Throw میکنن و یک Global Exception Handler در لایه بالاتر (مثل Middleware در ASP.NET
Core) همه اونها رو میگیره.
این Handler کارش:
این Handler کارش:
- لاگ کردن کامل Exception و Metadata مربوطه.
- آماده کردن Response استاندارد برای کلاینت، معمولاً شامل StatusCode، Message و TraceId.
- جلوگیری از نمایش جزئیات حساس به کاربر.
public class CustomExceptionHandler(ILogger<CustomExceptionHandler> logger) : IExceptionHandler
{
public async ValueTask<bool> TryHandleAsync(HttpContext httpContext, Exception exception, CancellationToken cancellationToken)
{
// log error
logger.LogError(exception, exception.Message, cancellationToken);
// initial problemDetails
var problemDetails = new ProblemDetails
{
Title = exception.Message,
Status = StatusCodes.Status500InternalServerError,
Detail = exception.StackTrace,
Instance = httpContext.Request.Path
};
// add Extensions
problemDetails.Extensions.Add("TraceId", httpContext.TraceIdentifier);
if (exception is ValidationException validationException)
problemDetails.Extensions.Add("ValidationErrors", validationException.Errors);
// prepare response
httpContext.Response.StatusCode = StatusCodes.Status500InternalServerError;
httpContext.Response.ContentType = MediaTypeNames.Application.ProblemJson;
await httpContext.Response.WriteAsJsonAsync(problemDetails, cancellationToken);
return true;
}
}
// in Program.cs file:
builder.Services.AddExceptionHandler<CustomExceptionHandler>();
به این ترتیب، کد تمیز میمونه، Performance حفظ میشه و تیم پشتیبانی میتونه با Logging و Monitoring
خطاها رو ردیابی کنه.
بعضیها کل app.Run رو داخل try-catch گذاشتن. این اوکیه؟ فرقش با Global Exception Handler چیه؟
بله، این روش کار میکنه، ولی مزایا و معایب خودش رو داره. وقتی کل app.Run داخل try-catch باشه، همه
Exceptionها توی Pipeline به این catch میرسن و میتونی لاگشون کنی یا Process رو مدیریت کنی.
مزایا
مزایا
- ساده و مستقیم
- همه Exceptionها رو میگیره
- Response استاندارد به کلاینت نمیده
- نمیتونه Exceptionهای خاص مثل ValidationException یا NotFoundException رو جداگانه Handle کنه
- برای پروژههای بزرگ و Microserviceها Maintain و توسعه سختتره
- Exceptionها رو درست در Pipeline مدیریت میکنه
- میتونه Response استاندارد JSON یا HTML بده و StatusCode مناسب انتخاب کنه
- Logging حرفهای و TraceId داره
- Exceptionهای خاص قابل مدیریت و Extendable هست
در مورد try-catch-finally توضیح بده
ساختار پایه به این شکله
نکته: تو async methodها Exception داخل Task ذخیره میشه و وقتی await میکنی throw میشه.
try
{
// کد ممکنه Exception بده
}
catch (ExceptionType1 ex)
{
// مدیریت ExceptionType1
}
catch (ExceptionType2 ex)
{
// مدیریت ExceptionType2
}
finally
{
// کدی که همیشه اجرا میشه، مهم نیست Exception بیاد یا نه
}
در این مثال:
- کد داخل try اجرا میشه.
-
اگر Exception رخ بده:
- CLR دنبال اولین catch مناسب میگرده (match با نوع Exception).
- اگر match پیدا شد، آن catch اجرا میشه و اجرای try متوقف میشه.
- اگر catch مناسب پیدا نشه، Exception به لایه بالاتر پرتاب میشه و در نهایت اگر هیچ جا Handle نشده باشه برنامه Crash میکنه.
- بلاک finally همیشه اجرا میشه، حتی اگر Exception رخ بده یا catch اجرا نشه.
نکته: تو async methodها Exception داخل Task ذخیره میشه و وقتی await میکنی throw میشه.
Access Modifierها در C# چی هستن؟
1- public
- دسترسی از هر جای پروژه و هر Assembly.
- دسترسی فقط از همان کلاس.
- دسترسی از همان کلاس و Subclassها.
- Struct نمیتونه protected داشته باشه، چون Struct ارثبری نداره.
- دسترسی فقط از همان Assembly.
- دسترسی ترکیبی، در همان Assembly همیشه در دسترسه و در Assembly های دیگه فقط برای SubClassها.
- دسترسی فقط برای SubClassها در همان Assembly
- Struct نمیتونه داشته باشه، چون Struct ارثبری نداره.
- Members (Class level) => private (برای محافظت از دادهها و اصول Encapsulation)
- Members (Struct level) => internal (چون Struct معمولاً داده ساده، Value Type، بدون ارثبری و برای استفاده داخلی Assembly هست)
- Class/Struct/Enum/... (Namespace level) => internal (اکثرا برای استفاده داخلی Assembly خودشون طراحی میشن، نه برای مصرف مستقیم توسط Assemblyهای دیگه)
توی سطح Namespace (یعنی کلاسها یا Typeهایی که مستقیم داخل Namespace تعریف میشن) چه Access Modifierهایی
میتونیم استفاده کنیم؟
فقط public و internal مجاز هستن
Modifierهای دیگه مثل private, protected, protected internal, private protected غیرمجاز هستن:
Modifierهای دیگه مثل private, protected, protected internal, private protected غیرمجاز هستن:
- این Modifierها مربوط به Memberهای داخل کلاس یا ارثبری هستن، نه کلاسهای مستقل داخل Namespace.
- توی Namespace هیچ کلاس یا Type بالاتر از خودش نیست که بخواد دسترسی محدود کنه، پس protected یا private معنایی نداره.
sealed در C# یعنی چی؟
وقتی یک کلاس رو sealed میکنی، دیگه هیچ کلاسی نمیتونه ازش ارثبری کنه. کی استفاده می کنیم:
نکته: وقتی کلاس یا متد sealed باشه CLR مطمئنه که هیچ Overrideای وجود نداره در نتیجه Call سریعتر و کمی بهبود Performance (نه معجزه) رو خواهیم داشت.
نکته: کلاس های Abstract ساخته شده تا ازشون ارث بری بشه. پس منطقیه که نمیتونن sealed باشن.
- وقتی مطمئنی این کلاس نباید Extend بشه
- برای حفظ امنیت، جلوگیری از Override ناخواسته
- sealed فقط روی متدی میاد که در کلاس والد virtual بوده و در کلاس مشتق override شده باشه.
- این متد دیگه قابل override شدن در کلاسهای بعدی نیست.
class Base
{
public virtual void DoWork() { }
}
class Child : Base
{
public sealed override void DoWork() { }
}
در این حالت Child میتونه ارثبری بشه ولی DoWork دیگه قابل override نیست.
نکته: وقتی کلاس یا متد sealed باشه CLR مطمئنه که هیچ Overrideای وجود نداره در نتیجه Call سریعتر و کمی بهبود Performance (نه معجزه) رو خواهیم داشت.
نکته: کلاس های Abstract ساخته شده تا ازشون ارث بری بشه. پس منطقیه که نمیتونن sealed باشن.
تفاوت lock و Semaphore در C# چیه و ویژگیهای هر کدوم چیه؟
1- Lock
- یک Thread همزمان: فقط یک Thread میتونه داخل بلوک lock باشه.
- Release خودکار: وقتی بلوک تموم میشه، lock آزاد میشه.
- Async Support نداره: نمیتونی مستقیماً با async/await استفاده کنی.
private readonly object _locker = new object();
void Increment()
{
lock(_locker)
{
counter++; // فقط یک Thread همزمان وارد میشه
}
}
2- Semaphore / SemaphoreSlim
- چند Thread همزمان: میتونی تعداد Threadهای مجاز رو مشخص کنی.
- async/await ساپورت میشه: فقط در SemaphoreSlim
- Release دستی: وقتی Thread کارش تموم شد باید Release بزنه.
- Queue شدن Threadها: وقتی Resource پر باشه، Threadها منتظر میمونن.
private static SemaphoreSlim _semaphore = new SemaphoreSlim(3); // max 3 Thread
async Task AccessResourceAsync()
{
await _semaphore.WaitAsync(); // منتظر میمونه تا نوبتش بشه
try
{
// دسترسی به Resource
}
finally
{
_semaphore.Release(); // آزاد کردن Resource
}
}
تفاوت Semaphore و SemaphoreSlim چیه؟
Semaphore و SemaphoreSlim هر دو برای کنترل تعداد دسترسی همزمان به یک منبع استفاده میشن، ولی تفاوت
اصلیشون در محدودهی اثرگذاری (Scope) هست.
- SemaphoreSlim: فقط داخل یک Process کار میکنه. یعنی اگر برنامهی وب ما روی سرور چند Instance داشته باشه (مثلاً پشت Load Balancer)، هر Instance یک SemaphoreSlim جدا داره و از بقیه خبر نداره.
- Semaphore: میتونه بین چند Process مشترک باشه. یعنی همهی Instanceها از یک Semaphore مشترک استفاده میکنن و واقعاً محدودیت سراسری اعمال میشه.
اون object داخل lock دقیقاً چیه؟
در lock، قفل روی خود کد یا متغیر اعمال نمیشه، بلکه روی یک object مشخص در حافظه اعمال میشه.
این object هیچ دادهی مهمی نداره و فقط نقش علامت قفل یا نقطهی هماهنگی بین Threadها رو بازی میکنه.
این object هیچ دادهی مهمی نداره و فقط نقش علامت قفل یا نقطهی هماهنگی بین Threadها رو بازی میکنه.
private readonly object _lock = new object();
- این object باید خصوصی باشه
- نباید از بیرون کلاس قابل دسترسی باشه
- نباید this یا string باشه
params در C# چیه و چه کاربردی داره؟
params اجازه میده به یک متد، تعداد متغیر (نامحدود) آرگومان از یک نوع مشخص پاس بدیم، بدون اینکه مجبور
باشیم دستی آرایه بسازیم.
int Sum(params int[] numbers)
{
return numbers.Sum();
}
Sum(1, 2, 3);
Sum(5, 10, 20, 30);
Sum(); // مجازه
نکات استفاده از params:
- فقط یک params میتونه توی امضای متد باشه
- params باید آخرین پارامتر متد باشه
- نوعش حتماً باید آرایه باشه
تفاوت class و struct در C# چیه؟
1. نوع داده
- Class: Reference Type
- Struct: Value Type
- Class: میتونه از کلاس دیگه ارث ببره.
- Struct: نمیتونه از struct یا class ارث ببره، همیشه sealedه. ولی میتونه interface پیادهسازی کنه
-
Class
- میتونه بدون پارامتر یا با پارامتر باشه
- میتونه چند constructor overload داشته باشه
-
Struct
- همیشه یک constructor پیشفرض (default) داره که تمام فیلدها رو صفر/null مقداردهی میکنه
- نمیتونه constructor بدون پارامتر تعریف کنه (چون پیشفرض همیشه هست)
- میتونه constructor با پارامتر داشته باشه، ولی باید حتما همه فیلد ها مقداردهی بشن
- Class: میتونه null باشه
- Struct: نمیتونه null باشه مگر Nullable باشه (int?)
- وقتی داده سبک و کوچک داری، مثل نقاط، مختصات، رنگ، یا مقدار عددی با چند فیلد
- وقتی نمیخوای هر بار reference اضافه بسازی چون stack allocation سریعتره
- وقتی inheritance نیاز نداری
- وقتی میخوای immutable باشه (معمولاً structها immutable طراحی میشن)
Constructor و Deconstructor (و Destructor) در C# چی هستن؟
1- Constructor چیه؟
Constructor متدیه که موقع ساخته شدن یک object اجرا میشه و کارش آمادهسازی اولیهی شیءه.
Deconstructor یعنی: شیء رو باز کن و فیلدهاش رو بده به چند تا متغیر.
Constructor متدیه که موقع ساخته شدن یک object اجرا میشه و کارش آمادهسازی اولیهی شیءه.
- اسمش دقیقاً هماسم کلاس
- return type نداره
class User
{
public User()
{
}
}
2- انواع Constructor
-
Normal / Parameterized Constructor: برای مقداردهی اولیه با ورودی
public User(string name) { Name = name; } -
Private Constructor: وقتی نمیخوای از بیرون کلاس object ساخته بشه
در مواردی مثل: Singleton, Factory, Utility class کاربرد داره.class Config { private Config() { } } -
Static Constructor
- مخصوص خود کلاسه نه object
- فقط یک بار اجرا میشه
- قبل از اولین استفاده از کلاس (یا اولین دسترسی به static member)
- پارامتر نمیگیره
- access modifier نداره (نه public نه private)
- خودت صداش نمیزنی
- برای مقداردهی static fieldها استفاده میشه
یعنی: قبل از اینکه کسی از Cache استفاده کنه، دیتاش آماده میشه.class Cache { public static Dictionary<int,string> Data; static Cache() { Data = LoadFromDb(); } }
Deconstructor یعنی: شیء رو باز کن و فیلدهاش رو بده به چند تا متغیر.
class Person
{
public string Name;
public int Age;
public void Deconstruct(out string name, out int age)
{
name = Name;
age = Age;
}
}
var p = new Person { Name="Ali", Age=30 };
var (n, a) = p;
- اسم متد باید Deconstruct
- خروجی با out
- فقط برای راحتی و خوانایی
- ربطی به حافظه و GC نداره
Destructor (~ClassName) در C# چیه؟
1- در C# متدی که به شکل زیر نوشته میشه:
2- حالا Finalizer دقیقاً چیه؟
3- Destructor و Finalizer فرق دارن؟
در عمل نه. در C# چیزی که ما مینویسیم ~ClassName، در سطح CLR اسمش Finalizer هست.
4- Destructor / Finalizer چه ویژگیهایی داره؟
نه، اصلاً.
الگوی استاندارد .NET:
تقریباً ❌ نه. در پروژههای معمول IDisposable + using کافیه.
Finalizer حتی اگر اجرا نشود، باعث افزایش هزینه GC میشود. بنابراین الگوی Dispose + Finalizer فقط زمانی استفاده میشود که کلاس مستقیماً unmanaged resource را نگه دارد. در غیر این صورت، Finalizer غیرضروری و مضر است.
~MyClass()
{
}
در واقع Destructor واقعی نیست؛ این در اصل Finalizer هست که فقط اسمش تو C# به شکل Destructor دیده میشه.
2- حالا Finalizer دقیقاً چیه؟
- توسط Garbage Collector صدا زده میشه
- وقتی GC تصمیم میگیره object رو از حافظه پاک کنه
- برای پاکسازی منابع unmanaged
3- Destructor و Finalizer فرق دارن؟
در عمل نه. در C# چیزی که ما مینویسیم ~ClassName، در سطح CLR اسمش Finalizer هست.
4- Destructor / Finalizer چه ویژگیهایی داره؟
- فقط برای class (struct نداره)
- پارامتر نمیگیره
- overload نمیشه
- زمان اجراش نامشخصه
- روی performance تاثیر منفی داره
- اجرای اون GC رو کندتر میکنه
نه، اصلاً.
- Destructor رو GC صدا میزنه ولی Dispose رو خود برنامه نویس
- زمان اجرای Destructor نامشخص هستش ولی Dispose دقیق و کنترل شده
- برای آزاد کردن منابع استفاده میشه
- توسط خود برنامهنویس صدا زده میشه
-
معمولاً با using
using (var file = new FileStream("a.txt", FileMode.Open)) { }
الگوی استاندارد .NET:
class MyResource : IDisposable
{
public void Dispose()
{
// آزادسازی منابع
GC.SuppressFinalize(this);
}
~MyResource()
{
Dispose();
}
}
- اولویت با Dispose
- اگر کسی Dispose رو صدا نزد Finalizer آخرین راهه
- GC.SuppressFinalize میگه: «دیگه Finalizer لازم نیست»
تقریباً ❌ نه. در پروژههای معمول IDisposable + using کافیه.
Finalizer حتی اگر اجرا نشود، باعث افزایش هزینه GC میشود. بنابراین الگوی Dispose + Finalizer فقط زمانی استفاده میشود که کلاس مستقیماً unmanaged resource را نگه دارد. در غیر این صورت، Finalizer غیرضروری و مضر است.