Null-Coalescing Operator ??
Toán tử ?? trả về vế trái nếu không null, ngược lại trả về vế phải.
// Cú pháp
result = leftOperand ?? rightOperand;
// Tương đương
result = leftOperand != null ? leftOperand : rightOperand;
Đặc điểm quan trọng
- Short-circuit: Chỉ evaluate vế phải khi vế trái là null.
- Hoạt động với: Nullable value types (
int?,DateTime?) và Reference types (string,object, custom class). - Kiểu kết quả: Common type của hai vế (hoặc nullable type).
Ví dụ cơ bản
using System;
class Program
{
static void Main()
{
// Nullable value type
int? value1 = null;
int value2 = value1 ?? 99; // 99
Console.WriteLine($"Value2: {value2}");
// Reference type
string testString = "Null Coalescing";
string resultString = testString ?? "Original string is null";
Console.WriteLine($"Result: {resultString}"); // Null Coalescing
// Chuỗi nhiều ?? (chain)
string a = null, b = null, c = "Found", d = "Also";
string result = a ?? b ?? c ?? d; // "Found" - trả về đầu tiên không null
Console.WriteLine(result);
}
}
Kết quả
Value2: 99 Result: Null Coalescing Found
Null-Coalescing Assignment ??= (C# 8+)
Gán giá trị chỉ khi biến là null.
string name = null;
name ??= "Default Name"; // Chỉ gán nếu name == null
Console.WriteLine(name); // "Default Name"
name ??= "Another"; // Không gán vì name đã có giá trị
Console.WriteLine(name); // "Default Name"
// Ứng dụng: Lazy initialization
private List _cache;
public List Cache => _cache ??= new List();
Ứng dụng thực tế
1. Default Value cho Nullable
public class User
{
public string Name { get; set; }
public string Nickname { get; set; }
public string DisplayName => Nickname ?? Name ?? "Anonymous";
}
2. Guard Clauses
public void Process(string input)
{
// Throw nếu null
_ = input ?? throw new ArgumentNullException(nameof(input));
// Hoặc gán default
var safeInput = input ?? string.Empty;
}
3. Lazy Initialization
private ExpensiveResource _resource;
public ExpensiveResource Resource => _resource ??= new ExpensiveResource();
// Thread-safe version (double-check pattern)
private ExpensiveResource _resource;
private readonly object _lock = new();
public ExpensiveResource Resource
{
get
{
if (_resource == null)
{
lock (_lock)
{
_resource ??= new ExpensiveResource();
}
}
return _resource;
}
}
4. Fallback Chain
// Config: Environment variable → AppSettings → Default
string connectionString =
Environment.GetEnvironmentVariable("DB_CONN")
?? Configuration["ConnectionStrings:Default"]
?? "Server=localhost;Database=Default;";
?? vs ?: (Ternary) vs ?. (Null-Conditional)
| Toán tử | Tên | Ví dụ | Khi nào dùng |
|---|---|---|---|
?? | Null-coalescing | a ?? b | Fallback value khi null |
?. | Null-conditional | a?.Prop | Truy cập member an toàn (trả null nếu a null) |
?[] | Null-conditional index | arr?[0] | Index an toàn |
?: | Ternary | cond ? a : b | Điều kiện bất kỳ (không chỉ null) |
Kết hợp ?? và ?.
// An toàn truy cập nested property
var city = user?.Address?.City ?? "Unknown";
// Method call an toàn
var length = list?.Count ?? 0;
// Delegate invocation an toàn
eventHandler?.Invoke(this, EventArgs.Empty);
// Tương đương:
if (eventHandler != null) eventHandler(this, EventArgs.Empty);
Type Rules
// Cả hai vế phải có implicit conversion đến kiểu chung
int? a = null;
int b = 5;
var result1 = a ?? b; // int (b là int, a là int? → int)
// Reference types
string s1 = null;
object s2 = "fallback";
var result2 = s1 ?? s2; // object (common base)
// ❌ Error: Không có implicit conversion giữa int và string
// var result3 = (int?)null ?? "fallback";
// ✅ Fix: Cast về object hoặc dùng ternary
var result3 = (object)(int?)null ?? "fallback";
var result4 = (int?)null ?? 0; // int
Lưu ý
??không hoạt động với non-nullable value types (int, bool, struct) vì chúng không bao giờ null.- Dùng
Nullable<T>hoặcT?cho value types. ??có độ ưu tiên thấp hơn hầu hết toán tử khác → dùng ngoặc khi cần.