Computer >> Hướng Dẫn Máy Tính >  >> Lập Trình >> C#

Toán tử ?? (Null-Coalescing) trong C#: Ý nghĩa và cách dùng

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ênVí dụKhi nào dùng
??Null-coalescinga ?? bFallback value khi null
?.Null-conditionala?.PropTruy cập member an toàn (trả null nếu a null)
?[]Null-conditional indexarr?[0]Index an toàn
?:Ternarycond ? 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ặc T? 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.