diff --git a/docs/csharp/fundamentals/expressions/operators.md b/docs/csharp/fundamentals/expressions/operators.md index 70deeda36e76d..9b20b385deefa 100644 --- a/docs/csharp/fundamentals/expressions/operators.md +++ b/docs/csharp/fundamentals/expressions/operators.md @@ -69,6 +69,8 @@ Relational operators compare two values and return a `bool`. Relational operators work on all numeric types and `char`. For `char`, comparison uses the character's numeric Unicode code point value, not any alphabetical or domain-specific ordering. In the grade example above, `'B'` is greater than or equal to `'A'` because `'B'` has Unicode value 66 and `'A'` has Unicode value 65 — the *numbers* determine the comparison, not the meaning of the letter grades. +The same symbols can form [relational patterns](../patterns/relational-logical-patterns.md) in an `is` expression or `switch`. For example, `temperature < 0` is a relational expression that returns a `bool`, while `temperature is < 0` applies the relational pattern `< 0` to the value of `temperature`. + ## Equality operators `==` and `!=` check whether two values are equal or not. `!=` is `true` when the operands are **not** equal, and `false` when they are. diff --git a/docs/csharp/fundamentals/patterns/list-patterns.md b/docs/csharp/fundamentals/patterns/list-patterns.md new file mode 100644 index 0000000000000..6309af77deb8f --- /dev/null +++ b/docs/csharp/fundamentals/patterns/list-patterns.md @@ -0,0 +1,68 @@ +--- +title: "List and slice patterns" +description: Learn how C# list patterns describe a sequence's shape through its element count, ordered positions, matched values, and slices. +ms.date: 09/24/2026 +ms.topic: concept-article +ai-usage: ai-assisted +--- + +# List and slice patterns + +> [!TIP] +> This article is part of the **Fundamentals** section for developers who already know at least one programming language and are learning C#. Start with the [pattern matching overview](pattern-matching.md) if patterns are new to you. For complete supported-type and language rules, see [list patterns](../../language-reference/operators/patterns.md#list-patterns) in the language reference. + +A sequence's *shape* is the set of observable properties that a pattern requires. These properties can include the number of elements, requirements written for every element or for a sequence of elements as a whole, and marker values at specific indexes. For example, a shape might require exactly two elements with `"Name"` at index 0, or it might require a first marker, a last marker, and a nonempty sequence between them. + +A *list pattern* describes a shape by combining an element-count requirement with nested patterns at ordered positions. It tests only the requirements written in the pattern: An element subpattern tests its corresponding element, while a *slice pattern* can allow or test a sequence of otherwise unmatched elements. You can use these parts to match an exact count and marker values, capture values at fixed positions, allow an unmatched middle, or apply a nested pattern to that middle. + +Choose a list pattern when the input's compile-time type provides a `Length` or `Count` property and indexed element access, which retrieves an element by its position, as in `input[index]`. Arrays, `List`, strings, and spans are common examples. The compiler determines eligibility from the variable's declared, compile-time type because it must resolve these required members before the program runs. The interface by itself provides enumeration instead of those members, even when the runtime object is an indexable collection. + +List patterns use a length or count and indexed element access instead of enumeration. For common built-in types, matching can check only the positions named by the pattern rather than iterate through every element. The cost of those operations depends on the input type. Iterating a long collection can take time, so use a loop or LINQ when the decision requires examining an arbitrary number of elements rather than specific positions. + +## Match an exact shape + +The following method recognizes a two-column header: + +:::code language="csharp" source="snippets/patterns/ListPatterns.cs" ID="ExactListPattern"::: + +The `columns` expression is the pattern input. `["Name", "Score"]` contains two constant patterns. An exact list pattern omits a slice pattern, requires exactly two elements, and applies each nested pattern to the element at the same index. This pattern describes a shape with an exact count and two marker values: `"Name"` at index 0 and `"Score"` at index 1. Nested element patterns are optional: The empty list pattern `[]` describes, and matches, a sequence with no elements. + +Choose a list pattern when the shape combines an element count with requirements at ordered positions. If only the number of elements matters, a `Length` or `Count` property pattern, such as `items is { Count: 0 }`, states that intent more directly. + +## Use a discard to match any element value + +The following method reads the winner and third-place finisher from a three-name finishing order: + +:::code language="csharp" source="snippets/patterns/ListPatterns.cs" ID="CaptureElements"::: + +The `var winner` and `var thirdPlace` patterns each declare a variable. When the list pattern matches, C# assigns the matched element value at that position to the corresponding variable, so the result can use `winner` and `thirdPlace`. The discard pattern `_` accepts the second element and discards its value. This shape requires exactly three elements and gives the first and third positions specific meaning, but it doesn't require particular values at any position. + +Choose this form when fixed positions have stable meaning. Use a loop or LINQ when you need to inspect an arbitrary number of elements, transform a sequence, search throughout it, or perform aggregation. + +## Allow remaining elements with a slice pattern + +In this simplified example, a command line can start with `--verbose` and contain other arguments, and is assumed to end with the input file name. The following method recognizes shapes with a file name in the last position and captures that value: + +:::code language="csharp" source="snippets/patterns/ListPatterns.cs" ID="SlicePattern"::: + +The *slice pattern* `..` allows zero or more remaining elements between the first and last element patterns. A list pattern can contain at most one slice pattern. The first switch arm describes a shape with at least two elements: the marker `"--verbose"` at index 0, a file name in the last position, and any number of unmatched arguments between them. The standalone slice doesn't test those middle arguments. The second arm accepts any nonempty shape and captures its last element, while the empty pattern handles a sequence with no elements. + +A slice can appear at the beginning, middle, or end of a list pattern. Use it when a variable-length sequence is part of the shape but only the surrounding positions need element tests. Use ordinary iteration when every element needs processing. + +## Apply a pattern to a slice + +You can apply another pattern to the part matched by `..`. The following method tests whether an array starts with `"BEGIN"`, ends with `"END"`, and has at least one element between them: + +:::code language="csharp" source="snippets/patterns/ListPatterns.cs" ID="SliceSubpattern"::: + +The outer pattern first requires the marker `"BEGIN"` at index 0 and `"END"` at the last index. The property pattern `{ Length: > 0 }` then adds a requirement for the sequence matched by the slice: It must contain at least one element. Together, these requirements describe the complete shape. The pattern doesn't test the values of the elements inside the slice. + +Use a slice subpattern only when the middle portion itself needs a test or capture. If only boundary elements matter, plain `..` is simpler. + +## See also + +- [Pattern matching overview](pattern-matching.md) +- [Property and positional patterns](property-positional-patterns.md) +- [List pattern reference](../../language-reference/operators/patterns.md#list-patterns) +- [Arrays](../../language-reference/builtin-types/arrays.md) +- [Use a `foreach` statement to iterate through a collection](../statements/collections.md) diff --git a/docs/csharp/fundamentals/patterns/pattern-matching.md b/docs/csharp/fundamentals/patterns/pattern-matching.md index e7d4d8e09ffd6..3cf488976f1ab 100644 --- a/docs/csharp/fundamentals/patterns/pattern-matching.md +++ b/docs/csharp/fundamentals/patterns/pattern-matching.md @@ -21,11 +21,13 @@ You can use a pattern in three contexts: - In a `case` label of a `switch` statement. - In an arm of a `switch` expression. -Patterns are often clearer than a sequence of comparison statements because each branch describes the data it handles. For example, the following method uses a `switch` expression to choose a delivery message: +Patterns test one evaluated input against types, constants, or a shape that can contain nested patterns. A Boolean condition can also compare two independently evaluated expressions whose values aren't constants. When either form can express the same test, choose the form that's easier to read. + +For example, the following method uses a `switch` expression to choose a delivery message: :::code language="csharp" source="snippets/patterns/Overview.cs" ID="SwitchExpressionOverview"::: -Recursive patterns have their own input expressions. In `StandardDelivery { Days: <= 2 }`, the outer pattern receives the `delivery` expression. The recursive `<= 2` pattern receives the `Days` property expression from the matched `StandardDelivery` object. +Property patterns can include an outer type test and nested patterns, but neither is required in every property pattern. In `StandardDelivery { Days: <= 2 }`, the outer pattern receives the `delivery` expression and tests its type. The `Days` property expression then becomes the input to the nested relational pattern `<= 2`. The expression before `switch` is the input expression. Each line inside the braces is a *switch arm*. The pattern appears before `=>`, and the result appears after it. C# evaluates the input expression, then selects the first arm, in text order, whose pattern matches and whose optional `when` guard is `true`. The optional `when` guard is an additional Boolean condition written after the pattern. The preceding example showed the following patterns: @@ -79,17 +81,20 @@ C# includes patterns for common kinds of data tests: | --- | --- | | [Declaration, constant, and `var` patterns](declaration-constant-var-patterns.md) | A run-time type, a specific constant value, or any value that you want to capture | | [Type patterns](type-patterns.md) | A run-time type without declaring a variable | -| Property and positional patterns | Properties, fields, or deconstructed values | -| Relational and logical patterns | Comparisons and combinations such as `and`, `or`, and `not` | -| List patterns | The values and shape of a list or array | +| [Property and positional patterns](property-positional-patterns.md) | Properties, fields, or deconstructed values | +| [Relational, logical, and parenthesized patterns](relational-logical-patterns.md) | Comparisons and combinations such as `and`, `or`, and `not` | +| [List and slice patterns](list-patterns.md) | The values and shape of a supported sequence | | [Discard patterns and discards](discards.md) | Any remaining value, or a value your code intentionally ignores | -The Fundamentals articles linked in the table provide focused coverage of the categories currently documented in this section. For complete syntax and examples for all pattern categories, see the [patterns reference](../../language-reference/operators/patterns.md). +The linked Fundamentals articles explain when to choose each category. For complete syntax and examples, see the [patterns reference](../../language-reference/operators/patterns.md). ## See also - [Declaration, constant, and `var` patterns](declaration-constant-var-patterns.md) - [Type patterns](type-patterns.md) +- [Property and positional patterns](property-positional-patterns.md) +- [Relational, logical, and parenthesized patterns](relational-logical-patterns.md) +- [List and slice patterns](list-patterns.md) - [Discards](discards.md) - [Patterns reference](../../language-reference/operators/patterns.md) - [`switch` expression reference](../../language-reference/operators/switch-expression.md) diff --git a/docs/csharp/fundamentals/patterns/property-positional-patterns.md b/docs/csharp/fundamentals/patterns/property-positional-patterns.md new file mode 100644 index 0000000000000..2ae70a69236af --- /dev/null +++ b/docs/csharp/fundamentals/patterns/property-positional-patterns.md @@ -0,0 +1,78 @@ +--- +title: "Property and positional patterns" +description: Learn when to use C# property patterns to test named members and positional patterns to test ordered values. +ms.date: 09/24/2026 +ms.topic: concept-article +ai-usage: ai-assisted +--- + +# Property and positional patterns + +> [!TIP] +> This article is part of the **Fundamentals** section for developers who already know at least one programming language and are learning C#. Start with the [pattern matching overview](pattern-matching.md) if patterns are new to you. For complete language rules, see [property patterns](../../language-reference/operators/patterns.md#property-pattern) and [positional patterns](../../language-reference/operators/patterns.md#positional-pattern) in the language reference. + +Property and positional patterns both test parts of a value. The difference is how they identify those parts: + +- A *property pattern* names the properties or fields to test. +- A *positional pattern* identifies values by their order. + +A *deconstruction* exposes an ordered set of component values. A tuple already has an element order; see [deconstruct tuples](../types/tuples.md#deconstruct-tuples). For another type, a [`Deconstruct` method](../functional/deconstruct.md#user-defined-types) defines which component values are exposed and their order. + +## Compare names and positions + +The following property pattern tests two named properties of a weather reading, with temperature values in degrees Celsius: + +:::code language="csharp" source="snippets/patterns/PropertyPositionalPatterns.cs" ID="PropertyPattern"::: + +The following positional pattern tests a signal value followed by a Boolean value: + +:::code language="csharp" source="snippets/patterns/PropertyPositionalPatterns.cs" ID="TuplePattern"::: + +The property pattern identifies its inputs by the names `TemperatureC` and `HumidityPercent`. The crossing code creates a tuple from the separate `signal` and `crossingIsClear` values. The tuple pattern then identifies those values by order: `signal` first and `crossingIsClear` second. A positional pattern is a strong fit because this newly created tuple has only two values, and their order has a clear meaning in the crossing decision. + +Choose a property pattern when member names help explain the test. Property patterns are usually clearer for classes, structs, and records. Choose a positional pattern when order already gives the values an obvious meaning. Positional patterns are most useful with tuples, which combine multiple related values into one value with a fixed order. + +## Follow nested inputs in recursive patterns + +Property and positional patterns are *recursive patterns*: They apply another pattern to each property, field, or position they select. The selected value becomes the input to that nested pattern. + +In `IsHotAndHumid`, the `reading` expression is the input to the property pattern. C# evaluates that expression before matching. The pattern gets two values from the resulting object: + +- The relational pattern `> 30` tests the value of `TemperatureC`. +- The relational pattern `> 70` tests the value of `HumidityPercent`. + +An outer type test is optional, and recursive pattern clauses can be empty. For example, the empty property pattern `{ }` matches any non-null evaluated value. + +Property and positional patterns match only non-null evaluated values. When `null` is part of the input domain, choose a recursive pattern that checks for a non-null value first: + +:::code language="csharp" source="snippets/patterns/PropertyPositionalPatterns.cs" ID="NullRecursivePattern"::: + +The input expression is `value`. C# evaluates it, and the `{ }` property pattern tests the resulting value for non-null before assigning it to `nonNullValue`. The following switch expression can then test multiple possible runtime types. Its `DateTime` and `string` type patterns have no designation because the method only needs to identify each type, not capture its value. + +When recursive pattern clauses contain nested patterns, each selected property, field, or position becomes the input to its nested pattern. + +You can add a type test before the braces when the input expression can produce different types. You can also use a member path to test a nested property: + +:::code language="csharp" source="snippets/patterns/PropertyPositionalPatterns.cs" ID="NestedPropertyPattern"::: + +`value` is the input expression. C# first evaluates it and tests whether the resulting value is a . The `Date` property value then becomes the input for the `DayOfWeek` member access. Finally, the `DayOfWeek` value becomes the input to the logical pattern that tests two constants. Matching succeeds when the outer value has the specified type and every object needed along the member path is non-null. + +## Compare patterns with branching statements + +The earlier `DescribeDate` method expresses four results as patterns: + +:::code language="csharp" source="snippets/patterns/PropertyPositionalPatterns.cs" ID="NestedPropertyPattern"::: + +The following method produces the same results with a series of imperative branching statements: + +:::code language="csharp" source="snippets/patterns/PropertyPositionalPatterns.cs" ID="ImperativeDateBranches"::: + +The pattern-based version keeps the possible results together when several branches test a value's type and shape. The imperative version makes each test and return step explicit. For one condition, either form might look similar. As the number of related branches grows, patterns can make the alternatives easier to compare. + +## See also + +- [Pattern matching overview](pattern-matching.md) +- [Relational, logical, and parenthesized patterns](relational-logical-patterns.md). +- [Deconstructing tuples and other types](../functional/deconstruct.md) +- [Property pattern reference](../../language-reference/operators/patterns.md#property-pattern). +- [Positional pattern reference](../../language-reference/operators/patterns.md#positional-pattern). diff --git a/docs/csharp/fundamentals/patterns/relational-logical-patterns.md b/docs/csharp/fundamentals/patterns/relational-logical-patterns.md new file mode 100644 index 0000000000000..171de32784ebb --- /dev/null +++ b/docs/csharp/fundamentals/patterns/relational-logical-patterns.md @@ -0,0 +1,93 @@ +--- +title: "Relational, logical, and parenthesized patterns" +description: Learn how C# relational patterns compare values and how logical and parenthesized patterns combine pattern tests. +ms.date: 09/24/2026 +ms.topic: concept-article +ai-usage: ai-assisted +--- + +# Relational, logical, and parenthesized patterns + +> [!TIP] +> This article is part of the **Fundamentals** section for developers who already know at least one programming language and are learning C#. Start with the [pattern matching overview](pattern-matching.md) if patterns are new to you. For complete language rules, see [relational patterns](../../language-reference/operators/patterns.md#relational-patterns) and [logical patterns](../../language-reference/operators/patterns.md#logical-patterns) in the language reference. + +Relational and logical patterns describe ranges, alternatives, and exclusions. The following method combines them to classify a temperature: + +:::code language="csharp" source="snippets/patterns/RelationalLogicalPatterns.cs" ID="CombinedPatterns"::: + +The expression `temperature` is the pattern input. C# evaluates it once, and each switch arm tests the resulting value. The arms demonstrate these tests: + +- `< 0` tests one boundary. +- `>= 18 and <= 24` tests a range. +- `(>= 0 and < 10) or > 30` tests two alternative ranges. + +This article shows both patterns and imperative conditions so you can learn each form and compare how they express the same decisions. A single condition can look similar in either form. Patterns can make a series of related branches easier to read by keeping the choices next to their results. Choose the form that makes the code easiest to understand. + +## Compare values with relational patterns + +A *relational pattern* compares its pattern input with a compile-time constant by using `<`, `>`, `<=`, or `>=`. A *compile-time constant* is a value the compiler can evaluate while compiling the program. Numeric and character literals, and `const` variables of compatible numeric or character types, are representative examples. Ordinary variables, properties, method calls, and `static readonly` fields aren't compile-time constants. In the opening example, both `>= 18` and `<= 24` test the same evaluated `temperature` value. + +The same relational symbol can appear in an ordinary expression or in a pattern. The following example uses both forms with a temperature: + +:::code language="csharp" source="snippets/patterns/RelationalLogicalPatterns.cs" ID="ExpressionAndPattern"::: + +`temperature < threshold` is a *relational expression*. It evaluates both operands and produces a `bool`. Either operand can be a nonconstant expression. + +In `temperature is < 0`, `temperature` is the pattern input expression. C# evaluates it, and the relational pattern `< 0` tests the resulting value. In the switch arm `< 0 => "Freezing"`, the expression before `switch` supplies the input, so the pattern contains only `< 0`. + +The expression can compare `temperature` with the variable `threshold`. A relational-pattern operand must be a compile-time constant, so use the relational expression when the comparison value is a variable. When the comparison value is constant, either form can work. + +When the right operand is constant, choose mainly for readability. A relational expression often fits one direct comparison. A relational pattern composes with other patterns and fits naturally when several ranges map to switch results. + +## Combine conditions with logical patterns + +*Logical patterns* combine or negate patterns with the pattern operators `and`, `or`, and `not`: + +- An `and` pattern matches when both nested patterns match. +- An `or` pattern matches when either nested pattern matches. +- A `not` pattern succeeds when its nested pattern fails. + +The opening example uses `and` to describe a range and `or` to describe alternatives. A `not` pattern can exclude a value, as in `status is not Status.Complete`. The following methods show both forms so you can learn their syntax and compare how they express the same test: + +:::code language="csharp" source="snippets/patterns/RelationalLogicalPatterns.cs" ID="PatternAndImperative"::: + +The pattern form uses two constant patterns joined by `or`. The imperative form uses two equality expressions joined by the conditional-OR operator `||`. Both forms are concise and clear for this single condition. Choose the form that best fits the surrounding code. Patterns often clarify several related choices in a `switch`, as in the opening example. + +Pattern operators form patterns rather than Boolean expressions: `and` corresponds to pattern conjunction, `or` to pattern alternatives, and `not` to pattern negation. Boolean expressions use `&&`, `||`, and `!`. Choose `or` when several pattern alternatives have the same result. Choose `not` when expressing the excluded pattern is clearer than listing every accepted value. + +## Group patterns with parentheses + +A *parenthesized pattern* uses parentheses to show or change how nested patterns are grouped. *Binding* determines which pattern operands an operator groups together, similar to implicit grouping when you don't write parentheses. C# specifies the following binding order: + +1. `not` +1. `and` +1. `or` + +The following test accepts priorities 1 through 3 or the special priority 9: + +:::code language="csharp" source="snippets/patterns/RelationalLogicalPatterns.cs" ID="ParenthesizedPattern"::: + +The compiler groups `and` before `or`. The parentheses make the intended grouping explicit and the two alternatives easy to see: the range from 1 through 3, or 9. For readability, use parentheses whenever a pattern mixes `and` and `or`, or when `not` applies to a compound pattern. Parentheses can also change the default grouping, as in `not (>= 1 and <= 3)`. + +The runtime check order for nested patterns is unspecified, and pattern operators follow pattern-matching rules rather than short-circuit Boolean rules. Write nested patterns so their result is independent of check order. + +## Use a `when` guard for a separate condition + +Logical patterns work best when nested patterns describe the input value itself. A `when` guard is an additional Boolean condition on a `case` label or switch arm. Use a guard when the decision also depends on information separate from the pattern input. + +The following warning depends on the temperature and a separate `isOutdoors` value: + +:::code language="csharp" source="snippets/patterns/RelationalLogicalPatterns.cs" ID="WhenGuard"::: + +The relational pattern `> 35` describes the `temperature` input. The guard `when isOutdoors` checks a separate value. A guard is also preferable when the condition needs a method call or a Boolean expression that pattern syntax doesn't express clearly. + +Use relational and logical patterns when they make the input's allowed shapes or values easier to see, especially across several switch arms. Use an ordinary Boolean expression when it states a direct condition more simply. Use a `when` guard when a switch choice depends on a separate value or on a condition better expressed as a Boolean expression. + +## See also + +- [Pattern matching overview](pattern-matching.md) +- [Property and positional patterns](property-positional-patterns.md) +- [C# operators](../expressions/operators.md) +- [Relational pattern reference](../../language-reference/operators/patterns.md#relational-patterns) +- [Logical pattern reference](../../language-reference/operators/patterns.md#logical-patterns) +- [Parenthesized pattern reference](../../language-reference/operators/patterns.md#parenthesized-pattern) diff --git a/docs/csharp/fundamentals/patterns/snippets/patterns/ListPatterns.cs b/docs/csharp/fundamentals/patterns/snippets/patterns/ListPatterns.cs new file mode 100644 index 0000000000000..9c3279df8e872 --- /dev/null +++ b/docs/csharp/fundamentals/patterns/snippets/patterns/ListPatterns.cs @@ -0,0 +1,42 @@ +static class ListPatterns +{ + public static void Run() + { + Console.WriteLine($"Header: {IsHeader(["Name", "Score"])}"); + Console.WriteLine(GetAnnouncements(["Mina", "Luis", "Ada"])); + Console.WriteLine( + GetInputFile(["--verbose", "--safe", "report.csv"])); + Console.WriteLine( + $"Has content: {HasContent(["BEGIN", "value", "END"])}"); + } + + // + static bool IsHeader(string[] columns) => + columns is ["Name", "Score"]; + // + + // + static string GetAnnouncements(List finishingOrder) => + finishingOrder switch + { + [var winner, _, var thirdPlace] => + $"Winner: {winner}; third place: {thirdPlace}", + _ => "A complete three-runner result isn't available" + }; + // + + // + static string GetInputFile(string[] arguments) => + arguments switch + { + ["--verbose", .., var fileName] => $"Verbose processing: {fileName}", + [.., var fileName] => $"Processing: {fileName}", + [] => "No input file was provided" + }; + // + + // + static bool HasContent(string[] entries) => + entries is ["BEGIN", .. { Length: > 0 }, "END"]; + // +} diff --git a/docs/csharp/fundamentals/patterns/snippets/patterns/Program.cs b/docs/csharp/fundamentals/patterns/snippets/patterns/Program.cs index c55bbc0a6b086..8aea8ed7ab1e2 100644 --- a/docs/csharp/fundamentals/patterns/snippets/patterns/Program.cs +++ b/docs/csharp/fundamentals/patterns/snippets/patterns/Program.cs @@ -1,3 +1,6 @@ Overview.Run(); BasicPatterns.Run(); TypePatterns.Run(); +PropertyPositionalPatterns.Run(); +RelationalLogicalPatterns.Run(); +ListPatterns.Run(); diff --git a/docs/csharp/fundamentals/patterns/snippets/patterns/PropertyPositionalPatterns.cs b/docs/csharp/fundamentals/patterns/snippets/patterns/PropertyPositionalPatterns.cs new file mode 100644 index 0000000000000..39511bd9ca9c6 --- /dev/null +++ b/docs/csharp/fundamentals/patterns/snippets/patterns/PropertyPositionalPatterns.cs @@ -0,0 +1,91 @@ +static class PropertyPositionalPatterns +{ + public static void Run() + { + Console.WriteLine($"Hot and humid: {IsHotAndHumid( + new WeatherReading(32, 75))}"); + Console.WriteLine($"Date: {DescribeDate( + new DateTime(2026, 9, 19))}"); + Console.WriteLine($"Nullable input: {DescribeNullableInput(null)}"); + Console.WriteLine($"Date (branches): {DescribeDateWithBranches( + new DateTime(2026, 9, 19))}"); + Console.WriteLine($"Crossing: {GetCrossingInstruction( + PedestrianSignal.Walk, crossingIsClear: true)}"); + } + + // + static bool IsHotAndHumid(WeatherReading reading) => + reading is { TemperatureC: > 30, HumidityPercent: > 70 }; + + sealed record WeatherReading(int TemperatureC, int HumidityPercent); + // + + // + static string DescribeDate(object? value) => + value switch + { + DateTime { Date.DayOfWeek: + DayOfWeek.Saturday or DayOfWeek.Sunday } => "Weekend date", + DateTime => "Weekday date", + null => "No date", + _ => "Not a date" + }; + // + + // + static string DescribeNullableInput(object? value) + { + if (value is not { } nonNullValue) + { + return "No value"; + } + + return nonNullValue switch + { + DateTime => "Date", + string => "Text", + _ => "Another type" + }; + } + // + + // + static string DescribeDateWithBranches(object? value) + { + if (value is DateTime date) + { + if (date.DayOfWeek == DayOfWeek.Saturday || + date.DayOfWeek == DayOfWeek.Sunday) + { + return "Weekend date"; + } + + return "Weekday date"; + } + + if (value is null) + { + return "No date"; + } + + return "Not a date"; + } + // + + // + static string GetCrossingInstruction( + PedestrianSignal signal, bool crossingIsClear) => + (signal, crossingIsClear) switch + { + (PedestrianSignal.Walk, true) => "Cross now", + (PedestrianSignal.Walk, false) => "Wait for the crossing to clear", + _ => "Wait for the walk signal" + }; + + enum PedestrianSignal + { + Stop, + Walk + } + // +} diff --git a/docs/csharp/fundamentals/patterns/snippets/patterns/RelationalLogicalPatterns.cs b/docs/csharp/fundamentals/patterns/snippets/patterns/RelationalLogicalPatterns.cs new file mode 100644 index 0000000000000..1b0bdb015dbd7 --- /dev/null +++ b/docs/csharp/fundamentals/patterns/snippets/patterns/RelationalLogicalPatterns.cs @@ -0,0 +1,67 @@ +static class RelationalLogicalPatterns +{ + public static void Run() + { + Console.WriteLine($"Temperature: {ClassifyTemperature(21)}"); + ShowExpressionAndPattern(-4, threshold: 0); + Console.WriteLine( + $"Weekend pattern: {IsWeekendPattern(DayOfWeek.Saturday)}; " + + $"imperative: {IsWeekendImperative(DayOfWeek.Saturday)}"); + Console.WriteLine($"Accepted priority: {IsAcceptedPriority(9)}"); + Console.WriteLine( + $"Heat warning: {GetHeatWarning(36, isOutdoors: true)}"); + } + + // + static string ClassifyTemperature(int temperature) => + temperature switch + { + < 0 => "Below freezing", + >= 18 and <= 24 => "Comfortable", + (>= 0 and < 10) or > 30 => "Far outside the comfortable range", + _ => "Cool or warm" + }; + // + + // + static void ShowExpressionAndPattern(int temperature, int threshold) + { + bool belowThreshold = temperature < threshold; + bool belowFreezing = temperature is < 0; + + string description = temperature switch + { + < 0 => "Freezing", + 0 => "Freezing point", + > 0 => "Above freezing" + }; + + Console.WriteLine( + $"Below threshold: {belowThreshold}; " + + $"below freezing: {belowFreezing}; {description}"); + } + // + + // + static bool IsWeekendPattern(DayOfWeek day) => + day is DayOfWeek.Saturday or DayOfWeek.Sunday; + + static bool IsWeekendImperative(DayOfWeek day) => + day == DayOfWeek.Saturday || day == DayOfWeek.Sunday; + // + + // + static bool IsAcceptedPriority(int priority) => + priority is (>= 1 and <= 3) or 9; + // + + // + static string GetHeatWarning(int temperature, bool isOutdoors) => + temperature switch + { + > 35 when isOutdoors => "High heat outdoors", + > 35 => "High heat", + _ => "No heat warning" + }; + // +} diff --git a/docs/csharp/toc.yml b/docs/csharp/toc.yml index e388ba15c7895..2aaa5559099fa 100644 --- a/docs/csharp/toc.yml +++ b/docs/csharp/toc.yml @@ -121,6 +121,12 @@ items: href: fundamentals/patterns/declaration-constant-var-patterns.md - name: Type patterns href: fundamentals/patterns/type-patterns.md + - name: Property and positional patterns + href: fundamentals/patterns/property-positional-patterns.md + - name: Relational, logical, and parenthesized patterns + href: fundamentals/patterns/relational-logical-patterns.md + - name: List and slice patterns + href: fundamentals/patterns/list-patterns.md - name: Discards and the discard pattern href: fundamentals/patterns/discards.md - name: Expressions and statements