Pattern matching that reads like the business rule
Switch expressions, property patterns and list patterns can turn a page of nested ifs into a few lines that read like the requirements. Here are the forms worth knowing.
Business rules often arrive as a table: if the customer is a partner and the order is over $10,000, apply this; if it's a first order, apply that. Written with nested if statements, the rule is hard to see. C# pattern matching lets the code look much more like the table.
Switch expressions
decimal ShippingCost(Order order) => order switch
{
{ Total: >= 100m } => 0m,
{ Destination.Country: "US", Express: true } => 25m,
{ Destination.Country: "US" } => 8m,
{ Express: true } => 60m,
_ => 30m
};
Each line is one rule, checked top to bottom, and the first match wins. That example combines several pattern types.
Property patterns
{ Total: >= 100m } matches when Total is at least 100. Patterns can reach into nested properties with a dot, as in { Destination.Country: "US" }, and several properties in one pattern must all match.
Relational and logical patterns
Comparisons and and, or, not let you express ranges directly:
string RiskBand(int score) => score switch
{
< 0 or > 1000 => throw new ArgumentOutOfRangeException(nameof(score)),
>= 800 => "Low",
>= 500 => "Medium",
_ => "High"
};
is not null is the clearest null check in C# for the same reason:
if (order.Coupon is not null) { /* ... */ }
Type patterns with conditions
Handling different message types is a natural fit:
Task HandleAsync(object message, CancellationToken ct) => message switch
{
OrderPlaced { Total: > 10_000m } large => fraudCheck.ReviewAsync(large, ct),
OrderPlaced placed => fulfillment.StartAsync(placed, ct),
OrderCancelled cancelled => fulfillment.StopAsync(cancelled.OrderId, ct),
_ => throw new NotSupportedException($"Unknown message {message.GetType().Name}")
};
List patterns
Since C# 11 you can match on the shape of arrays and lists:
string Describe(string[] parts) => parts switch
{
[] => "empty",
[var only] => $"just {only}",
[var first, .., var last] => $"from {first} to {last}"
};
That's handy for parsing simple command formats or CSV lines without index arithmetic.
Let the compiler check exhaustiveness
If a switch expression on an enum doesn't handle every value, the compiler warns you (CS8509). Add a value to the enum later and every switch that forgot it gets flagged. Avoid a catch-all _ arm on enums when you want that safety net.
Know when to stop
A switch with fifteen arms and three levels of nested patterns isn't more readable than the if statements it replaced. If a rule needs a comment to explain it, give it a well-named method or variable instead.
Takeaway
Use switch expressions with property, relational and list patterns to write business rules as a list of cases that reads like the requirement. Let the compiler check that you've covered every case, and keep each pattern simple enough to read at a glance.