Csharp Nullable
# C# Nullable Types
## C# Nullable Types
In C#, types like `int`, `float`, `bool`, and `DateTime` are **value types**. By default, they must always have a value and cannot be `null`:
```csharp
int a = null; // Compile error: Cannot convert null to 'int'
**Nullable Types** solve this problem by allowing value types to have an additional "no value" state (i.e., `null`). This is very useful in scenarios like handling database fields or API return values where data might be missing.
The declaration is simple: add a `?` after the type:
```csharp
int? a = null; // Valid
int? b = 42; // Valid
Here, `int?` is syntactic sugar for `Nullable`. They are completely equivalent:
```csharp
Nullable a = null; // Full form
int? a = null; // Shorthand, same effect
The diagram below illustrates how nullable types work in memoryβthey use an extra boolean flag to record "whether a value exists":
* * *
## Difference Between Single Question Mark `?` and Double Question Mark `??`
C# has two operators related to nullable types that are often used together but have completely different meanings:
| Operator | Name | Purpose | Example |
| :--- | :--- | :--- | :--- |
| `?` | Nullable Type Modifier | Makes a value type nullable (can be `null`) | `int? i = 3;` is equivalent to `Nullable i = new Nullable(3);` |
| `??` | Null-Coalescing Operator | Provides a default value when a variable is `null` | `int result = i ?? 0;` |
A simple comparison:
```csharp
int i; // Regular value type, default is 0, can never be null
int? ii; // Nullable type, default is null
* * *
## Declaration and Assignment of Nullable Types
### Declaration Syntax
```csharp
? = null;
For example:
```csharp
int? age = null;
double? temperature = 36.6;
bool? isActive = new bool?(); // Explicitly constructed, default is null
DateTime? birthday = null;
`Nullable` can represent the normal range of its underlying value type, plus an additional `null` value. For example, `Nullable` can be any integer from `-2,147,483,648` to `2,147,483,647`, or `null`.
### Complete Example
## Example
```csharp
using System;
namespace NullableDemo
{
class Program
{
static void Main(string[] args)
{
// Declare nullable variables of different types
int? num1 = null;
int? num2 = 45;
double? num3 = new double?(); // null
double? num4 = 3.14157;
bool? boolVal = new bool?(); // null
// Display values (null will display as an empty string)
Console.WriteLine($"num1 = {num1 ?? 0}"); // 0
Console.WriteLine($"num2 = {num2 ?? 0}"); // 45
Console.WriteLine($"num3 = {num3 ?? 0.0}"); // 0
Console.WriteLine($"num4 = {num4 ?? 0.0}"); // 3.14157
Console.WriteLine($"boolVal = {boolVal}"); // (empty)
}
}
}
Output:
num1 = 0
num2 = 45
num3 = 0
num4 = 3.14157
boolVal =
* * *
## Null-Coalescing Operator (`??`)
The `??` operator is used to provide a **fallback default value** for nullable types or reference types. It returns the left-hand value if it is not `null`; otherwise, it returns the right-hand value:
```csharp
??
* If `` is not `null`, it returns ``;
* Otherwise, it returns ``.
Starting from C# 8.0, you can also use `??=` (null-coalescing assignment operator), which assigns a value only if the variable is `null`:
```csharp
int? x = null;
x ??= 10; // x is null, assign 10
x ??= 20; // x already has value 10, no assignment
## Example
```csharp
using System;
namespace NullableDemo
{
class Program
{
static void Main(string[] args)
{
double? num1 = null;
double? num2 = 3.14157;
// ?? provides a default value
double result1 = num1 ?? 5.34; // num1 is null β returns 5.34
double result2 = num2 ?? 5.34; // num2 has value β returns 3.14157
Console.WriteLine($"num1 ?? 5.34 = {result1}"); // 5.34
Console.WriteLine($"num2 ?? 5.34 = {result2}"); // 3.14157
// Chained usage: provide multiple fallback values
int? a = null;
int? b = null;
int? c = 42;
int value = a ?? b ?? c ?? 0; // Tries sequentially, ultimately gets 42
Console.WriteLine($"a ?? b ?? c ?? 0 = {value}"); // 42
}
}
}
Output:
num1 ?? 5.34 = 5.34
num2 ?? 5.34 = 3.14157
a ?? b ?? c ?? 0 = 42
* * *
## Common Properties and Methods of Nullable Types
| Member | Description | Example |
| :--- | :--- | :--- |
| `.HasValue` | Determines if the variable has a value (returns `bool`) | `if (num.HasValue) { ... }` |
| `.Value` | Gets the actual value (throws `InvalidOperationException` if `null`) | `int x = num.Value;` |
| `.GetValueOrDefault()` | Safely gets the value; returns the type's default value (e.g., `0`) if `null` | `num.GetValueOrDefault()` |
| `.GetValueOrDefault(T)` | Safely gets the value; returns the specified default value if `null` | `num.GetValueOrDefault(100)` |
| `??` | Null-coalescing operator (syntactic sugar, equivalent to `GetValueOrDefault`) | `int result = num ?? 100;` |
## Example
```csharp
using System;
namespace NullableDemo
{
class Program
{
static void Main(string[] args)
{
int? num = null;
// HasValue + Value (Traditional approach)
if (num.HasValue)
Console.WriteLine($"Value is: {num.Value}");
else
Console.WriteLine("num has no value");
// Safely get default value
Console.WriteLine(num.GetValueOrDefault()); // 0
Console.WriteLine(num.GetValueOrDefault(99)); // 99
Console.WriteLine(num ?? 99); // 99 (Equivalent syntax)
// β οΈ Note: Accessing .Value directly will throw an exception
// int x = num.Value; // InvalidOperationException!
}
}
}
* * *
## Practical Application Scenarios
Nullable types are very common in real-world development, especially when dealing with potentially missing data:
### Database Field Mapping
Database fields can be `NULL`. Nullable types can map to them precisely:
| UserID | Age | IsActive |
| :--- | :--- | :--- |
| 1 | 28 | true |
| 2 | null | false |
```csharp
int? age = GetUserAgeFromDB(userId: 2); // Safely handles null
string display = age.HasValue ? $"User Age: {age.Value}" : "Age unknown";
// Or a more concise way:
string display2 = $"User Age: {age ?? 0}";
### Optional Parameters and Configuration
```csharp
// Configuration item might not be set
int? timeout = GetConfigValue("timeout");
int actualTimeout = timeout ?? 30; // Use default 30 seconds if not set
### Chained Null Checking (C# 6.0+)
```csharp
// Use ?. operator to safely access objects that might be null
string? name = user?.Address?.City?.Name;
// If user, Address, or City is null, the result is null, no exception thrown
* * *
## Summary
| Function | Example | Description |
| :--- | :--- | :--- |
| Define a nullable type | `int? x = null;` | Equivalent to `Nullable` |
| Check if it has a value | `x.HasValue` | Returns `true` or `false` |
| Get value (unsafe) | `x.Value` | Throws `InvalidOperationException` if `null` |
| Get default value | `x ?? 0` | Returns the right-hand value if `null` |
| Get specified default value | `x.GetValueOrDefault(10)` | Returns 10 if `null` |
| Null-coalescing assignment | `x ??= 10` | Assigns only if `x` is `null` (C# 8.0+) |
| Safe navigation access | `user?.Name` | Returns `null` if `user` is `null` (C# 6.0+) |
* * *
## C# 8.0 "Nullable Reference Types"
Starting with C# 8.0, **Nullable Reference Types** were introduced. This is a different mechanism from the **Nullable Value Types** discussed in this article:
| Comparison Item | Nullable Value Types | Nullable Reference Types |
| :--- | :--- | :--- |
| Example | `int?` | `string?` |
| Target | Value types (`int`, `bool`, `struct`, etc.) | Reference types (`string`, `class`, arrays, etc.) |
| Implementation | Implemented at runtime via the `Nullable` struct | Static analysis by the compiler; does not change runtime behavior |
| Default State | Always available (since C# 1.0) | Must be enabled in the project with `enable` |
| null Check | Use `.HasValue` | Compiler issues warnings (not errors) |
> Nullable Reference Types are a compile-time safety check toolβthey do not change the runtime behavior of your program, but they emit compiler warnings where code might produce a `NullReferenceException`, helping you catch potential issues during development.
YouTip