مقدمه

در درس قبل، با تفاوت بنیادین بین نوع‌های مقداری و ارجاعی آشنا شدیم. یک نکته‌ی کلیدی این بود که نوع‌های مقداری نمی‌توانند مقدار null (پوچ یا بی‌مقدار) را بپذیرند و باید همیشه یک مقدار مشخص داشته باشند. اما در دنیای واقعی برنامه‌نویسی، سناریوهای زیادی وجود دارد که در آن‌ها یک مقدار ممکن است وجود نداشته باشد. برای مثال، سن یک کاربر در پایگاه داده ممکن است وارد نشده باشد، یا یک فیلد اختیاری در فرم، خالی گذاشته شود. برای حل این مشکل، C# مفهومی به نام نوع‌های مقداری پوچ‌پذیر (Nullable Value Types) را معرفی کرده است که به ما اجازه می‌دهد مقدار null را به متغیرهای از نوع مقداری اختصاص دهیم.

مشکل: نوع‌های مقداری نمی‌توانند null باشند

بیایید ابتدا مشکل را با یک مثال ساده مرور کنیم. همانطور که می‌دانیم، نوع‌های ارجاعی مانند string می‌توانند به سادگی مقدار null بگیرند، اما تلاش برای انجام همین کار با یک نوع مقداری مانند int منجر به خطای کامپایل (compile-time error) می‌شود.

Copy Icon Program.cs
// This is perfectly fine for a reference type.
string name = null;

// This causes a compile-time error!
// Error: Cannot convert null to 'int' because it is a non-nullable value type.
int age = null;

این محدودیت، طراحی زبان C# را امن‌تر می‌کند، زیرا جلوی بسیاری از خطاهای ناشی از مقادیر null را که در زبان‌های دیگر رایج است، می‌گیرد. اما برای موقعیت‌هایی که "نبود مقدار" یک حالت معتبر است، به یک راه‌حل نیاز داریم. اینجاست که نوع‌های پوچ‌پذیر وارد عمل می‌شوند.

نوع‌های مقداری پوچ‌پذیر (Nullable Value Types)

یک نوع پوچ‌پذیر، در واقع یک ساختار (struct) ویژه است که یک نوع مقداری را در بر می‌گیرد و به آن اجازه می‌دهد علاوه بر مقادیر معتبر خود، مقدار null را نیز بپذیرد.

نحوه‌ی تعریف یک نوع پوچ‌پذیر

برای تعریف یک نوع مقداری به عنوان پوچ‌پذیر، کافی است یک علامت سؤال (?) بعد از نام نوع قرار دهیم. این روش، یک سینتکس کوتاه‌شده برای نوع ژنریک System.Nullable<T> است.

Copy Icon Program.cs
// Declaring a nullable integer using the shorthand syntax (preferred)
int? age = null;

// This is also valid now
age = 30; 

// Declaring using the full generic type syntax
Nullable<bool> hasAgreed = null;

Console.WriteLine($"The age is: {age}");
Console.WriteLine($"Has agreed: {hasAgreed}");

در کد بالا، متغیر age از نوع int? (بخوانید "int پوچ‌پذیر") تعریف شده است. این متغیر اکنون می‌تواند یک عدد صحیح یا مقدار null را در خود نگه دارد. در عمل، همیشه از سینتکس کوتاه‌تر `?` استفاده می‌شود زیرا خواناتر و رایج‌تر است.

کار با نوع‌های پوچ‌پذیر

وقتی با یک متغیر پوچ‌پذیر کار می‌کنیم، قبل از استفاده از مقدار آن باید بررسی کنیم که آیا واقعاً مقداری دارد یا null است. در غیر این صورت، ممکن است با خطای زمان اجرا مواجه شویم. نوع‌های پوچ‌پذیر دو پراپرتی بسیار مفید برای این کار ارائه می‌دهند: HasValue و Value.

بررسی کردن مقدار و دسترسی به آن

پراپرتی HasValue یک مقدار بولین برمی‌گرداند. اگر متغیر پوچ‌پذیر مقداری داشته باشد، این پراپرتی true و در غیر این صورت false خواهد بود. اگر و فقط اگر HasValue برابر با true باشد، می‌توانیم با استفاده از پراپرتی Value به مقدار اصلی آن دسترسی پیدا کنیم.

Copy Icon Program.cs
int? temperature = 22;

if (temperature.HasValue)
{
    Console.WriteLine($"Current temperature is: {temperature.Value}°C");
}
else
{
    Console.WriteLine("Temperature data is not available.");
}

// Another way to check is directly against null
if (temperature != null)
{
     Console.WriteLine("It's definitely not null.");
}

همانطور که در مثال می‌بینید، الگوی رایج این است که ابتدا با HasValue (یا مقایسه مستقیم با null) از وجود مقدار اطمینان حاصل کرده و سپس با Value از آن استفاده کنیم. تلاش برای دسترسی به Value زمانی که متغیر null است، منجر به خطای InvalidOperationException می‌شود.

عملگر ادغام پوچ (Null-Coalescing Operator: ??)

بررسی با if کاملاً کارآمد است، اما C# یک راهکار بسیار کوتاه‌تر و خواناتر برای کار با مقادیر پوچ‌پذیر ارائه می‌دهد: عملگر ادغام پوچ `??`. این عملگر دو عملوند می‌گیرد. اگر عملوند اول null نباشد، مقدار آن را برمی‌گرداند. در غیر این صورت (اگر null باشد)، مقدار عملوند دوم را برمی‌گرداند. این عملگر برای تعیین یک مقدار پیش‌فرض عالی است.

Copy Icon Program.cs
int? downloadedFiles = null;

// Using ?? to provide a default value (0)
int filesToProcess = downloadedFiles ?? 0;

Console.WriteLine($"Files to process: {filesToProcess}"); // Output: 0

downloadedFiles = 15;
filesToProcess = downloadedFiles ?? 0;
Console.WriteLine($"Files to process: {filesToProcess}"); // Output: 15

در این کد، downloadedFiles ?? 0 می‌گوید: "اگر downloadedFiles مقدار دارد، از همان استفاده کن؛ وگرنه از 0 استفاده کن". این کد معادل یک بلوک if-else کامل است اما بسیار مختصرتر و خواناتر نوشته شده است.

نوع‌های ارجاعی پوچ‌پذیر (Nullable Reference Types)

از نسخه 8 C# به بعد، مفهوم پوچ‌پذیری به نوع‌های ارجاعی نیز گسترش یافت. در پروژه‌های مدرن .NET، قابلیتی به نام Nullable Reference Types به صورت پیش‌فرض فعال است. این قابلیت، رفتار پیش‌فرض نوع‌های ارجاعی را تغییر می‌دهد. قبل از این قابلیت، هر متغیر از نوع ارجاعی (مثل string) می‌توانست null باشد. اما با فعال بودن این ویژگی، کامپایلر فرض می‌کند که یک string معمولی نباید null باشد و در صورت احتمال وجود null، به شما هشدار می‌دهد.

برای اینکه به کامپایلر بگویید یک نوع ارجاعی می‌تواند null باشد، باید از همان سینتکس علامت سؤال استفاده کنید.

// With Nullable Reference Types enabled:
string message = "Hello"; // Compiler assumes this is never null
string? optionalMessage = null; // This is explicitly allowed to be null

// The compiler will warn you here because optionalMessage might be null
Console.WriteLine(optionalMessage.Length);

این ویژگی یک ابزار قدرتمند برای جلوگیری از خطای رایج NullReferenceException در زمان کامپایل است و شما را تشویق می‌کند که کدهای امن‌تری بنویسید.