Chuyển tới nội dung chính

Pattern Matching

Định nghĩa (Definition)

Khớp mẫu (Pattern Matching) là cơ chế kiểm tra giá trị theo các hình dạng (mẫu) và trích xuất thông tin từ chúng. Được giới thiệu trong C# 7 và mở rộng đáng kể trong các phiên bản sau, khớp mẫu thay thế các chuỗi if-else dài dòng và câu lệnh switch bằng cú pháp ngắn gọn, biểu cảm, kết hợp kiểm tra kiểu (type checking), so sánh giá trị (value comparison), và trích xuất dữ liệu (data extraction) vào một cấu trúc duy nhất.

Khái niệm cốt lõi (Core Concepts)

Biểu thức is (The is Expression)

Toán tử is kiểm tra xem một biểu thức có khớp với mẫu hay không. Nó có thể kiểm tra kiểu, khai báo biến, và kết hợp với toán tử quan hệ/logic.

// Type pattern — test and cast
if (obj is string s)
Console.WriteLine(s.Length);

// Declaration pattern — extract value
if (value is int number && number > 0)
Console.WriteLine($"Positive: {number}");

// Negation pattern
if (value is not null)
Console.WriteLine(value);

Biểu thức Switch (Switch Expressions - C# 8+)

Biểu thức switch (switch expression) là dạng ngắn gọn, dựa trên biểu thức của switch trả về một giá trị. Mỗi nhánh (arm) là một mẫu theo sau bởi => và kết quả.

string Classify(int value) => value switch
{
< 0 => "Negative",
0 => "Zero",
> 0 and <= 100 => "Small positive",
> 100 => "Large positive",
};

Kiểu Mẫu (Type Patterns)

Khớp dựa trên kiểu thời gian chạy (runtime type) của đối tượng và tùy chọn liên kết biến:

string Describe(object shape) => shape switch
{
Circle c => $"Circle with radius {c.Radius}",
Rectangle r => $"Rectangle {r.Width}x{r.Height}",
Triangle => "A triangle",
null => "No shape",
_ => "Unknown shape"
};

Mẫu Thuộc tính (Property Patterns)

Khớp với các thuộc tính của đối tượng sử dụng cú pháp { Property: pattern }:

decimal CalculateDiscount(Order order) => order switch
{
{ Customer.IsVip: true, Total: > 1000m } => 0.20m,
{ Customer.IsVip: true } => 0.10m,
{ Total: > 500m } => 0.05m,
_ => 0m
};

C# 10 mở rộng mẫu thuộc tính cho phép truy cập lồng nhau mà không cần lặp dấu ngoặc nhọn:

// C# 10 extended property pattern
if (order is { Customer.IsVip: true, Items.Count: > 5 })
ApplyBulkDiscount(order);

Mẫu Vị trí (Positional Patterns)

Hoạt động với các kiểu hỗ trợ phân cấu (deconstruction) (thông qua phương thức Deconstruct hoặc record vị trí):

record Point(double X, double Y);

string Classify(Point p) => p switch
{
(0, 0) => "Origin",
(0, _) => "On Y-axis",
(_, 0) => "On X-axis",
( > 0, > 0) => "First quadrant",
_ => "Elsewhere"
};

Mẫu Quan hệ (Relational Patterns)

So sánh với giá trị hằng sử dụng <, >, <=, >=:

string GetGrade(int score) => score switch
{
>= 90 => "A",
>= 80 => "B",
>= 70 => "C",
>= 60 => "D",
_ => "F"
};

Mẫu Logic (Logical Patterns)

Kết hợp mẫu với and, or, not:

bool IsLetter(char c) => c is (>= 'a' and <= 'z') or (>= 'A' and <= 'Z');

bool IsNotValid(int? age) => age is null or < 0 or > 150;

Mẫu Danh sách (List Patterns - C# 11)

Khớp các phần tử trong mảng và danh sách. Sử dụng [..] cho mẫu cắt (slice pattern):

int SumFirstTwo(int[] numbers) => numbers switch
{
[] => 0,
[var a] => a,
[var a, var b] => a + b,
[var a, var b, ..] => a + b,
};

string ClassifyResponse(int[] codes) => codes switch
{
[200] => "OK",
[200, 200] => "All OK",
[400, ..] => "Client error prefix",
[>= 500] => "Server error",
_ => "Unknown"
};

Ví dụ Mã (Code Examples)

Máy trạng thái với Khớp mẫu (State Machine with Pattern Matching)

enum State { Open, Closed, Locked }
enum Action { Open, Close, Lock, Unlock }

State Transition(State current, Action action) => (current, action) switch
{
(State.Open, Action.Close) => State.Closed,
(State.Closed, Action.Open) => State.Open,
(State.Closed, Action.Lock) => State.Locked,
(State.Locked, Action.Unlock) => State.Closed,
_ => current // No transition
};

Xác thực Dữ liệu (Data Validation)

static string ValidateUser(User user) => user switch
{
null => "User is required",
{ Name: null or "" } => "Name is required",
{ Age: < 0 or > 150 } => "Invalid age",
{ Email: not string { Length: > 0 } } => "Email is required",
_ => "Valid"
};

Khi nào Sử dụng (When to Use)

  • Thay thế chuỗi if-else — biểu thức switch dễ đọc hơn khi phân nhánh trên cùng một giá trị
  • Kiểm tra kiểu và ép kiểuis Type var an toàn và gọn hơn typeof + ép kiểu (cast)
  • Xác thực dữ liệu — mẫu thuộc tính thể hiện rõ ràng các ràng buộc cấu trúc
  • Máy trạng thái (State machine) — mẫu tuple trên cặp (trạng thái, sự kiện) làm cho chuyển đổi mang tính khai báo
  • Phân cấu record và tuple — mẫu vị trí phù hợp tự nhiên với kiểu record
  • Xử lý trường hợp nullis null, is not null tích hợp với mọi mẫu khác

Lỗi thường gặp (Common Pitfalls)

Thứ tự mẫu quan trọng (Pattern order matters)

Các mẫu được đánh giá từ trên xuống dưới. Mẫu cụ thể hơn phải xuất hiện trước mẫu tổng quát hơn, nếu không chúng sẽ không bao giờ được đạt tới. Trình biên dịch cảnh báo về các mẫu không thể đạt tới, nhưng chỉ trong cùng một biểu thức switch.

Tính đầy đủ (Exhaustiveness)

Biểu thức switch phải đầy đủ — mọi đầu vào có thể phải có một nhánh khớp. Sử dụng mẫu loại bỏ (discard pattern) _ làm nhánh bắt tất cả (catch-all) khi cần. Trình biên dịch sẽ báo lỗi nếu thiếu nhánh.

Hiệu suất trong đường dẫn nóng (Performance in hot paths)

Khớp mẫu phức tạp với mẫu thuộc tính lồng nhau hoặc mẫu danh sách liên quan đến kiểm tra kiểu thời gian chạy và cấp phát. Trong các vòng lặp quan trọng hiệu suất, hãy đo lường trước khi áp dụng. Mẫu kiểu và hằng đơn giản thân thiện với JIT.

Sử dụng cảnh báo trình biên dịch (Use compiler warnings)

Bật <TreatWarningsAsErrors>true</TreatWarningsAsErrors> hoặc ít nhất CS8509 (switch không đầy đủ - non-exhaustive switch) để phát hiện thiếu nhánh mẫu tại thời điểm biên dịch.

Tóm tắt (Key Takeaways)

  1. Khớp mẫu (Pattern matching) thống nhất kiểm tra kiểu (type checking), so sánh giá trị (value comparison), và trích xuất dữ liệu (data extraction) vào một cú pháp duy nhất.
  2. Biểu thức switch (switch expression) trả về giá trị và ngắn gọn hơn câu lệnh switch.
  3. Mẫu thuộc tính (property pattern) và mẫu vị trí (positional pattern) cho phép khớp cấu trúc mà không cần ép kiểu thủ công.
  4. Mẫu quan hệ (relational pattern) và mẫu logic (logical pattern) (and, or, not) thay thế điều kiện if phức hợp.
  5. Mẫu danh sách (list pattern - C# 11) mang tính năng phân cấu (destructuring) đến mảng và span.
  6. Luôn sắp xếp mẫu từ cụ thể nhất đến tổng quát nhất.

Câu hỏi Phỏng vấn (Interview Questions)

Câu hỏi: Khớp mẫu (pattern matching) trong C# là gì? Khớp mẫu là tính năng cho phép kiểm tra một biểu thức với một loạt mẫu và trích xuất dữ liệu khi khớp. Nó mở rộng toán tử is và cấu trúc switch để hỗ trợ mẫu kiểu (type pattern), mẫu thuộc tính (property pattern), mẫu vị trí (positional pattern), mẫu quan hệ (relational pattern), mẫu logic (logical pattern), và mẫu danh sách (list pattern).

Câu hỏi: Biểu thức switch (switch expression) là gì và khác gì so với câu lệnh switch (switch statement)? Biểu thức switch (C# 8) dựa trên biểu thức: chúng trả về giá trị, sử dụng nhánh =>, và yêu cầu tính đầy đủ (exhaustiveness). Câu lệnh switch dựa trên câu lệnh, sử dụng case/break, và không trả về giá trị. Biểu thức switch được ưu tiên khi mục tiêu là tính toán một giá trị.

Câu hỏi: Có mẫu mới nào được thêm trong C# 11 không? C# 11 giới thiệu mẫu danh sách (list pattern) để khớp chuỗi ([a, b, .., c]), mẫu cắt (slice pattern) (..) trong mẫu danh sách, và cải thiện khớp mẫu Span<char> cho các tình huống phân tích cú pháp.

Câu hỏi: Sự khác biệt giữa is Typeas Type là gì? is Type trả về boolean và, với mẫu khai báo, cũng liên kết một biến có kiểu. as trả về giá trị đã ép kiểu hoặc null nếu ép kiểu thất bại. is được ưu tiên với khớp mẫu vì nó kết hợp kiểm tra và liên kết.

Câu hỏi: Mẫu thuộc tính (property pattern) là gì và khi nào sử dụng? Mẫu thuộc tính ({ Prop: value }) khớp đối tượng có thuộc tính thỏa mãn các mẫu lồng nhau. Sử dụng khi cần phân nhánh dựa trên hình dạng dữ liệu — ví dụ, định tuyến yêu cầu dựa trên phương thức HTTP và đường dẫn, hoặc xác thực DTO.

Tài liệu tham khảo (References)