How to Use Property Patterns in Modern C
Property patterns in modern C# provide a concise, declarative syntax to test the properties and fields of an object against specific values, conditions, or nested structures. First introduced in C# 8 and significantly expanded in subsequent versions, property patterns eliminate repetitive null checks and boilerplate comparison code. This article explains the core mechanics of property patterns, demonstrates their integration with modern switch expressions and logical combinators, and covers extended nested patterns introduced in recent C# updates.
Basic Syntax and Matching
A basic property pattern matches an expression when an object is
non-null and its specified properties match the given constants or
conditions. The syntax uses curly braces { } containing one
or more property-value pairs.
public record Order(int Id, decimal Total, string Status);
public bool IsEligibleForFreeShipping(Order order)
{
// Matches if order is non-null and Status is "Completed" with Total >= 50
return order is { Status: "Completed", Total: >= 50m };
}In this example, the runtime ensures order is not null
before evaluating Status and Total. If
order is null, the pattern safely evaluates to
false without throwing a
NullReferenceException.
Using Property Patterns with Switch Expressions
Property patterns are commonly paired with switch
expressions to handle multi-branch logic cleanly:
public decimal CalculateDiscount(Order order) => order switch
{
{ Status: "Cancelled" } => 0m,
{ Total: >= 100m, Status: "Active" } => 0.15m,
{ Total: >= 50m, Status: "Active" } => 0.05m,
{ } => 0m, // Matches any non-null Order not caught above
null => throw new ArgumentNullException(nameof(order))
};The empty pattern { } serves as a match-all for any
non-null instance of the type, providing a clear distinction between
unhandled values and actual null references.
Relational and Logical Combinators
Starting in C# 9, property patterns can incorporate relational
operators (<, <=, >,
>=) and logical combinators (and,
or, not):
public record Customer(int Age, int LoyaltyPoints, bool IsSubscribed);
public bool QualifiesForVip(Customer customer) => customer is
{
Age: >= 18,
LoyaltyPoints: > 500 or (>= 250 and not 300),
IsSubscribed: true
};This syntax allows multiple constraints on individual properties
without separating them into distinct if statements.
Extended Property Patterns (C# 10+)
Before C# 10, inspecting nested properties required deeply nested braces, which could become difficult to read:
// C# 8 and C# 9 nested property pattern
if (employee is { Department: { Location: { City: "Seattle" } } })
{
// ...
}C# 10 introduced extended property patterns, enabling dot notation within the pattern:
// C# 10+ extended property pattern
if (employee is { Department.Location.City: "Seattle" })
{
// ...
}Each referenced property in the chain is automatically verified for
null. If Department or Location is null, the
pattern resolves to false without raising a runtime
error.
Pattern Variable Assignment
You can capture either the root object or an individual property
value while evaluating the pattern using the var keyword or
type patterns:
if (order is { Total: var total and > 100m } highValueOrder)
{
Console.WriteLine($"Order {highValueOrder.Id} qualified with a total of {total}.");
}Here, total receives the evaluated value of
order.Total, and highValueOrder receives the
reference to order, scoped to the conditional block.
Summary of Key Behaviors
- Implicit Null Safety: Property patterns evaluate to
falseif the target object—or any property accessed via extended dot notation—isnull. - Type Independence: Property patterns do not require
classes to implement a
Deconstructmethod, unlike positional patterns. - Composability: They integrate seamlessly with
relational operators, logical operators, variable declarations, and
switchexpressions.