C# 7.0 giới thiệu pattern matching trong hai cases: is expression và switch statement.
Patterns test value có certain shape, và có thể extract information từ value khi nó có matching shape.
Pattern matching cung cấp concise syntax cho algorithms.
Có thể perform pattern matching trên bất kỳ data type nào, thậm chí custom types, trong khi if/else luôn cần primitives để match.
Pattern matching có thể extract values từ expression.
Trước Pattern Matching
public class PI {
public const float Pi = 3.142f;
}
public class Rectangle : PI {
public double Width { get; set; }
public double Height { get; set; }
}
public class Circle : PI {
public double Radius { get; set; }
}
class Program {
public static void PrintArea(PI pi) {
if (pi is Rectangle) {
Rectangle rectangle = pi as Rectangle;
System.Console.WriteLine("Area of Rect {0}", rectangle.Width * rectangle.Height);
}
else if (pi is Circle) {
Circle c = pi as Circle;
System.Console.WriteLine("Area of Circle {0}", Circle.Pi * c.Radius * c.Radius);
}
}
public static void Main() {
Rectangle r1 = new Rectangle { Width = 12.2, Height = 33 };
Rectangle r2 = new Rectangle { Width = 12.2, Height = 44 };
Circle c1 = new Circle { Radius = 12 };
PrintArea(r1);
PrintArea(r2);
PrintArea(c1);
Console.ReadLine();
}
}
Output
Area of Rect 402.59999999999997 Area of Rect 536.8 Area of Circle 452.44799423217773
Sau Pattern Matching (C# 7.0)
public class PI {
public const float Pi = 3.142f;
}
public class Rectangle : PI {
public double Width { get; set; }
public double Height { get; set; }
}
public class Circle : PI {
public double Radius { get; set; }
}
class Program {
public static void PrintArea(PI pi) {
// Type pattern: is Type variable
if (pi is Rectangle rectangle) {
System.Console.WriteLine("Area of Rect {0}", rectangle.Width * rectangle.Height);
}
else if (pi is Circle c) {
System.Console.WriteLine("Area of Circle {0}", Circle.Pi * c.Radius * c.Radius);
}
}
public static void Main() {
Rectangle r1 = new Rectangle { Width = 12.2, Height = 33 };
Rectangle r2 = new Rectangle { Width = 12.2, Height = 44 };
Circle c1 = new Circle { Radius = 12 };
PrintArea(r1);
PrintArea(r2);
PrintArea(c1);
Console.ReadLine();
}
}
Output
Area of Rect 402.59999999999997 Area of Rect 536.8 Area of Circle 452.44799423217773
Pattern Matching Types (C# 7.0 - 9.0)
| Pattern Type | Version | Syntax | Ví dụ |
|---|---|---|---|
| Constant Pattern | 7.0 | constant | case 0: case "hello": case null: |
| Type Pattern | 7.0 | Type varName | case int i: case string s: |
| Var Pattern | 7.0 | var varName | case var x: (always matches) |
| Declaration Pattern | 7.0 | Type varName | if (x is int i) |
| Switch Expression | 8.0 | expr switch { ... } | x switch { 1 => "one", _ => "other" } |
| Property Pattern | 8.0 | { Prop: pattern } | { Age: > 18, Name: "John" } |
| Tuple Pattern | 8.0 | (p1, p2) | (x, y) switch { (0,0) => ... } |
| Positional Pattern | 8.0 | Type(prop1, prop2) | Point(0, 0) => "Origin" |
| Relational Patterns | 9.0 | <, >, <=, >= | case >= 18: |
| Logical Patterns | 9.0 | and, or, not | case > 0 and < 100: |
| List/Slice Patterns | 11.0 | [p1, p2, ..] | [1, 2, ..] => "Starts with 1,2" |
Advanced Examples
Switch Expression với Property Patterns (C# 8.0)
public string GetDiscount(Customer customer) => customer switch {
{ IsVip: true, YearsRegistered: > 5 } => "25% VIP Loyalty",
{ IsVip: true } => "15% VIP",
{ YearsRegistered: > 10 } => "10% Loyalty",
{ Age: < 18 } => "5% Student",
_ => "0%"
};
Positional Patterns với Deconstruct (C# 8.0)
public record Point(int X, int Y);
public string Describe(Point p) => p switch {
(0, 0) => "Origin",
(0, _) => "On Y-axis",
(_, 0) => "On X-axis",
(var x, var y) when x == y => "On diagonal",
_ => $"Point({p.X}, {p.Y})"
};
Nested Patterns
public record Order(int Id, Customer Customer, List- Items); public record Customer(string Name, Address Address); public record Address(string City, string Country); public string GetShippingInfo(Order order) => order switch { { Customer: { Address: { Country: "Vietnam" } } } => "Domestic shipping", { Customer: { Address: { Country: var country } } } => $"International: {country}", _ => "Unknown" };
Pattern Matching với Null
public string GetName(Person person) => person switch {
{ Name: { Length: > 0 } name } => name, // Non-null, non-empty
{ Name: null } => "Anonymous",
null => "No person",
_ => "Invalid"
};
Performance & Best Practices
- Pattern matching được compile thành efficient IL (switch table, type checks)
- Property patterns compile thành property access + pattern match
- Tránh lạm dụng pattern matching phức tạp trong hot paths
- Sử dụng
_(discard) cho default case thay vìvar _ - Exhaustiveness checking: Compiler cảnh báo nếu missing cases cho sealed types/enums