This browser is no longer supported.
Upgrade to Microsoft Edge to take advantage of the latest features, security updates, and technical support.
Note
Access to this page requires authorization. You can trysigning in orchanging directories.
Access to this page requires authorization. You can trychanging directories.
is
andswitch
expressions, and operatorsand
,or
, andnot
in patternsYou use theis
expression, theswitch statement, and theswitch expression to match an input expression against any number of characteristics. C# supports multiple patterns, including declaration, type, constant, relational, property, list, var, and discard. Patterns can be combined using Boolean logic keywordsand
,or
, andnot
.
The following C# expressions and statements support pattern matching:
In those constructs, you can match an input expression against any of the following patterns:
var
pattern: match any expression and assign its result to a declared variable.Logical,property,positional, andlist patterns arerecursive patterns. That is, they can containnested patterns.
For the example of how to use those patterns to build a data-driven algorithm, seeTutorial: Use pattern matching to build type-driven and data-driven algorithms.
You use declaration and type patterns to check if the run-time type of an expression is compatible with a given type. With a declaration pattern, you can also declare a new local variable. When a declaration pattern matches an expression, that variable is assigned a converted expression result, as the following example shows:
object greeting = "Hello, World!";if (greeting is string message){ Console.WriteLine(message.ToLower()); // output: hello, world!}
Adeclaration pattern with typeT
matches an expression when an expression result is non-null and any of the following conditions are true:
T
.T
is aref struct
type and there is an identity conversion from the expression toT
.T
, implements interfaceT
, or anotherimplicit reference conversion exists from it toT
. This covers inheritance relationships and interface implementations. The following example demonstrates two cases when this condition is true:var numbers = new int[] { 10, 20, 30 };Console.WriteLine(GetSourceLabel(numbers)); // output: 1var letters = new List<char> { 'a', 'b', 'c', 'd' };Console.WriteLine(GetSourceLabel(letters)); // output: 2static int GetSourceLabel<T>(IEnumerable<T> source) => source switch{ Array array => 1, ICollection<T> collection => 2, _ => 3,};
In the preceding example, at the first call to theGetSourceLabel
method, the first pattern matches an argument value because the argument's run-time typeint[]
derives from theArray type. At the second call to theGetSourceLabel
method, the argument's run-time typeList<T> doesn't derive from theArray type but implements theICollection<T> interface.T
and theNullable<T>.HasValue istrue
.T
when the expression isn't an instance of aref struct
.Declaration patterns don't consider user-defined conversions or implicit span conversions.
The following example demonstrates the last two conditions:
int? xNullable = 7;int y = 23;object yBoxed = y;if (xNullable is int a && yBoxed is int b){ Console.WriteLine(a + b); // output: 30}
If you want to check only the type of an expression, you can use a discard_
in place of a variable's name, as the following example shows:
public abstract class Vehicle {}public class Car : Vehicle {}public class Truck : Vehicle {}public static class TollCalculator{ public static decimal CalculateToll(this Vehicle vehicle) => vehicle switch { Car _ => 2.00m, Truck _ => 7.50m, null => throw new ArgumentNullException(nameof(vehicle)), _ => throw new ArgumentException("Unknown type of a vehicle", nameof(vehicle)), };}
For that purpose you can use atype pattern, as the following example shows:
public static decimal CalculateToll(this Vehicle vehicle) => vehicle switch{ Car => 2.00m, Truck => 7.50m, null => throw new ArgumentNullException(nameof(vehicle)), _ => throw new ArgumentException("Unknown type of a vehicle", nameof(vehicle)),};
Like a declaration pattern, a type pattern matches an expression when an expression result is non-null and its run-time type satisfies any of the preceding conditions.
To check for non-null, you can use anegatednull
constant pattern, as the following example shows:
if (input is not null){ // ...}
For more information, see theDeclaration pattern andType pattern sections of the feature proposal notes.
Theconstant pattern is an alternative syntax for==
when the right operand is a constant. You use aconstant pattern to test if an expression result equals a specified constant, as the following example shows:
public static decimal GetGroupTicketPrice(int visitorCount) => visitorCount switch{ 1 => 12.0m, 2 => 20.0m, 3 => 27.0m, 4 => 32.0m, 0 => 0.0m, _ => throw new ArgumentException($"Not supported number of visitors: {visitorCount}", nameof(visitorCount)),};
In a constant pattern, you can use any constant expression, such as:
true
orfalse
null
The expression must be a type that is convertible to the constant type, with one exception: An expression whose type isSpan<char>
orReadOnlySpan<char>
can be matched against constant strings in C# 11 and later versions.
Use a constant pattern to check fornull
, as the following example shows:
if (input is null){ return;}
The compiler guarantees that no user-overloaded equality operator==
is invoked when expressionx is null
is evaluated.
You can use anegatednull
constant pattern to check for non-null, as the following example shows:
if (input is not null){ // ...}
For more information, see theConstant pattern section of the feature proposal note.
You use arelational pattern to compare an expression result with a constant, as the following example shows:
Console.WriteLine(Classify(13)); // output: Too highConsole.WriteLine(Classify(double.NaN)); // output: UnknownConsole.WriteLine(Classify(2.4)); // output: Acceptablestatic string Classify(double measurement) => measurement switch{ < -4.0 => "Too low", > 10.0 => "Too high", double.NaN => "Unknown", _ => "Acceptable",};
In a relational pattern, you can use any of therelational operators<
,>
,<=
, or>=
. The right-hand part of a relational pattern must be a constant expression. The constant expression can be of aninteger,floating-point,char, orenum type.
To check if an expression result is in a certain range, match it against aconjunctiveand
pattern, as the following example shows:
Console.WriteLine(GetCalendarSeason(new DateTime(2021, 3, 14))); // output: springConsole.WriteLine(GetCalendarSeason(new DateTime(2021, 7, 19))); // output: summerConsole.WriteLine(GetCalendarSeason(new DateTime(2021, 2, 17))); // output: winterstatic string GetCalendarSeason(DateTime date) => date.Month switch{ >= 3 and < 6 => "spring", >= 6 and < 9 => "summer", >= 9 and < 12 => "autumn", 12 or (>= 1 and < 3) => "winter", _ => throw new ArgumentOutOfRangeException(nameof(date), $"Date with unexpected month: {date.Month}."),};
If an expression result isnull
or fails to convert to the type of a constant by a nullable or unboxing conversion, a relational pattern doesn't match an expression.
For more information, see theRelational patterns section of the feature proposal note.
You use thenot
,and
, andor
pattern combinators to create the followinglogical patterns:
Negationnot
pattern that matches an expression when the negated pattern doesn't match the expression. The following example shows how you can negate aconstantnull
pattern to check if an expression is non-null:
if (input is not null){ // ...}
Conjunctiveand
pattern that matches an expression when both patterns match the expression. The following example shows how you can combinerelational patterns to check if a value is in a certain range:
Console.WriteLine(Classify(13)); // output: HighConsole.WriteLine(Classify(-100)); // output: Too lowConsole.WriteLine(Classify(5.7)); // output: Acceptablestatic string Classify(double measurement) => measurement switch{ < -40.0 => "Too low", >= -40.0 and < 0 => "Low", >= 0 and < 10.0 => "Acceptable", >= 10.0 and < 20.0 => "High", >= 20.0 => "Too high", double.NaN => "Unknown",};
Disjunctiveor
pattern that matches an expression when either pattern matches the expression, as the following example shows:
Console.WriteLine(GetCalendarSeason(new DateTime(2021, 1, 19))); // output: winterConsole.WriteLine(GetCalendarSeason(new DateTime(2021, 10, 9))); // output: autumnConsole.WriteLine(GetCalendarSeason(new DateTime(2021, 5, 11))); // output: springstatic string GetCalendarSeason(DateTime date) => date.Month switch{ 3 or 4 or 5 => "spring", 6 or 7 or 8 => "summer", 9 or 10 or 11 => "autumn", 12 or 1 or 2 => "winter", _ => throw new ArgumentOutOfRangeException(nameof(date), $"Date with unexpected month: {date.Month}."),};
As the preceding example shows, you can repeatedly use the pattern combinators in a pattern.
The pattern combinators are ordered based on the binding order of expressions as follows:
not
and
or
Thenot
pattern binds to its operand first. Theand
pattern binds after anynot
pattern expression binding. Theor
pattern binds after allnot
andand
patterns are bound to operands. The following example tries to match all characters that aren't lower case letters,a
-z
. It has an error, because thenot
pattern binds before theand
pattern:
// Incorrect pattern. `not` binds before `and`static bool IsNotLowerCaseLetter(char c) => c is not >= 'a' and <= 'z';
The default binding means the previous example is parsed as the following example:
// The default binding without parentheses is shows in this method. `not` binds before `and`static bool IsNotLowerCaseLetterDefaultBinding(char c) => c is ((not >= 'a') and <= 'z');
To fix it, you must specify that you want thenot
to bind to the>= 'a' and <= 'z'
expression:
// Correct pattern. Force `and` before `not`static bool IsNotLowerCaseLetterParentheses(char c) => c is not (>= 'a' and <= 'z');
Adding parentheses becomes more important as your patterns become more complicated. In general, use parentheses to clarify your patterns for other developers, as the following example shows:
static bool IsLetter(char c) => c is (>= 'a' and <= 'z') or (>= 'A' and <= 'Z');
Note
The order in which patterns having the same binding order are checked is undefined. At run time, the right-hand nested patterns of multipleor
patterns and multipleand
patterns can be checked first.
For more information, see thePattern combinators section of the feature proposal note.
You use aproperty pattern to match an expression's properties or fields against nested patterns, as the following example shows:
static bool IsConferenceDay(DateTime date) => date is { Year: 2020, Month: 5, Day: 19 or 20 or 21 };
A property pattern matches an expression when an expression result is non-null and every nested pattern matches the corresponding property or field of the expression result.
You can also add a run-time type check and a variable declaration to a property pattern, as the following example shows:
Console.WriteLine(TakeFive("Hello, world!")); // output: HelloConsole.WriteLine(TakeFive("Hi!")); // output: Hi!Console.WriteLine(TakeFive(new[] { '1', '2', '3', '4', '5', '6', '7' })); // output: 12345Console.WriteLine(TakeFive(new[] { 'a', 'b', 'c' })); // output: abcstatic string TakeFive(object input) => input switch{ string { Length: >= 5 } s => s.Substring(0, 5), string s => s, ICollection<char> { Count: >= 5 } symbols => new string(symbols.Take(5).ToArray()), ICollection<char> symbols => new string(symbols.ToArray()), null => throw new ArgumentNullException(nameof(input)), _ => throw new ArgumentException("Not supported input type."),};
A property pattern is a recursive pattern. You can use any pattern as a nested pattern. Use a property pattern to match parts of data against nested patterns, as the following example shows:
public record Point(int X, int Y);public record Segment(Point Start, Point End);static bool IsAnyEndOnXAxis(Segment segment) => segment is { Start: { Y: 0 } } or { End: { Y: 0 } };
The preceding example uses theor
pattern combinator andrecord types.
You can reference nested properties or fields within a property pattern. This capability is known as anextended property pattern. For example, you can refactor the method from the preceding example into the following equivalent code:
static bool IsAnyEndOnXAxis(Segment segment) => segment is { Start.Y: 0 } or { End.Y: 0 };
For more information, see theProperty pattern section of the feature proposal note and theExtended property patterns feature proposal note.
Tip
You can use theSimplify property pattern (IDE0170) style rule to improve code readability by suggesting places to use extended property patterns.
You use apositional pattern to deconstruct an expression result and match the resulting values against the corresponding nested patterns, as the following example shows:
public readonly struct Point{ public int X { get; } public int Y { get; } public Point(int x, int y) => (X, Y) = (x, y); public void Deconstruct(out int x, out int y) => (x, y) = (X, Y);}static string Classify(Point point) => point switch{ (0, 0) => "Origin", (1, 0) => "positive X basis end", (0, 1) => "positive Y basis end", _ => "Just a point",};
At the preceding example, the type of an expression contains theDeconstruct method, which is used to deconstruct an expression result.
Important
The order of members in a positional pattern must match the order of parameters in theDeconstruct
method. That's because the code generated for the positional pattern calls theDeconstruct
method.
You can also match expressions oftuple types against positional patterns. In that way, you can match multiple inputs against various patterns, as the following example shows:
static decimal GetGroupTicketPriceDiscount(int groupSize, DateTime visitDate) => (groupSize, visitDate.DayOfWeek) switch { (<= 0, _) => throw new ArgumentException("Group size must be positive."), (_, DayOfWeek.Saturday or DayOfWeek.Sunday) => 0.0m, (>= 5 and < 10, DayOfWeek.Monday) => 20.0m, (>= 10, DayOfWeek.Monday) => 30.0m, (>= 5 and < 10, _) => 12.0m, (>= 10, _) => 15.0m, _ => 0.0m, };
The preceding example usesrelational andlogical patterns.
You can use the names of tuple elements andDeconstruct
parameters in a positional pattern, as the following example shows:
var numbers = new List<int> { 1, 2, 3 };if (SumAndCount(numbers) is (Sum: var sum, Count: > 0)){ Console.WriteLine($"Sum of [{string.Join(" ", numbers)}] is {sum}"); // output: Sum of [1 2 3] is 6}static (double Sum, int Count) SumAndCount(IEnumerable<int> numbers){ int sum = 0; int count = 0; foreach (int number in numbers) { sum += number; count++; } return (sum, count);}
You can also extend a positional pattern in any of the following ways:
Add a run-time type check and a variable declaration, as the following example shows:
public record Point2D(int X, int Y);public record Point3D(int X, int Y, int Z);static string PrintIfAllCoordinatesArePositive(object point) => point switch{ Point2D (> 0, > 0) p => p.ToString(), Point3D (> 0, > 0, > 0) p => p.ToString(), _ => string.Empty,};
The preceding example usespositional records that implicitly provide theDeconstruct
method.
Use aproperty pattern within a positional pattern, as the following example shows:
public record WeightedPoint(int X, int Y){ public double Weight { get; set; }}static bool IsInDomain(WeightedPoint point) => point is (>= 0, >= 0) { Weight: >= 0.0 };
Combine two preceding usages, as the following example shows:
if (input is WeightedPoint (> 0, > 0) { Weight: > 0.0 } p){ // ..}
A positional pattern is a recursive pattern. That is, you can use any pattern as a nested pattern.
For more information, see thePositional pattern section of the feature proposal note.
var
patternYou use avar
pattern to match any expression, includingnull
, and assign its result to a new local variable, as the following example shows:
static bool IsAcceptable(int id, int absLimit) => SimulateDataFetch(id) is var results && results.Min() >= -absLimit && results.Max() <= absLimit;static int[] SimulateDataFetch(int id){ var rand = new Random(); return Enumerable .Range(start: 0, count: 5) .Select(s => rand.Next(minValue: -10, maxValue: 11)) .ToArray();}
Avar
pattern is useful when you need a temporary variable within a Boolean expression to hold the result of intermediate calculations. You can also use avar
pattern when you need to perform more checks inwhen
case guards of aswitch
expression or statement, as the following example shows:
public record Point(int X, int Y);static Point Transform(Point point) => point switch{ var (x, y) when x < y => new Point(-x, y), var (x, y) when x > y => new Point(x, -y), var (x, y) => new Point(x, y),};static void TestTransform(){ Console.WriteLine(Transform(new Point(1, 2))); // output: Point { X = -1, Y = 2 } Console.WriteLine(Transform(new Point(5, 2))); // output: Point { X = 5, Y = -2 }}
In the preceding example, patternvar (x, y)
is equivalent to apositional pattern(var x, var y)
.
In avar
pattern, the type of a declared variable is the compile-time type of the expression that is matched against the pattern.
For more information, see theVar pattern section of the feature proposal note.
You use adiscard pattern_
to match any expression, includingnull
, as the following example shows:
Console.WriteLine(GetDiscountInPercent(DayOfWeek.Friday)); // output: 5.0Console.WriteLine(GetDiscountInPercent(null)); // output: 0.0Console.WriteLine(GetDiscountInPercent((DayOfWeek)10)); // output: 0.0static decimal GetDiscountInPercent(DayOfWeek? dayOfWeek) => dayOfWeek switch{ DayOfWeek.Monday => 0.5m, DayOfWeek.Tuesday => 12.5m, DayOfWeek.Wednesday => 7.5m, DayOfWeek.Thursday => 12.5m, DayOfWeek.Friday => 5.0m, DayOfWeek.Saturday => 2.5m, DayOfWeek.Sunday => 2.0m, _ => 0.0m,};
In the preceding example, a discard pattern is used to handlenull
and any integer value that doesn't have the corresponding member of theDayOfWeek enumeration. That guarantees that aswitch
expression in the example handles all possible input values. If you don't use a discard pattern in aswitch
expression and none of the expression's patterns matches an input, the runtimethrows an exception. The compiler generates a warning if aswitch
expression doesn't handle all possible input values.
A discard pattern can't be a pattern in anis
expression or aswitch
statement. In those cases, to match any expression, use avar
pattern with a discard:var _
. A discard pattern can be a pattern in aswitch
expression.
For more information, see theDiscard pattern section of the feature proposal note.
You can put parentheses around any pattern. Typically, you do that to emphasize or change the precedence inlogical patterns, as the following example shows:
if (input is not (float or double)){ return;}
Beginning with C# 11, you can match an array or a list against asequence of patterns, as the following example shows:
int[] numbers = { 1, 2, 3 };Console.WriteLine(numbers is [1, 2, 3]); // TrueConsole.WriteLine(numbers is [1, 2, 4]); // FalseConsole.WriteLine(numbers is [1, 2, 3, 4]); // FalseConsole.WriteLine(numbers is [0 or 1, <= 2, >= 3]); // True
As the preceding example shows, a list pattern is matched when each nested pattern matches the corresponding element of an input sequence. You can use any pattern within a list pattern. To match any element, use thediscard pattern or, if you also want to capture the element, thevar pattern, as the following example shows:
List<int> numbers = new() { 1, 2, 3 };if (numbers is [var first, _, _]){ Console.WriteLine($"The first element of a three-item list is {first}.");}// Output:// The first element of a three-item list is 1.
The preceding examples match a whole input sequence against a list pattern. To match elements only at the start or/and the end of an input sequence, use theslice pattern..
, as the following example shows:
Console.WriteLine(new[] { 1, 2, 3, 4, 5 } is [> 0, > 0, ..]); // TrueConsole.WriteLine(new[] { 1, 1 } is [_, _, ..]); // TrueConsole.WriteLine(new[] { 0, 1, 2, 3, 4 } is [> 0, > 0, ..]); // FalseConsole.WriteLine(new[] { 1 } is [1, 2, ..]); // FalseConsole.WriteLine(new[] { 1, 2, 3, 4 } is [.., > 0, > 0]); // TrueConsole.WriteLine(new[] { 2, 4 } is [.., > 0, 2, 4]); // FalseConsole.WriteLine(new[] { 2, 4 } is [.., 2, 4]); // TrueConsole.WriteLine(new[] { 1, 2, 3, 4 } is [>= 0, .., 2 or 4]); // TrueConsole.WriteLine(new[] { 1, 0, 0, 1 } is [1, 0, .., 0, 1]); // TrueConsole.WriteLine(new[] { 1, 0, 1 } is [1, 0, .., 0, 1]); // False
A slice pattern matches zero or more elements. You can use at most one slice pattern in a list pattern. The slice pattern can only appear in a list pattern.
You can also nest a subpattern within a slice pattern, as the following example shows:
void MatchMessage(string message){ var result = message is ['a' or 'A', .. var s, 'a' or 'A'] ? $"Message {message} matches; inner part is {s}." : $"Message {message} doesn't match."; Console.WriteLine(result);}MatchMessage("aBBA"); // output: Message aBBA matches; inner part is BB.MatchMessage("apron"); // output: Message apron doesn't match.void Validate(int[] numbers){ var result = numbers is [< 0, .. { Length: 2 or 4 }, > 0] ? "valid" : "not valid"; Console.WriteLine(result);}Validate(new[] { -1, 0, 1 }); // output: not validValidate(new[] { -1, 0, 0, 1 }); // output: valid
For more information, see theList patterns feature proposal note.
For more information, see thePatterns and pattern matching section of theC# language specification.
For information about features added in C# 8 and later, see the following feature proposal notes:
Span<char>
on string literalWas this page helpful?
Was this page helpful?