From f20f93fe8cfcee65cbe907f10074d0c6da5d72fc Mon Sep 17 00:00:00 2001 From: Bill Wagner Date: Thu, 17 Sep 2026 16:45:07 -0400 Subject: [PATCH 1/2] First run. Scenarios need work --- .../fundamentals/expressions/operators.md | 2 + .../fundamentals/patterns/list-patterns.md | 64 +++++++++++++ .../fundamentals/patterns/pattern-matching.md | 11 ++- .../patterns/property-positional-patterns.md | 67 +++++++++++++ .../patterns/relational-logical-patterns.md | 82 ++++++++++++++++ .../snippets/patterns/ListPatterns.cs | 45 +++++++++ .../patterns/snippets/patterns/Program.cs | 3 + .../patterns/PropertyPositionalPatterns.cs | 68 +++++++++++++ .../patterns/RelationalLogicalPatterns.cs | 96 +++++++++++++++++++ docs/csharp/toc.yml | 6 ++ 10 files changed, 440 insertions(+), 4 deletions(-) create mode 100644 docs/csharp/fundamentals/patterns/list-patterns.md create mode 100644 docs/csharp/fundamentals/patterns/property-positional-patterns.md create mode 100644 docs/csharp/fundamentals/patterns/relational-logical-patterns.md create mode 100644 docs/csharp/fundamentals/patterns/snippets/patterns/ListPatterns.cs create mode 100644 docs/csharp/fundamentals/patterns/snippets/patterns/PropertyPositionalPatterns.cs create mode 100644 docs/csharp/fundamentals/patterns/snippets/patterns/RelationalLogicalPatterns.cs 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..3ea8090e8f408 --- /dev/null +++ b/docs/csharp/fundamentals/patterns/list-patterns.md @@ -0,0 +1,64 @@ +--- +title: "List and slice patterns" +description: Learn when to use C# list patterns to test a sequence's shape and selected elements, and slice patterns to allow unmatched elements. +ms.date: 09/17/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 *list pattern* tests the shape of an array, list, or another supported sequence and applies nested patterns to selected elements. Shape includes the number and positions of elements. A *slice pattern*, written `..`, allows a list pattern to contain zero or more elements that aren't tested individually. + +List patterns don't make every input matchable. The input's compile-time type must support the length or count and element access required by list-pattern rules. Arrays, `List`, strings, and spans are common examples. + +## Match an exact shape + +A card reader receives command bytes from a device. A valid reset command contains exactly three bytes: a start marker, the reset operation code, and an end marker. The program must validate the complete command before resetting the device: + +:::code language="csharp" source="snippets/patterns/ListPatterns.cs" ID="ExactListPattern"::: + +The `command` expression is the pattern input. `[0x02, 0x52, 0x03]` contains three constant patterns. Without a slice pattern, the length must be exactly three, and each nested pattern must match the element in the same position. A longer command doesn't match even if its first three bytes are the same. + +Choose a list pattern when both the sequence shape and selected element values express the decision. If only the number of elements matters, a `Length` or `Count` property pattern, such as `items is { Count: 0 }`, states that intent more directly. + +## Match selected elements with discards + +A race application receives the finishing order as a list of runner names. It needs the winner and third-place finisher to prepare two separate announcements: + +:::code language="csharp" source="snippets/patterns/ListPatterns.cs" ID="CaptureElements"::: + +`var winner` and `var thirdPlace` capture elements that the result uses. The discard pattern `_` accepts the second element without retaining it. Because there's no `..`, the list must contain exactly three elements. + +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 + +An application command line can start with `--verbose`, continue with zero or more other arguments, and end with the input file name. The program needs to recognize that shape and capture the file name: + +:::code language="csharp" source="snippets/patterns/ListPatterns.cs" ID="SlicePattern"::: + +The slice pattern `..` matches zero or more elements between the first and last elements. A list pattern can contain at most one slice pattern. In this example, the program doesn't need the middle arguments, so the slice has no nested pattern or variable. + +A slice can appear at the beginning, middle, or end of a list pattern. Use it when the elements around the slice are the meaningful part of the shape. Don't use a list pattern to replace ordinary iteration when every element needs processing. + +## Apply a pattern to a slice + +You can apply another pattern to the part matched by `..`. A message-processing service treats the first and last entries as protocol markers and needs to know whether the payload between them is empty: + +:::code language="csharp" source="snippets/patterns/ListPatterns.cs" ID="SliceSubpattern"::: + +The outer pattern first requires `"BEGIN"` and `"END"` at the boundaries. The property pattern `{ Length: 0 }` then tests the slice between them. The result tells the service to reject an empty payload instead of sending it for processing. + +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..3f5ae6d6edeaf 100644 --- a/docs/csharp/fundamentals/patterns/pattern-matching.md +++ b/docs/csharp/fundamentals/patterns/pattern-matching.md @@ -79,17 +79,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..15979f6a10378 --- /dev/null +++ b/docs/csharp/fundamentals/patterns/property-positional-patterns.md @@ -0,0 +1,67 @@ +--- +title: "Property and positional patterns" +description: Learn when to use C# property patterns to test named members and positional patterns to test deconstructed or tuple values. +ms.date: 09/17/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 test parts of a value. A *property pattern* names the properties or fields to test. A *positional pattern* tests values produced by deconstructing an object or tuple. + +Both are *recursive patterns*: Each member or position has its own nested pattern. The input to the outer pattern is an expression. C# evaluates that expression, then applies each nested pattern to the corresponding part of the evaluated value. + +## Test named members with a property pattern + +A warehouse uses `Package` objects to decide which packages need careful handling. The `Destination` property identifies where a package is going, and `WeightKg` records its weight. The following test identifies heavy international packages so the shipping system can add special handling instructions: + +:::code language="csharp" source="snippets/patterns/PropertyPositionalPatterns.cs" ID="PropertyPattern"::: + +The `package` expression is the input to the outer pattern. The pattern matches only when its evaluated value is non-null and both nested patterns match: + +- The constant pattern `"International"` tests the value of the named `Destination` property. +- The relational pattern `> 20` tests the value of the named `WeightKg` property. + +Choose a property pattern when member names help explain the test. Unlike a series of Boolean expressions, the pattern groups the relevant shape and values in one description. For one simple comparison, such as `package.WeightKg > 20`, an ordinary relational expression is often clearer. + +You can add a type test before the braces when the input expression can produce different types. The following order system receives an `Order` base type. `StorePickup` and `ShippedOrder` are specialized order types. A shipped order has an `Address`, and the address has a `CountryCode`. The system needs to choose the team that fulfills each order: + +:::code language="csharp" source="snippets/patterns/PropertyPositionalPatterns.cs" ID="NestedPropertyPattern"::: + +`ShippedOrder { Address.CountryCode: not "US" }` first tests that the evaluated value is a non-null `ShippedOrder`. It then follows the named `Address.CountryCode` member path. The pattern doesn't match if the outer value is `null`, if the type test fails, or if any object needed along that member path is `null`. Otherwise, the nested `not "US"` pattern tests the country code. This behavior makes the pattern suitable for selecting the correct fulfillment team without separate type, null, and member checks. + +Choose named properties over positions when readers would need to memorize what each position means. + +## Test a stable shape with a positional pattern + +A *positional pattern* deconstructs a value and applies nested patterns in order. A type can define that order with a `Deconstruct` method. Positional records provide deconstruction automatically. + +The following `GridPoint` record represents a location with an `X` coordinate followed by a `Y` coordinate. That two-value shape is intentional and familiar throughout the mapping code. The method classifies points so the map can choose where to draw their labels: + +:::code language="csharp" source="snippets/patterns/PropertyPositionalPatterns.cs" ID="PositionalPattern"::: + +The `point` expression is the pattern input. For `(0, 0)`, C# evaluates `point`, deconstructs the non-null value into its `X` and `Y` components, and applies a constant pattern to each component. The discard pattern `_` accepts a component that doesn't matter to that arm. + +The positions must follow the type's deconstruction order. Choose a positional pattern when that order is a deliberate, stable part of the type's design, such as `(X, Y)`. Use a property pattern when names communicate the test better or when the deconstruction order is difficult to remember. + +## Match a tuple of related inputs + +A tuple combines multiple values into one value with a fixed positional shape. A traffic controller considers the pedestrian signal and whether the crossing is clear. It needs both results to decide whether a person should cross: + +:::code language="csharp" source="snippets/patterns/PropertyPositionalPatterns.cs" ID="TuplePattern"::: + +The tuple expression `(signal, crossingIsClear)` is the input. Each switch arm applies a positional pattern to both tuple elements. This form keeps the combinations and their consequential results together. + +Choose a tuple pattern when several small, related inputs jointly determine one result. If the positions need extensive explanation or the data belongs together throughout the program, define a type with named properties instead. + +## 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..b6b8cd19113e1 --- /dev/null +++ b/docs/csharp/fundamentals/patterns/relational-logical-patterns.md @@ -0,0 +1,82 @@ +--- +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/17/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. + +A *relational pattern* compares an evaluated value with a constant by using `<`, `>`, `<=`, or `>=`. *Logical patterns* combine or negate patterns with the pattern operators `and`, `or`, and `not`. A *parenthesized pattern* uses parentheses to make the intended grouping explicit or to change the default grouping. + +## Distinguish expressions from patterns + +Suppose a weather app receives the outdoor temperature as a whole number of degrees Celsius. The app needs to decide whether to show a freeze warning and to display a short description of the current conditions. The same relational symbol can appear in an ordinary expression or in a pattern: + +:::code language="csharp" source="snippets/patterns/RelationalLogicalPatterns.cs" ID="ExpressionAndPattern"::: + +`temperature < 0` is a *relational expression*. It has a left operand and a right operand, and produces a `bool`. + +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 example displays both Boolean results to show that these two tests classify the same temperature. It uses the result from `temperature is < 0` to choose whether to show the warning. The string returned by the switch expression becomes the conditions description. + +Choose a relational expression for one direct comparison. Choose relational patterns when the comparison is part of a larger pattern or when several ranges map cleanly to switch results. + +## Describe ranges with `and` + +A greenhouse controller has a preferred temperature range of 18 through 24 degrees Celsius, inclusive. When the temperature is in that range, the controller keeps the current airflow. Otherwise, it tells the ventilation system to adjust the airflow: + +:::code language="csharp" source="snippets/patterns/RelationalLogicalPatterns.cs" ID="AndPattern"::: + +The input expression is `temperature`. The logical pattern `>= 18 and <= 24` combines two relational patterns that both test the same evaluated value. The `and` pattern matches only when both nested patterns match. + +`and` is a pattern operator here, not the conditional-AND Boolean operator `&&`. Don't read the syntax as a promise that nested patterns are tested left to right or short-circuit like Boolean operands. Pattern matching describes what must match; don't rely on the order in which nested patterns are tested. + +## Describe alternatives with `or` and exclusions with `not` + +A transit service uses named enum values for a service day. Weekend service follows a different timetable: + +:::code language="csharp" source="snippets/patterns/RelationalLogicalPatterns.cs" ID="OrNotPatterns"::: + +`ServiceDay` is an enum that defines the named values used by the schedule. `ServiceDay.Saturday or ServiceDay.Sunday` is one logical pattern composed of two constant patterns. It matches when either nested pattern matches. The returned timetable name controls which schedule the app displays. + +`ServiceStatus` represents the current operating condition of the transit service. An `Open` or `Limited` service can still accept trip-planning requests, but a `Closed` service can't. The `IsAvailable` result controls whether the app offers trip planning or shows that the service is unavailable. + +The `IsAvailable` method uses the `not` pattern to reject the `Closed` status. `not` is a pattern operator, not the Boolean negation operator `!`. 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 + +Pattern operators bind in this order: + +1. `not` +1. `and` +1. `or` + +Suppose a request queue supports priority levels 1 through 3 for ordinary requests. Dispatchers can use priority 9 as an override for an urgent request. The following test accepts either kind of supported priority: + +:::code language="csharp" source="snippets/patterns/RelationalLogicalPatterns.cs" ID="ParenthesizedPattern"::: + +The parentheses aren't required for the compiler because `and` binds before `or`, but they make the two alternatives visible: an ordinary priority range, or the override. The program uses the Boolean result to add a request to the queue or reject an unsupported priority. Use parentheses whenever a reader might hesitate over the grouping. Parentheses can also change the default grouping, such as `not (>= 1 and <= 3)`. + +## 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 that isn't naturally part of the pattern. + +The following delivery price depends on the package weight and on a separate `isHoliday` setting: + +:::code language="csharp" source="snippets/patterns/RelationalLogicalPatterns.cs" ID="WhenGuard"::: + +The relational pattern `> 20` describes the `weightKg` input. The guard `when isHoliday` checks separate application state. A guard is also preferable when the condition needs a method call or a Boolean expression that pattern syntax doesn't express clearly. + +## 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 and parenthesized pattern reference](../../language-reference/operators/patterns.md#logical-patterns) 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..c5581fca8c580 --- /dev/null +++ b/docs/csharp/fundamentals/patterns/snippets/patterns/ListPatterns.cs @@ -0,0 +1,45 @@ +static class ListPatterns +{ + public static void Run() + { + Console.WriteLine(IsResetCommand([0x02, 0x52, 0x03])); + Console.WriteLine(GetAnnouncements(["Mina", "Luis", "Ada"])); + Console.WriteLine(GetInputFile(["--verbose", "--safe", "report.csv"])); + Console.WriteLine(ValidateMessage(["BEGIN", "END"])); + } + + // + static bool IsResetCommand(byte[] command) => + command is [0x02, 0x52, 0x03]; + // + + // + 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 string ValidateMessage(string[] entries) => + entries switch + { + ["BEGIN", .. { Length: 0 }, "END"] => "Reject empty payload", + ["BEGIN", .., "END"] => "Process payload", + _ => "Reject malformed message" + }; + // +} 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..c66b31f953f6b --- /dev/null +++ b/docs/csharp/fundamentals/patterns/snippets/patterns/PropertyPositionalPatterns.cs @@ -0,0 +1,68 @@ +static class PropertyPositionalPatterns +{ + public static void Run() + { + Console.WriteLine(NeedsSpecialHandling( + new Package("International", 24))); + Console.WriteLine(SelectFulfillmentTeam( + new ShippedOrder(new Address("CA")))); + Console.WriteLine(ClassifyPoint(new GridPoint(0, 5))); + Console.WriteLine(GetCrossingInstruction( + PedestrianSignal.Walk, crossingIsClear: true)); + } + + // + static bool NeedsSpecialHandling(Package package) => + package is { Destination: "International", WeightKg: > 20 }; + + sealed record Package(string Destination, decimal WeightKg); + // + + // + static string SelectFulfillmentTeam(Order? order) => + order switch + { + StorePickup => "Store team", + ShippedOrder { Address.CountryCode: not "US" } => + "International shipping team", + ShippedOrder => "Domestic shipping team", + null => "No order to fulfill", + _ => "Order review team" + }; + + abstract record Order; + sealed record StorePickup : Order; + sealed record ShippedOrder(Address Address) : Order; + sealed record Address(string CountryCode); + // + + // + static string ClassifyPoint(GridPoint point) => + point switch + { + (0, 0) => "Origin", + (0, _) => "On the vertical axis", + (_, 0) => "On the horizontal axis", + _ => "Away from both axes" + }; + + readonly record struct GridPoint(int X, int Y); + // + + // + 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..c9f3a22d7a2e7 --- /dev/null +++ b/docs/csharp/fundamentals/patterns/snippets/patterns/RelationalLogicalPatterns.cs @@ -0,0 +1,96 @@ +static class RelationalLogicalPatterns +{ + public static void Run() + { + ShowExpressionAndPattern(-4); + Console.WriteLine(GetVentilationMode(21)); + Console.WriteLine(GetTimetable(ServiceDay.Saturday)); + Console.WriteLine(GetServiceAction(ServiceStatus.Limited)); + Console.WriteLine(GetPriorityAction(9)); + Console.WriteLine(GetShippingPrice(22, isHoliday: true)); + } + + // + static void ShowExpressionAndPattern(int temperature) + { + bool freezeWarningFromExpression = temperature < 0; + bool freezeWarningFromPattern = temperature is < 0; + + string description = temperature switch + { + < 0 => "Freezing", + 0 => "Freezing point", + > 0 => "Above freezing" + }; + + string warning = freezeWarningFromPattern + ? "Show freeze warning" + : "No freeze warning"; + + Console.WriteLine( + $"Expression: {freezeWarningFromExpression}; " + + $"pattern: {freezeWarningFromPattern}; {description}; {warning}"); + } + // + + // + static string GetVentilationMode(int temperature) => + temperature is >= 18 and <= 24 + ? "Keep current airflow" + : "Adjust airflow"; + // + + // + static string GetTimetable(ServiceDay day) => + day is ServiceDay.Saturday or ServiceDay.Sunday + ? "Weekend timetable" + : "Weekday timetable"; + + static bool IsAvailable(ServiceStatus status) => + status is not ServiceStatus.Closed; + + static string GetServiceAction(ServiceStatus status) => + IsAvailable(status) + ? "Offer trip planning" + : "Show service unavailable"; + + enum ServiceDay + { + Monday, + Tuesday, + Wednesday, + Thursday, + Friday, + Saturday, + Sunday + } + + enum ServiceStatus + { + Open, + Limited, + Closed + } + // + + // + static bool IsAcceptedPriority(int priority) => + priority is (>= 1 and <= 3) or 9; + + static string GetPriorityAction(int priority) => + IsAcceptedPriority(priority) + ? "Add request to queue" + : "Reject unsupported priority"; + // + + // + static decimal GetShippingPrice(decimal weightKg, bool isHoliday) => + weightKg switch + { + > 20 when isHoliday => 45.00m, + > 20 => 35.00m, + _ when isHoliday => 20.00m, + _ => 15.00m + }; + // +} 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 From 16309296e225c3e49f3ab11f7c3ff693b1854e0e Mon Sep 17 00:00:00 2001 From: Bill Wagner Date: Thu, 17 Sep 2026 17:32:31 -0400 Subject: [PATCH 2/2] A better second draft. --- .../fundamentals/patterns/list-patterns.md | 12 +-- .../patterns/property-positional-patterns.md | 20 ++--- .../patterns/relational-logical-patterns.md | 24 +++--- .../snippets/patterns/ListPatterns.cs | 21 +++--- .../patterns/PropertyPositionalPatterns.cs | 40 +++++----- .../patterns/RelationalLogicalPatterns.cs | 74 ++++++------------- 6 files changed, 76 insertions(+), 115 deletions(-) diff --git a/docs/csharp/fundamentals/patterns/list-patterns.md b/docs/csharp/fundamentals/patterns/list-patterns.md index 3ea8090e8f408..5057378b58454 100644 --- a/docs/csharp/fundamentals/patterns/list-patterns.md +++ b/docs/csharp/fundamentals/patterns/list-patterns.md @@ -17,17 +17,17 @@ List patterns don't make every ## Match an exact shape -A card reader receives command bytes from a device. A valid reset command contains exactly three bytes: a start marker, the reset operation code, and an end marker. The program must validate the complete command before resetting the device: +The following method recognizes a two-column header: :::code language="csharp" source="snippets/patterns/ListPatterns.cs" ID="ExactListPattern"::: -The `command` expression is the pattern input. `[0x02, 0x52, 0x03]` contains three constant patterns. Without a slice pattern, the length must be exactly three, and each nested pattern must match the element in the same position. A longer command doesn't match even if its first three bytes are the same. +The `columns` expression is the pattern input. `["Name", "Score"]` contains two constant patterns. Without a slice pattern, the length must be exactly two, and each nested pattern must match the element in the same position. A longer array doesn't match even if its first two elements are the same. Choose a list pattern when both the sequence shape and selected element values express the decision. If only the number of elements matters, a `Length` or `Count` property pattern, such as `items is { Count: 0 }`, states that intent more directly. ## Match selected elements with discards -A race application receives the finishing order as a list of runner names. It needs the winner and third-place finisher to prepare two separate announcements: +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"::: @@ -37,7 +37,7 @@ Choose this form when fixed positions have stable meaning. Use a loop or LINQ wh ## Allow remaining elements with a slice pattern -An application command line can start with `--verbose`, continue with zero or more other arguments, and end with the input file name. The program needs to recognize that shape and capture the file name: +A command line can start with `--verbose`, contain other arguments, and end with the input file name. The following method recognizes that shape and captures the file name: :::code language="csharp" source="snippets/patterns/ListPatterns.cs" ID="SlicePattern"::: @@ -47,11 +47,11 @@ A slice can appear at the beginning, middle, or end of a list pattern. Use it wh ## Apply a pattern to a slice -You can apply another pattern to the part matched by `..`. A message-processing service treats the first and last entries as protocol markers and needs to know whether the payload between them is empty: +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 `"BEGIN"` and `"END"` at the boundaries. The property pattern `{ Length: 0 }` then tests the slice between them. The result tells the service to reject an empty payload instead of sending it for processing. +The outer pattern first requires `"BEGIN"` and `"END"` at the boundaries. The property pattern `{ Length: > 0 }` then tests the slice between them. Use a slice subpattern only when the middle portion itself needs a test or capture. If only boundary elements matter, plain `..` is simpler. diff --git a/docs/csharp/fundamentals/patterns/property-positional-patterns.md b/docs/csharp/fundamentals/patterns/property-positional-patterns.md index 15979f6a10378..4762351f79ab1 100644 --- a/docs/csharp/fundamentals/patterns/property-positional-patterns.md +++ b/docs/csharp/fundamentals/patterns/property-positional-patterns.md @@ -17,22 +17,22 @@ Both are *recursive patterns*: Each member or position has its own nested patter ## Test named members with a property pattern -A warehouse uses `Package` objects to decide which packages need careful handling. The `Destination` property identifies where a package is going, and `WeightKg` records its weight. The following test identifies heavy international packages so the shipping system can add special handling instructions: +The following method 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 `package` expression is the input to the outer pattern. The pattern matches only when its evaluated value is non-null and both nested patterns match: +The `reading` expression is the input to the outer pattern. The pattern matches only when its evaluated value is non-null and both nested patterns match: -- The constant pattern `"International"` tests the value of the named `Destination` property. -- The relational pattern `> 20` tests the value of the named `WeightKg` property. +- The relational pattern `> 30` tests the value of `TemperatureC`. +- The relational pattern `> 70` tests the value of `HumidityPercent`. -Choose a property pattern when member names help explain the test. Unlike a series of Boolean expressions, the pattern groups the relevant shape and values in one description. For one simple comparison, such as `package.WeightKg > 20`, an ordinary relational expression is often clearer. +Choose a property pattern when member names help explain the test. Unlike a series of Boolean expressions, the pattern groups the relevant shape and values in one description. For one simple comparison, such as `reading.TemperatureC > 30`, an ordinary relational expression is often clearer. -You can add a type test before the braces when the input expression can produce different types. The following order system receives an `Order` base type. `StorePickup` and `ShippedOrder` are specialized order types. A shipped order has an `Address`, and the address has a `CountryCode`. The system needs to choose the team that fulfills each order: +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"::: -`ShippedOrder { Address.CountryCode: not "US" }` first tests that the evaluated value is a non-null `ShippedOrder`. It then follows the named `Address.CountryCode` member path. The pattern doesn't match if the outer value is `null`, if the type test fails, or if any object needed along that member path is `null`. Otherwise, the nested `not "US"` pattern tests the country code. This behavior makes the pattern suitable for selecting the correct fulfillment team without separate type, null, and member checks. +`DateTime { Date.DayOfWeek: DayOfWeek.Saturday or DayOfWeek.Sunday }` first tests that the evaluated value is a . It then follows the `Date.DayOfWeek` member path and tests the day against two constant patterns. The pattern doesn't match if the outer value is `null` or the type test fails. In general, a property pattern also doesn't match if an object needed along a member path is `null`. Choose named properties over positions when readers would need to memorize what each position means. @@ -40,7 +40,7 @@ Choose named properties over positions when readers would need to memorize what A *positional pattern* deconstructs a value and applies nested patterns in order. A type can define that order with a `Deconstruct` method. Positional records provide deconstruction automatically. -The following `GridPoint` record represents a location with an `X` coordinate followed by a `Y` coordinate. That two-value shape is intentional and familiar throughout the mapping code. The method classifies points so the map can choose where to draw their labels: +The following `GridPoint` record has an `X` coordinate followed by a `Y` coordinate. The method classifies a point by its position relative to the axes: :::code language="csharp" source="snippets/patterns/PropertyPositionalPatterns.cs" ID="PositionalPattern"::: @@ -50,11 +50,11 @@ The positions must follow the type's deconstruction order. Choose a positional p ## Match a tuple of related inputs -A tuple combines multiple values into one value with a fixed positional shape. A traffic controller considers the pedestrian signal and whether the crossing is clear. It needs both results to decide whether a person should cross: +A tuple combines multiple values into one value with a fixed positional shape. The following method uses a signal value and a Boolean value to choose one result: :::code language="csharp" source="snippets/patterns/PropertyPositionalPatterns.cs" ID="TuplePattern"::: -The tuple expression `(signal, crossingIsClear)` is the input. Each switch arm applies a positional pattern to both tuple elements. This form keeps the combinations and their consequential results together. +The tuple expression `(signal, crossingIsClear)` is the input. Each switch arm applies a positional pattern to both tuple elements. This form keeps each combination next to its result. Choose a tuple pattern when several small, related inputs jointly determine one result. If the positions need extensive explanation or the data belongs together throughout the program, define a type with named properties instead. diff --git a/docs/csharp/fundamentals/patterns/relational-logical-patterns.md b/docs/csharp/fundamentals/patterns/relational-logical-patterns.md index b6b8cd19113e1..5112e831af468 100644 --- a/docs/csharp/fundamentals/patterns/relational-logical-patterns.md +++ b/docs/csharp/fundamentals/patterns/relational-logical-patterns.md @@ -15,7 +15,7 @@ A *relational pattern* compares an evaluated value with a constant by using `<`, ## Distinguish expressions from patterns -Suppose a weather app receives the outdoor temperature as a whole number of degrees Celsius. The app needs to decide whether to show a freeze warning and to display a short description of the current conditions. The same relational symbol can appear in an ordinary expression or in a pattern: +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"::: @@ -23,31 +23,29 @@ Suppose a weather app receives the outdoor temperature as a whole number of degr 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 example displays both Boolean results to show that these two tests classify the same temperature. It uses the result from `temperature is < 0` to choose whether to show the warning. The string returned by the switch expression becomes the conditions description. +The example displays both Boolean results to show that the two tests classify the same temperature. The switch expression maps the value to a description. Choose a relational expression for one direct comparison. Choose relational patterns when the comparison is part of a larger pattern or when several ranges map cleanly to switch results. ## Describe ranges with `and` -A greenhouse controller has a preferred temperature range of 18 through 24 degrees Celsius, inclusive. When the temperature is in that range, the controller keeps the current airflow. Otherwise, it tells the ventilation system to adjust the airflow: +The following pattern tests whether a temperature is in the inclusive range from 18 through 24: :::code language="csharp" source="snippets/patterns/RelationalLogicalPatterns.cs" ID="AndPattern"::: The input expression is `temperature`. The logical pattern `>= 18 and <= 24` combines two relational patterns that both test the same evaluated value. The `and` pattern matches only when both nested patterns match. -`and` is a pattern operator here, not the conditional-AND Boolean operator `&&`. Don't read the syntax as a promise that nested patterns are tested left to right or short-circuit like Boolean operands. Pattern matching describes what must match; don't rely on the order in which nested patterns are tested. +`and` is a pattern operator here, not the conditional-AND Boolean operator `&&`. Pattern matching describes what must match. Don't rely on nested patterns being tested left to right or short-circuiting like Boolean operands. ## Describe alternatives with `or` and exclusions with `not` -A transit service uses named enum values for a service day. Weekend service follows a different timetable: +The following methods test a day of the week and a simple status value: :::code language="csharp" source="snippets/patterns/RelationalLogicalPatterns.cs" ID="OrNotPatterns"::: -`ServiceDay` is an enum that defines the named values used by the schedule. `ServiceDay.Saturday or ServiceDay.Sunday` is one logical pattern composed of two constant patterns. It matches when either nested pattern matches. The returned timetable name controls which schedule the app displays. +`DayOfWeek.Saturday or DayOfWeek.Sunday` is one logical pattern composed of two constant patterns. It matches when either nested pattern matches. `IsActive` uses the `not` pattern to exclude `Status.Complete`. -`ServiceStatus` represents the current operating condition of the transit service. An `Open` or `Limited` service can still accept trip-planning requests, but a `Closed` service can't. The `IsAvailable` result controls whether the app offers trip planning or shows that the service is unavailable. - -The `IsAvailable` method uses the `not` pattern to reject the `Closed` status. `not` is a pattern operator, not the Boolean negation operator `!`. Choose `or` when several pattern alternatives have the same result. Choose `not` when expressing the excluded pattern is clearer than listing every accepted value. +`not` is a pattern operator, not the Boolean negation operator `!`. 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 @@ -57,21 +55,21 @@ Pattern operators bind in this order: 1. `and` 1. `or` -Suppose a request queue supports priority levels 1 through 3 for ordinary requests. Dispatchers can use priority 9 as an override for an urgent request. The following test accepts either kind of supported priority: +The following test accepts priorities 1 through 3 or the special priority 9: :::code language="csharp" source="snippets/patterns/RelationalLogicalPatterns.cs" ID="ParenthesizedPattern"::: -The parentheses aren't required for the compiler because `and` binds before `or`, but they make the two alternatives visible: an ordinary priority range, or the override. The program uses the Boolean result to add a request to the queue or reject an unsupported priority. Use parentheses whenever a reader might hesitate over the grouping. Parentheses can also change the default grouping, such as `not (>= 1 and <= 3)`. +The parentheses aren't required for the compiler because `and` binds before `or`, but they make the two alternatives visible: the range from 1 through 3, or 9. Use parentheses whenever a reader might hesitate over the grouping. Parentheses can also change the default grouping, such as `not (>= 1 and <= 3)`. ## 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 that isn't naturally part of the pattern. -The following delivery price depends on the package weight and on a separate `isHoliday` setting: +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 `> 20` describes the `weightKg` input. The guard `when isHoliday` checks separate application state. A guard is also preferable when the condition needs a method call or a Boolean expression that pattern syntax doesn't express clearly. +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. ## See also diff --git a/docs/csharp/fundamentals/patterns/snippets/patterns/ListPatterns.cs b/docs/csharp/fundamentals/patterns/snippets/patterns/ListPatterns.cs index c5581fca8c580..9c3279df8e872 100644 --- a/docs/csharp/fundamentals/patterns/snippets/patterns/ListPatterns.cs +++ b/docs/csharp/fundamentals/patterns/snippets/patterns/ListPatterns.cs @@ -2,15 +2,17 @@ static class ListPatterns { public static void Run() { - Console.WriteLine(IsResetCommand([0x02, 0x52, 0x03])); + Console.WriteLine($"Header: {IsHeader(["Name", "Score"])}"); Console.WriteLine(GetAnnouncements(["Mina", "Luis", "Ada"])); - Console.WriteLine(GetInputFile(["--verbose", "--safe", "report.csv"])); - Console.WriteLine(ValidateMessage(["BEGIN", "END"])); + Console.WriteLine( + GetInputFile(["--verbose", "--safe", "report.csv"])); + Console.WriteLine( + $"Has content: {HasContent(["BEGIN", "value", "END"])}"); } // - static bool IsResetCommand(byte[] command) => - command is [0x02, 0x52, 0x03]; + static bool IsHeader(string[] columns) => + columns is ["Name", "Score"]; // // @@ -34,12 +36,7 @@ static string GetInputFile(string[] arguments) => // // - static string ValidateMessage(string[] entries) => - entries switch - { - ["BEGIN", .. { Length: 0 }, "END"] => "Reject empty payload", - ["BEGIN", .., "END"] => "Process payload", - _ => "Reject malformed message" - }; + static bool HasContent(string[] entries) => + entries is ["BEGIN", .. { Length: > 0 }, "END"]; // } diff --git a/docs/csharp/fundamentals/patterns/snippets/patterns/PropertyPositionalPatterns.cs b/docs/csharp/fundamentals/patterns/snippets/patterns/PropertyPositionalPatterns.cs index c66b31f953f6b..14016023b5d07 100644 --- a/docs/csharp/fundamentals/patterns/snippets/patterns/PropertyPositionalPatterns.cs +++ b/docs/csharp/fundamentals/patterns/snippets/patterns/PropertyPositionalPatterns.cs @@ -2,38 +2,32 @@ static class PropertyPositionalPatterns { public static void Run() { - Console.WriteLine(NeedsSpecialHandling( - new Package("International", 24))); - Console.WriteLine(SelectFulfillmentTeam( - new ShippedOrder(new Address("CA")))); - Console.WriteLine(ClassifyPoint(new GridPoint(0, 5))); - Console.WriteLine(GetCrossingInstruction( - PedestrianSignal.Walk, crossingIsClear: true)); + Console.WriteLine($"Hot and humid: {IsHotAndHumid( + new WeatherReading(32, 75))}"); + Console.WriteLine($"Date: {DescribeDate( + new DateTime(2026, 9, 19))}"); + Console.WriteLine($"Point: {ClassifyPoint(new GridPoint(0, 5))}"); + Console.WriteLine($"Crossing: {GetCrossingInstruction( + PedestrianSignal.Walk, crossingIsClear: true)}"); } // - static bool NeedsSpecialHandling(Package package) => - package is { Destination: "International", WeightKg: > 20 }; + static bool IsHotAndHumid(WeatherReading reading) => + reading is { TemperatureC: > 30, HumidityPercent: > 70 }; - sealed record Package(string Destination, decimal WeightKg); + sealed record WeatherReading(int TemperatureC, int HumidityPercent); // // - static string SelectFulfillmentTeam(Order? order) => - order switch + static string DescribeDate(object? value) => + value switch { - StorePickup => "Store team", - ShippedOrder { Address.CountryCode: not "US" } => - "International shipping team", - ShippedOrder => "Domestic shipping team", - null => "No order to fulfill", - _ => "Order review team" + DateTime { Date.DayOfWeek: + DayOfWeek.Saturday or DayOfWeek.Sunday } => "Weekend date", + DateTime => "Weekday date", + null => "No date", + _ => "Not a date" }; - - abstract record Order; - sealed record StorePickup : Order; - sealed record ShippedOrder(Address Address) : Order; - sealed record Address(string CountryCode); // // diff --git a/docs/csharp/fundamentals/patterns/snippets/patterns/RelationalLogicalPatterns.cs b/docs/csharp/fundamentals/patterns/snippets/patterns/RelationalLogicalPatterns.cs index c9f3a22d7a2e7..fa1c6e14dfe8d 100644 --- a/docs/csharp/fundamentals/patterns/snippets/patterns/RelationalLogicalPatterns.cs +++ b/docs/csharp/fundamentals/patterns/snippets/patterns/RelationalLogicalPatterns.cs @@ -3,11 +3,13 @@ static class RelationalLogicalPatterns public static void Run() { ShowExpressionAndPattern(-4); - Console.WriteLine(GetVentilationMode(21)); - Console.WriteLine(GetTimetable(ServiceDay.Saturday)); - Console.WriteLine(GetServiceAction(ServiceStatus.Limited)); - Console.WriteLine(GetPriorityAction(9)); - Console.WriteLine(GetShippingPrice(22, isHoliday: true)); + Console.WriteLine( + $"Comfortable temperature: {IsComfortableTemperature(21)}"); + Console.WriteLine($"Weekend: {IsWeekend(DayOfWeek.Saturday)}"); + Console.WriteLine($"Active status: {IsActive(Status.Pending)}"); + Console.WriteLine($"Accepted priority: {IsAcceptedPriority(9)}"); + Console.WriteLine( + $"Heat warning: {GetHeatWarning(36, isOutdoors: true)}"); } // @@ -23,74 +25,44 @@ static void ShowExpressionAndPattern(int temperature) > 0 => "Above freezing" }; - string warning = freezeWarningFromPattern - ? "Show freeze warning" - : "No freeze warning"; - Console.WriteLine( $"Expression: {freezeWarningFromExpression}; " + - $"pattern: {freezeWarningFromPattern}; {description}; {warning}"); + $"pattern: {freezeWarningFromPattern}; {description}"); } // // - static string GetVentilationMode(int temperature) => - temperature is >= 18 and <= 24 - ? "Keep current airflow" - : "Adjust airflow"; + static bool IsComfortableTemperature(int temperature) => + temperature is >= 18 and <= 24; // // - static string GetTimetable(ServiceDay day) => - day is ServiceDay.Saturday or ServiceDay.Sunday - ? "Weekend timetable" - : "Weekday timetable"; - - static bool IsAvailable(ServiceStatus status) => - status is not ServiceStatus.Closed; + static bool IsWeekend(DayOfWeek day) => + day is DayOfWeek.Saturday or DayOfWeek.Sunday; - static string GetServiceAction(ServiceStatus status) => - IsAvailable(status) - ? "Offer trip planning" - : "Show service unavailable"; + static bool IsActive(Status status) => + status is not Status.Complete; - enum ServiceDay + enum Status { - Monday, - Tuesday, - Wednesday, - Thursday, - Friday, - Saturday, - Sunday - } - - enum ServiceStatus - { - Open, - Limited, - Closed + Pending, + Running, + Complete } // // static bool IsAcceptedPriority(int priority) => priority is (>= 1 and <= 3) or 9; - - static string GetPriorityAction(int priority) => - IsAcceptedPriority(priority) - ? "Add request to queue" - : "Reject unsupported priority"; // // - static decimal GetShippingPrice(decimal weightKg, bool isHoliday) => - weightKg switch + static string GetHeatWarning(int temperature, bool isOutdoors) => + temperature switch { - > 20 when isHoliday => 45.00m, - > 20 => 35.00m, - _ when isHoliday => 20.00m, - _ => 15.00m + > 35 when isOutdoors => "High heat outdoors", + > 35 => "High heat", + _ => "No heat warning" }; // }