diff --git a/.openpublishing.redirection.csharp.json b/.openpublishing.redirection.csharp.json index 1ef014a0ab376..fce48dc377616 100644 --- a/.openpublishing.redirection.csharp.json +++ b/.openpublishing.redirection.csharp.json @@ -38,7 +38,7 @@ }, { "source_path_from_root": "/docs/csharp/discards.md", - "redirect_url": "/dotnet/csharp/fundamentals/functional/discards" + "redirect_url": "/dotnet/csharp/fundamentals/patterns/discards" }, { "source_path_from_root": "/docs/csharp/expression-classes.md", @@ -76,6 +76,14 @@ "source_path_from_root": "/docs/csharp/features.md", "redirect_url": "/dotnet/csharp/programming-guide/concepts" }, + { + "source_path_from_root": "/docs/csharp/fundamentals/functional/discards.md", + "redirect_url": "/dotnet/csharp/fundamentals/patterns/discards" + }, + { + "source_path_from_root": "/docs/csharp/fundamentals/functional/pattern-matching.md", + "redirect_url": "/dotnet/csharp/fundamentals/patterns/pattern-matching" + }, { "source_path_from_root": "/docs/csharp/fundamentals/null-safety/migration-strategies.md", "redirect_url": "/dotnet/csharp/advanced-topics/update-applications/nullable-migration-strategies" @@ -3370,7 +3378,7 @@ }, { "source_path_from_root": "/docs/csharp/pattern-matching.md", - "redirect_url": "/dotnet/csharp/fundamentals/functional/pattern-matching" + "redirect_url": "/dotnet/csharp/fundamentals/patterns/pattern-matching" }, { "source_path_from_root": "/docs/csharp/programming-guide/arrays/arrays-as-objects.md", diff --git a/docs/csharp/advanced-topics/expression-trees/index.md b/docs/csharp/advanced-topics/expression-trees/index.md index 8dfa292eb08cd..d21593f1c863f 100644 --- a/docs/csharp/advanced-topics/expression-trees/index.md +++ b/docs/csharp/advanced-topics/expression-trees/index.md @@ -64,6 +64,6 @@ Expression trees don't support new expression node types. It would be a breaking - Expressions using or , [index "from end" (`^`) operator](../../language-reference/operators/member-access-operators.md#index-from-end-operator-) or [range expressions (`..`)](../../language-reference/operators/member-access-operators.md#range-operator-) - [`async` lambda expressions or `await` expressions](../../language-reference/operators/lambda-expressions.md#async-lambdas), including [`await foreach` and `await using`](../../language-reference/operators/await.md#asynchronous-streams-and-disposables) - [Tuple literals, tuple conversions, tuple `==` or `!=`, or `with` expressions](../../language-reference/builtin-types/value-tuples.md) -- [Discards (`_`)](../../fundamentals/functional/discards.md), [deconstructing assignment](../../fundamentals/functional/deconstruct.md), [pattern matching `is` operator, or the pattern matching `switch` expression](../../language-reference/operators/patterns.md) +- [Discards (`_`)](../../fundamentals/patterns/discards.md), [deconstructing assignment](../../fundamentals/functional/deconstruct.md), [pattern matching `is` operator, or the pattern matching `switch` expression](../../language-reference/operators/patterns.md) - COM call with `ref` omitted on the arguments - [`ref`](../../language-reference/keywords/ref.md), [`in`](../../language-reference/keywords/method-parameters.md#in-parameter-modifier) or [`out`](../../language-reference/keywords/method-parameters.md#out-parameter-modifier) parameters, `ref` return values, `out` arguments, or any values of [`ref struct` type](../../language-reference/builtin-types/ref-struct.md) diff --git a/docs/csharp/advanced-topics/interface-implementation/mixins-with-default-interface-methods.md b/docs/csharp/advanced-topics/interface-implementation/mixins-with-default-interface-methods.md index 3a93c9ac0c83a..1df299e0b9bff 100644 --- a/docs/csharp/advanced-topics/interface-implementation/mixins-with-default-interface-methods.md +++ b/docs/csharp/advanced-topics/interface-implementation/mixins-with-default-interface-methods.md @@ -96,7 +96,7 @@ The `HalogenLight` you created earlier doesn't support blinking. So, don't add t ## Detect the light types using pattern matching -Next, let's write some test code. You can make use of C#'s [pattern matching](../../fundamentals/functional/pattern-matching.md) feature to determine a light's capabilities by examining which interfaces it supports. The following method exercises the supported capabilities of each light: +Next, let's write some test code. You can make use of C#'s [pattern matching](../../fundamentals/patterns/pattern-matching.md) feature to determine a light's capabilities by examining which interfaces it supports. The following method exercises the supported capabilities of each light: :::code language="csharp" source="./snippets/mixins-with-default-interface-methods/Program.cs" id="SnippetTestLightFunctions"::: diff --git a/docs/csharp/fundamentals/expressions/index.md b/docs/csharp/fundamentals/expressions/index.md index 542c2d47b6185..55985749af843 100644 --- a/docs/csharp/fundamentals/expressions/index.md +++ b/docs/csharp/fundamentals/expressions/index.md @@ -105,4 +105,5 @@ For a broader look at null-safe operators, see [C# null operators](../null-safet - [Arithmetic, comparison, logical, and assignment operators](operators.md) — the everyday operators in depth - [Equality comparisons](equality.md) — how `==`, `!=`, and `Equals` work - [C# null operators](../null-safety/null-operators.md) — `?.`, `??`, and `??=` +- [Pattern matching](../patterns/pattern-matching.md) — use a `switch` expression to select a value by matching the type, value, or shape of data - [Boolean logical operators](../../language-reference/operators/boolean-logical-operators.md) diff --git a/docs/csharp/fundamentals/functional/deconstruct.md b/docs/csharp/fundamentals/functional/deconstruct.md index f6018ebec7e8c..e9fcc280e460d 100644 --- a/docs/csharp/fundamentals/functional/deconstruct.md +++ b/docs/csharp/fundamentals/functional/deconstruct.md @@ -107,5 +107,5 @@ When you declare a [record](../../language-reference/builtin-types/record.md) ty ## See also - [Deconstruct variable declaration (style rule IDE0042)](../../../fundamentals/code-analysis/style-rules/ide0042.md) -- [Discards](discards.md) +- [Discards](../patterns/discards.md) - [Tuple types](../../language-reference/builtin-types/value-tuples.md) diff --git a/docs/csharp/fundamentals/functional/discards.md b/docs/csharp/fundamentals/functional/discards.md deleted file mode 100644 index 34e632203530b..0000000000000 --- a/docs/csharp/fundamentals/functional/discards.md +++ /dev/null @@ -1,85 +0,0 @@ ---- -title: Discards - unassigned discardable variables -description: Describes C#'s support for discards, which are unassigned, discardable variables, and the ways in which discards can be used. -ms.date: 02/19/2025 -f1_keywords: - - "discard_CSharpKeyword" ---- -# Discards - C# Fundamentals - -Discards are placeholder variables that are intentionally unused in application code. Discards are equivalent to unassigned variables; they don't have a value. A discard communicates intent to the compiler and others that read your code: You intended to ignore the result of an expression. You may want to ignore the result of an expression, one or more members of a tuple expression, an `out` parameter to a method, or the target of a pattern matching expression. - -Discards make the intent of your code clear. A discard indicates that our code never uses the variable. They enhance its readability and maintainability. - -You indicate that a variable is a discard by assigning it the underscore (`_`) as its name. For example, the following method call returns a tuple in which the first and second values are discards. `area` is a previously declared variable set to the third component returned by `GetCityInformation`: - -```csharp -(_, _, area) = city.GetCityInformation(cityName); -``` - -You can use discards to specify unused input parameters of a lambda expression. For more information, see the [Input parameters of a lambda expression](../../language-reference/operators/lambda-expressions.md#input-parameters-of-a-lambda-expression) section of the [Lambda expressions](../../language-reference/operators/lambda-expressions.md) article. - -When `_` is a valid discard, attempting to retrieve its value or use it in an assignment operation generates compiler error CS0103, "The name '\_' doesn't exist in the current context". This error is because `_` isn't assigned a value, and may not even be assigned a storage location. If it were an actual variable, you couldn't discard more than one value, as the previous example did. - -## Tuple and object deconstruction - -Discards are useful in working with tuples when your application code uses some tuple elements but ignores others. For example, the following `QueryCityDataForYears` method returns a tuple with the name of a city, its area, a year, the city's population for that year, a second year, and the city's population for that second year. The example shows the change in population between those two years. Of the data available from the tuple, we're unconcerned with the city area, and we know the city name and the two dates at design-time. As a result, we're only interested in the two population values stored in the tuple, and can handle its remaining values as discards. - -:::code language="csharp" source="snippets/discards/discard-tuple.cs" ID="DiscardTupleMember" ::: - -For more information on deconstructing tuples with discards, see [Deconstructing tuples and other types](deconstruct.md#tuple-elements-with-discards). - -The `Deconstruct` method of a class, structure, or interface also allows you to retrieve and deconstruct a specific set of data from an object. You can use discards when you're interested in working with only a subset of the deconstructed values. The following example deconstructs a `Person` object into four strings (the first and last names, the city, and the state), but discards the last name and the state. - -:::code language="csharp" source="snippets/discards/discard-class.cs" ::: - -For more information on deconstructing user-defined types with discards, see [Deconstructing tuples and other types](deconstruct.md#user-defined-type-with-discards). - -## Pattern matching with `switch` - -The *discard pattern* can be used in pattern matching with the [switch expression](../../language-reference/operators/switch-expression.md). Every expression, including `null`, always matches the discard pattern. - -The following example defines a `ProvidesFormatInfo` method that uses a `switch` expression to determine whether an object provides an implementation and tests whether the object is `null`. It also uses the discard pattern to handle non-null objects of any other type. - -:::code language="csharp" source="snippets/discards/discard-pattern2.cs" ID="DiscardSwitchExample" ::: - -## Calls to methods with `out` parameters - -When calling the `Deconstruct` method to deconstruct a user-defined type (an instance of a class, structure, or interface), you can discard the values of individual `out` arguments. But you can also discard the value of `out` arguments when calling any method with an `out` parameter. - -The following example calls the [DateTime.TryParse(String, out DateTime)]() method to determine whether the string representation of a date is valid in the current culture. Because the example is concerned only with validating the date string and not with parsing it to extract the date, the `out` argument to the method is a discard. - -:::code language="csharp" source="snippets/discards/discard-out1.cs" ID="DiscardOutParameter" ::: - -## A standalone discard - -You can use a standalone discard to indicate any variable that you choose to ignore. One typical use is to use an assignment to ensure that an argument isn't null. The following code uses a discard to force an assignment. The right side of the assignment uses the [null coalescing operator](../../language-reference/operators/null-coalescing-operator.md) to throw an when the argument is `null`. The code doesn't need the result of the assignment, so it's discarded. The expression forces a null check. The discard clarifies your intent: the result of the assignment isn't needed or used. - -:::code language="csharp" source="snippets/discards/standalone-discard1.cs" ID="ArgNullCheck" ::: - -The following example uses a standalone discard to ignore the object returned by an asynchronous operation. Assigning the task has the effect of suppressing the compiler warning about unobserved exceptions. It makes your intent clear: You want to discard the `Task`, and propagate any errors generated from that asynchronous operation to callers. - -:::code language="csharp" source="snippets/discards/standalone-discard1.cs" ID="SnippetDiscardTask" ::: - -Without assigning the task to a discard, the following code generates a compiler warning: - -:::code language="csharp" source="snippets/discards/standalone-discard1.cs" ID="SnippetNoDiscardTask" ::: - -> [!NOTE] -> If you run either of the preceding two samples using a debugger, the debugger will stop the program when the exception is thrown. Without a debugger attached, the exception is silently ignored in both cases. - -`_` is also a valid identifier. When used outside of a supported context, `_` is treated not as a discard but as a valid variable. If an identifier named `_` is already in scope, the use of `_` as a standalone discard can result in: - -- Accidental modification of the value of the in-scope `_` variable by assigning it the value of the intended discard. For example: - :::code language="csharp" source="snippets/discards/standalone-discard2.cs" ID="VariableIdentifier" ::: -- A compiler error for violating type safety. For example: - :::code language="csharp" source="snippets/discards/standalone-discard2.cs" ID="VariableTypeInference" ::: - -## See also - -- [Remove unnecessary expression value (style rule IDE0058)](../../../fundamentals/code-analysis/style-rules/ide0058.md) -- [Remove unnecessary value assignment (style rule IDE0059)](../../../fundamentals/code-analysis/style-rules/ide0059.md) -- [Remove unused parameter (style rule IDE0060)](../../../fundamentals/code-analysis/style-rules/ide0060.md) -- [Deconstructing tuples and other types](deconstruct.md) -- [`is` operator](../../language-reference/operators/is.md) -- [`switch` expression](../../language-reference/operators/switch-expression.md) diff --git a/docs/csharp/fundamentals/functional/pattern-matching.md b/docs/csharp/fundamentals/functional/pattern-matching.md deleted file mode 100644 index 7653f75c88137..0000000000000 --- a/docs/csharp/fundamentals/functional/pattern-matching.md +++ /dev/null @@ -1,123 +0,0 @@ ---- -title: Pattern matching overview -description: "Learn about pattern matching expressions in C#" -ms.date: 11/18/2025 ---- - -# Pattern matching overview - -*Pattern matching* is a technique where you test an expression to determine if it has certain characteristics. C# pattern matching provides more concise syntax for testing expressions and taking action when an expression matches. The "[`is`](../../language-reference/operators/is.md) expression" supports pattern matching to test an expression and conditionally declare a new variable to the result of that expression. The "[`switch`](../../language-reference/operators/switch-expression.md) expression" enables you to perform actions based on the first matching pattern for an expression. These two expressions support a rich vocabulary of [*patterns*](../../language-reference/operators/patterns.md). - -This article provides an overview of scenarios where you can use pattern matching. These techniques can improve the readability and correctness of your code. For a full discussion of all the patterns you can apply, see the article on [patterns](../../language-reference/operators/patterns.md) in the language reference. - -## Null checks - -One of the most common scenarios for pattern matching is to ensure values aren't `null`. You can test and convert a nullable value type to its underlying type while testing for `null` using the following example: - -:::code language="csharp" source="snippets/patterns/Program.cs" ID="NullableCheck"::: - -The preceding code is a [*declaration pattern*](../../language-reference/operators/patterns.md#declaration-and-type-patterns) to test the type of the variable, and assign it to a new variable. The language rules make this technique safer than many others. The variable `number` is only accessible and assigned in the true portion of the `if` clause. If you try to access it elsewhere, either in the `else` clause, or after the `if` block, the compiler issues an error. Secondly, because you're not using the `==` operator, this pattern works when a type overloads the `==` operator. That makes it an ideal way to check null reference values, adding the `not` pattern: - -:::code language="csharp" source="snippets/patterns/Program.cs" ID="NullReferenceCheck"::: - -The preceding example used a [*constant pattern*](../../language-reference/operators/patterns.md#constant-pattern) to compare the variable to `null`. The `not` is a [*logical pattern*](../../language-reference/operators/patterns.md#logical-patterns) that matches when the negated pattern doesn't match. - -## Type tests - -Another common use for pattern matching is to test a variable to see if it matches a given type. For example, the following code tests if a variable is non-null and implements the interface. If it does, it uses the property on that list to find the middle index. The declaration pattern doesn't match a `null` value, regardless of the compile-time type of the variable. The code below guards against `null`, in addition to guarding against a type that doesn't implement `IList`. - -:::code language="csharp" source="snippets/patterns/Program.cs" ID="MidPoint"::: - -The same tests can be applied in a `switch` expression to test a variable against multiple different types. You can use that information to create better algorithms based on the specific run-time type. - -## Compare discrete values - -You can also test a variable to find a match on specific values. The following code shows one example where you test a value against all possible values declared in an enumeration: - -:::code language="csharp" source="snippets/patterns/Simulation.cs" ID="PerformOperation"::: - -The previous example demonstrates a method dispatch based on the value of an enumeration. The final `_` case is a [*discard pattern*](../../language-reference/operators/patterns.md#discard-pattern) that matches all values. It handles any error conditions where the value doesn't match one of the defined `enum` values. If you omit that switch arm, the compiler warns that your pattern expression doesn't handle all possible input values. At run time, the `switch` expression throws an exception if the object being examined doesn't match any of the switch arms. You could use numeric constants instead of a set of enum values. You can also use this similar technique for constant string values that represent the commands: - -:::code language="csharp" source="snippets/patterns/Simulation.cs" ID="PerformStringOperation"::: - -The preceding example shows the same algorithm, but uses string values instead of an enum. You would use this scenario if your application responds to text commands instead of a regular data format. You can also use a `Span` or a `ReadOnlySpan`to test for constant string values, as shown in the following sample: - -:::code language="csharp" source="snippets/patterns/Simulation.cs" ID="PerformSpanOperation"::: - -In all these examples, the *discard pattern* ensures that you handle every input. The compiler helps you by making sure every possible input value is handled. - -## Relational patterns - -You can use [*relational patterns*](../../language-reference/operators/patterns.md#relational-patterns) to test how a value compares to constants. For example, the following code returns the state of water based on the temperature in Fahrenheit: - -:::code language="csharp" source="snippets/patterns/Simulation.cs" ID="RelationalPattern"::: - -The preceding code also demonstrates the conjunctive `and` [*logical pattern*](../../language-reference/operators/patterns.md#logical-patterns) to check that both relational patterns match. You can also use a disjunctive `or` pattern to check that either pattern matches. The two relational patterns are surrounded by parentheses, which you can use around any pattern for clarity. The two explicit switch arms (32°F and 212°F) handle the cases for the melting point and the boiling point. Without those two arms, the compiler warns you that your logic doesn't cover every possible input. - -The preceding code also demonstrates another important feature the compiler provides for pattern matching expressions: The compiler warns you if you don't handle every input value. The compiler also issues a warning if the pattern for a switch arm is covered by a previous pattern. That gives you freedom to refactor and reorder switch expressions. Another way to write the same expression could be: - -:::code language="csharp" source="snippets/patterns/Simulation.cs" ID="RelationalPattern2"::: - -The key lesson in the preceding sample, and any other refactoring or reordering, is that the compiler validates that your code handles all possible inputs. - -## Multiple inputs - -All the patterns covered so far have been checking one input. You can write patterns that examine multiple properties of an object. Consider the following `Order` record: - -:::code language="csharp" source="snippets/patterns/OrderProcessor.cs" ID="OrderRecord"::: - -The preceding positional record type declares two members at explicit positions. Appearing first is the `Items`, then the order's `Cost`. For more information, see [Records](../../language-reference/builtin-types/record.md). - -The following code examines the number of items and the value of an order to calculate a discounted price: - -:::code language="csharp" source="snippets/patterns/OrderProcessor.cs" ID="PropertyPattern"::: - -The first two arms examine two properties of the `Order`. The third examines only the cost. The next checks against `null`, and the final matches any other value. If the `Order` type defines a suitable [`Deconstruct`](deconstruct.md) method, you can omit the property names from the pattern and use deconstruction to examine properties: - -:::code language="csharp" source="snippets/patterns/OrderProcessor.cs" ID="DeconstructPattern"::: - -The preceding code demonstrates the [*positional pattern*](../../language-reference/operators/patterns.md#positional-pattern) where the properties are deconstructed for the expression. - -You can also match a property against `{ }`, which matches any non-null value. Consider the following declaration, which stores measurements with an optional annotation: - -:::code language="csharp" source="snippets/patterns/Program.cs" ID="Observation"::: - -You can test if a given observation has a non-null annotation using the following pattern matching expression: - -:::code language="csharp" source="snippets/patterns/Program.cs" ID="NotNullPropertyPattern"::: - -## List patterns - -You can check elements in a list or an array using a *list pattern*. A [list pattern](../../language-reference/operators/patterns.md#list-patterns) provides a means to apply a pattern to any element of a sequence. In addition, you can apply the *discard pattern* (`_`) to match any element, or apply a *slice pattern* to match zero or more elements. - -List patterns are a valuable tool when data doesn't follow a regular structure. You can use pattern matching to test the shape and values of the data instead of transforming it into a set of objects. - -Consider the following excerpt from a text file containing bank transactions: - -```output -04-01-2020, DEPOSIT, Initial deposit, 2250.00 -04-15-2020, DEPOSIT, Refund, 125.65 -04-18-2020, DEPOSIT, Paycheck, 825.65 -04-22-2020, WITHDRAWAL, Debit, Groceries, 255.73 -05-01-2020, WITHDRAWAL, #1102, Rent, apt, 2100.00 -05-02-2020, INTEREST, 0.65 -05-07-2020, WITHDRAWAL, Debit, Movies, 12.57 -04-15-2020, FEE, 5.55 -``` - -It's a CSV format, but some of the rows have more columns than others. Even worse for processing, one column in the `WITHDRAWAL` type contains user-generated text and can contain a comma in the text. A *list pattern* that includes the *discard* pattern, *constant* pattern, and *var* pattern to capture the value processes data in this format: - -:::code language="csharp" source="snippets/patterns/ListPattern.cs" id="ListPattern"::: - -The preceding example takes a string array, where each element is one field in the row. The `switch` expression keys on the second field, which determines the kind of transaction, and the number of remaining columns. Each row ensures the data is in the correct format. The discard pattern (`_`) skips the first field, with the date of the transaction. The second field matches the type of transaction. Remaining element matches skip to the field with the amount. The final match uses the *var* pattern to capture the string representation of the amount. The expression calculates the amount to add or subtract from the balance. - -*List patterns* enable you to match on the shape of a sequence of data elements. You use the *discard* and *slice* patterns to match the location of elements. You use other patterns to match characteristics about individual elements. - -This article provided a tour of the kinds of code you can write with pattern matching in C#. The following articles show more examples of using patterns in scenarios, and the full vocabulary of patterns available to use. - -## See also - -- [Use pattern matching to avoid 'is' check followed by a cast (style rules IDE0020 and IDE0038)](../../../fundamentals/code-analysis/style-rules/ide0020-ide0038.md) -- [Exploration: Use pattern matching to build your class behavior for better code](../../tutorials/patterns-objects.md) -- [Tutorial: Use pattern matching to build type-driven and data-driven algorithms](../tutorials/pattern-matching.md) -- [Reference: Pattern matching](../../language-reference/operators/patterns.md) diff --git a/docs/csharp/fundamentals/functional/snippets/discards/Program.cs b/docs/csharp/fundamentals/functional/snippets/discards/Program.cs deleted file mode 100644 index b64804c52c8ec..0000000000000 --- a/docs/csharp/fundamentals/functional/snippets/discards/Program.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System; -using System.Threading.Tasks; - -namespace Discards -{ - class Program - { - static async Task Main(string[] args) - { - DiscardPatternMatching.DiscardSwitchExample(); - DiscardExamples.DiscardOutParameter(); - await AsyncExample.TaskDiscard(); - DiscardOrVariable.DiscardVariables(); - Example.Main(); - } - } -} diff --git a/docs/csharp/fundamentals/functional/snippets/discards/discard-class.cs b/docs/csharp/fundamentals/functional/snippets/discards/discard-class.cs deleted file mode 100644 index 74771203b1ef9..0000000000000 --- a/docs/csharp/fundamentals/functional/snippets/discards/discard-class.cs +++ /dev/null @@ -1,59 +0,0 @@ -using System; - -namespace Discards -{ - public class Person - { - public string FirstName { get; set; } - public string MiddleName { get; set; } - public string LastName { get; set; } - public string City { get; set; } - public string State { get; set; } - - public Person(string fname, string mname, string lname, - string cityName, string stateName) - { - FirstName = fname; - MiddleName = mname; - LastName = lname; - City = cityName; - State = stateName; - } - - // Return the first and last name. - public void Deconstruct(out string fname, out string lname) - { - fname = FirstName; - lname = LastName; - } - - public void Deconstruct(out string fname, out string mname, out string lname) - { - fname = FirstName; - mname = MiddleName; - lname = LastName; - } - - public void Deconstruct(out string fname, out string lname, - out string city, out string state) - { - fname = FirstName; - lname = LastName; - city = City; - state = State; - } - } - class Example - { - public static void Main() - { - var p = new Person("John", "Quincy", "Adams", "Boston", "MA"); - - // Deconstruct the person object. - var (fName, _, city, _) = p; - Console.WriteLine($"Hello {fName} of {city}!"); - // The example displays the following output: - // Hello John of Boston! - } - } -} diff --git a/docs/csharp/fundamentals/functional/snippets/discards/discard-out1.cs b/docs/csharp/fundamentals/functional/snippets/discards/discard-out1.cs deleted file mode 100644 index acf90971e38aa..0000000000000 --- a/docs/csharp/fundamentals/functional/snippets/discards/discard-out1.cs +++ /dev/null @@ -1,31 +0,0 @@ -using System; - -public class DiscardExamples -{ - public static void DiscardOutParameter() - { - // - string[] dateStrings = ["05/01/2018 14:57:32.8", "2018-05-01 14:57:32.8", - "2018-05-01T14:57:32.8375298-04:00", "5/01/2018", - "5/01/2018 14:57:32.80 -07:00", - "1 May 2018 2:57:32.8 PM", "16-05-2018 1:00:32 PM", - "Fri, 15 May 2018 20:10:57 GMT"]; - foreach (string dateString in dateStrings) - { - if (DateTime.TryParse(dateString, out _)) - Console.WriteLine($"'{dateString}': valid"); - else - Console.WriteLine($"'{dateString}': invalid"); - } - // The example displays output like the following: - // '05/01/2018 14:57:32.8': valid - // '2018-05-01 14:57:32.8': valid - // '2018-05-01T14:57:32.8375298-04:00': valid - // '5/01/2018': valid - // '5/01/2018 14:57:32.80 -07:00': valid - // '1 May 2018 2:57:32.8 PM': valid - // '16-05-2018 1:00:32 PM': invalid - // 'Fri, 15 May 2018 20:10:57 GMT': invalid - // - } -} diff --git a/docs/csharp/fundamentals/functional/snippets/discards/discard-pattern2.cs b/docs/csharp/fundamentals/functional/snippets/discards/discard-pattern2.cs deleted file mode 100644 index 0e66fb68e8e59..0000000000000 --- a/docs/csharp/fundamentals/functional/snippets/discards/discard-pattern2.cs +++ /dev/null @@ -1,33 +0,0 @@ -using System; -using System.Globalization; -using System.IO; -using System.Threading.Tasks; - -public class DiscardPatternMatching -{ - public static void DiscardSwitchExample() - { - // - object?[] objects = [CultureInfo.CurrentCulture, - CultureInfo.CurrentCulture.DateTimeFormat, - CultureInfo.CurrentCulture.NumberFormat, - new ArgumentException(), null]; - foreach (var obj in objects) - ProvidesFormatInfo(obj); - - static void ProvidesFormatInfo(object? obj) => - Console.WriteLine(obj switch - { - IFormatProvider fmt => $"{fmt.GetType()} object", - null => "A null object reference: Its use could result in a NullReferenceException", - _ => "Some object type without format information" - }); - // The example displays the following output: - // System.Globalization.CultureInfo object - // System.Globalization.DateTimeFormatInfo object - // System.Globalization.NumberFormatInfo object - // Some object type without format information - // A null object reference: Its use could result in a NullReferenceException - // - } -} diff --git a/docs/csharp/fundamentals/functional/snippets/discards/discard-tuple.cs b/docs/csharp/fundamentals/functional/snippets/discards/discard-tuple.cs deleted file mode 100644 index 1c656608c1654..0000000000000 --- a/docs/csharp/fundamentals/functional/snippets/discards/discard-tuple.cs +++ /dev/null @@ -1,40 +0,0 @@ -using System; - -namespace Discards -{ - class DiscardTuples - { - public static void DiscardTupleMember() - { - // - var (_, _, _, pop1, _, pop2) = QueryCityDataForYears("New York City", 1960, 2010); - - Console.WriteLine($"Population change, 1960 to 2010: {pop2 - pop1:N0}"); - - static (string, double, int, int, int, int) QueryCityDataForYears(string name, int year1, int year2) - { - int population1 = 0, population2 = 0; - double area = 0; - - if (name == "New York City") - { - area = 468.48; - if (year1 == 1960) - { - population1 = 7781984; - } - if (year2 == 2010) - { - population2 = 8175133; - } - return (name, area, year1, population1, year2, population2); - } - - return ("", 0, 0, 0, 0, 0); - } - // The example displays the following output: - // Population change, 1960 to 2010: 393,149 - // - } - } -} diff --git a/docs/csharp/fundamentals/functional/snippets/discards/standalone-discard1.cs b/docs/csharp/fundamentals/functional/snippets/discards/standalone-discard1.cs deleted file mode 100644 index c44e7f0b82fd7..0000000000000 --- a/docs/csharp/fundamentals/functional/snippets/discards/standalone-discard1.cs +++ /dev/null @@ -1,64 +0,0 @@ - -using System; -using System.Threading.Tasks; - -public class AsyncExample -{ - public static async Task TaskDiscard() - { - await ExecuteAsyncMethods(); - } - - // - public static void Method(string arg) - { - _ = arg ?? throw new ArgumentNullException(paramName: nameof(arg), message: "arg can't be null"); - - // Do work with arg. - } - // - - // - private static async Task ExecuteAsyncMethods() - { - Console.WriteLine("About to launch a task..."); - _ = Task.Run(() => - { - var iterations = 0; - for (int ctr = 0; ctr < int.MaxValue; ctr++) - iterations++; - Console.WriteLine("Completed looping operation..."); - throw new InvalidOperationException(); - }); - await Task.Delay(5000); - Console.WriteLine("Exiting after 5 second delay"); - } - // The example displays output like the following: - // About to launch a task... - // Completed looping operation... - // Exiting after 5 second delay - // -} - -public class Unused -{ - // - private static async Task ExecuteAsyncMethods() - { - Console.WriteLine("About to launch a task..."); - // CS4014: Because this call is not awaited, execution of the current method continues before the call is completed. - // Consider applying the 'await' operator to the result of the call. - Task.Run(() => - { - var iterations = 0; - for (int ctr = 0; ctr < int.MaxValue; ctr++) - iterations++; - Console.WriteLine("Completed looping operation..."); - throw new InvalidOperationException(); - }); - await Task.Delay(5000); - Console.WriteLine("Exiting after 5 second delay"); - // - } - -} diff --git a/docs/csharp/fundamentals/functional/snippets/discards/standalone-discard2.cs b/docs/csharp/fundamentals/functional/snippets/discards/standalone-discard2.cs deleted file mode 100644 index 9a37a90de38c5..0000000000000 --- a/docs/csharp/fundamentals/functional/snippets/discards/standalone-discard2.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System; - -public class DiscardOrVariable -{ - public static void DiscardVariables() - { - int value = 3; - ShowValue(value); - } - - // - private static void ShowValue(int _) - { - byte[] arr = [0, 0, 1, 2]; - _ = BitConverter.ToInt32(arr, 0); - Console.WriteLine(_); - } - // The example displays the following output: - // 33619968 - // - - /* - // - private static bool RoundTrips(int _) - { - string value = _.ToString(); - int newValue = 0; - _ = Int32.TryParse(value, out newValue); - return _ == newValue; - } - // The example displays the following compiler error: - // error CS0029: Cannot implicitly convert type 'bool' to 'int' - // - - */ -} diff --git a/docs/csharp/fundamentals/functional/snippets/patterns/ListPattern.cs b/docs/csharp/fundamentals/functional/snippets/patterns/ListPattern.cs deleted file mode 100644 index 6094b550315cb..0000000000000 --- a/docs/csharp/fundamentals/functional/snippets/patterns/ListPattern.cs +++ /dev/null @@ -1,42 +0,0 @@ -namespace patterns; - -static class ListPattern -{ - static string[] s_records = - { - "04-01-2020, DEPOSIT, Initial deposit, 2250.00", - "04-15-2020, DEPOSIT, Refund, 125.65", - "04-18-2020, DEPOSIT, Paycheck, 125.65", - "04-22-2020, WITHDRAWAL, Debit, Groceries, 255.73", - "05-01-2020, WITHDRAWAL, #1102, Rent, apt, 2100.00", - "05-02-2020, INTEREST, 0.65", - "05-07-2020, WITHDRAWAL, Debit, Movies, 12.57", - "04-15-2020, FEE, 5.55", - }; - - - public static void Example() - { - // - decimal balance = 0m; - foreach (string[] transaction in ReadRecords()) - { - balance += transaction switch - { - [_, "DEPOSIT", _, var amount] => decimal.Parse(amount), - [_, "WITHDRAWAL", .., var amount] => -decimal.Parse(amount), - [_, "INTEREST", var amount] => decimal.Parse(amount), - [_, "FEE", var fee] => -decimal.Parse(fee), - _ => throw new InvalidOperationException($"Record {string.Join(", ", transaction)} is not in the expected format!"), - }; - Console.WriteLine($"Record: {string.Join(", ", transaction)}, New balance: {balance:C}"); - } - // - - IEnumerable ReadRecords() - { - foreach (var record in s_records) - yield return record.Split(',', StringSplitOptions.TrimEntries); - } - } -} diff --git a/docs/csharp/fundamentals/functional/snippets/patterns/OrderProcessor.cs b/docs/csharp/fundamentals/functional/snippets/patterns/OrderProcessor.cs deleted file mode 100644 index 09d33c9aa0f07..0000000000000 --- a/docs/csharp/fundamentals/functional/snippets/patterns/OrderProcessor.cs +++ /dev/null @@ -1,35 +0,0 @@ -namespace patterns; - -// -public record Order(int Items, decimal Cost); -// - -class OrderProcessor -{ - // - public decimal CalculateDiscount(Order order) => - order switch - { - { Items: > 10, Cost: > 1000.00m } => 0.10m, - { Items: > 5, Cost: > 500.00m } => 0.05m, - { Cost: > 250.00m } => 0.02m, - null => throw new ArgumentNullException(nameof(order), "Can't calculate discount on null order"), - var someObject => 0m, - }; - // -} - -class OrderProcessing -{ - // - public decimal CalculateDiscount(Order order) => - order switch - { - ( > 10, > 1000.00m) => 0.10m, - ( > 5, > 50.00m) => 0.05m, - { Cost: > 250.00m } => 0.02m, - null => throw new ArgumentNullException(nameof(order), "Can't calculate discount on null order"), - var someObject => 0m, - }; - // -} \ No newline at end of file diff --git a/docs/csharp/fundamentals/functional/snippets/patterns/Program.cs b/docs/csharp/fundamentals/functional/snippets/patterns/Program.cs deleted file mode 100644 index 5e61209361470..0000000000000 --- a/docs/csharp/fundamentals/functional/snippets/patterns/Program.cs +++ /dev/null @@ -1,87 +0,0 @@ -namespace patterns; - -class Program -{ - static void Main(string[] args) - { - NullCheck(); - - NullReferenceCheck(); - - var sequence = new List { 1, 2, 3, 4, 5, 6, 7 }; - var middle = MidPoint(sequence); - Console.WriteLine(middle); - - ListPattern.Example(); - - NotNullProperty(new Observation(42, "C", "Temperature")); - } - - // - public static T MidPoint(IEnumerable sequence) - { - if (sequence is IList list) - { - return list[list.Count / 2]; - } - else if (sequence is null) - { - throw new ArgumentNullException(nameof(sequence), "Sequence can't be null."); - } - else - { - int halfLength = sequence.Count() / 2 - 1; - if (halfLength < 0) halfLength = 0; - return sequence.Skip(halfLength).First(); - } - } - // - - private static void NullReferenceCheck() - { - // - string? message = ReadMessageOrDefault(); - - if (message is not null) - { - Console.WriteLine(message); - } - // - - static string? ReadMessageOrDefault() => "This is not the null string"; - } - - - private static void NullCheck() - { - // - int? maybe = 12; - - if (maybe is int number) - { - Console.WriteLine($"The nullable int 'maybe' has the value {number}"); - } - else - { - Console.WriteLine("The nullable int 'maybe' doesn't hold a value"); - } - // - } - - private static void NotNullProperty(Observation observation) - { - // - if (observation.Annotation is { }) - { - Console.WriteLine($"Observation description: {observation.Annotation}"); - } - // - } - - // - public record class Observation(int Value, string Units, string Name) - { - public string? Annotation { get; set; } - } - // -} diff --git a/docs/csharp/fundamentals/functional/snippets/patterns/Simulation.cs b/docs/csharp/fundamentals/functional/snippets/patterns/Simulation.cs deleted file mode 100644 index b5fa8e78f113d..0000000000000 --- a/docs/csharp/fundamentals/functional/snippets/patterns/Simulation.cs +++ /dev/null @@ -1,84 +0,0 @@ -namespace patterns; - -public enum Operation -{ - SystemTest, - Start, - Stop, - Reset -} - -public enum State -{ - Off, - Ready, - Running -} - -class Simulation -{ - // - public State PerformOperation(Operation command) => - command switch - { - Operation.SystemTest => RunDiagnostics(), - Operation.Start => StartSystem(), - Operation.Stop => StopSystem(), - Operation.Reset => ResetToReady(), - _ => throw new ArgumentException("Invalid enum value for command", nameof(command)), - }; - // - - // - public State PerformOperation(string command) => - command switch - { - "SystemTest" => RunDiagnostics(), - "Start" => StartSystem(), - "Stop" => StopSystem(), - "Reset" => ResetToReady(), - _ => throw new ArgumentException("Invalid string value for command", nameof(command)), - }; - // - - // - public State PerformOperation(ReadOnlySpan command) => - command switch - { - "SystemTest" => RunDiagnostics(), - "Start" => StartSystem(), - "Stop" => StopSystem(), - "Reset" => ResetToReady(), - _ => throw new ArgumentException("Invalid string value for command", nameof(command)), - }; - // - - // - string WaterState(int tempInFahrenheit) => - tempInFahrenheit switch - { - < 32 => "solid", - 32 => "solid/liquid transition", - (> 32) and (< 212) => "liquid", - 212 => "liquid / gas transition", - > 212 => "gas", - }; - // - - // - string WaterState2(int tempInFahrenheit) => - tempInFahrenheit switch - { - < 32 => "solid", - 32 => "solid/liquid transition", - < 212 => "liquid", - 212 => "liquid / gas transition", - _ => "gas", - }; - // - - private State ResetToReady() => State.Ready; - private State StopSystem() => State.Off; - private State StartSystem() => State.Running; - private State RunDiagnostics() => State.Ready; -} diff --git a/docs/csharp/fundamentals/patterns/declaration-constant-var-patterns.md b/docs/csharp/fundamentals/patterns/declaration-constant-var-patterns.md new file mode 100644 index 0000000000000..679cb13181840 --- /dev/null +++ b/docs/csharp/fundamentals/patterns/declaration-constant-var-patterns.md @@ -0,0 +1,83 @@ +--- +title: "Declaration, constant, and var patterns" +description: Learn when to use C# declaration, constant, and var patterns to test or capture the result of an expression. +ms.date: 09/15/2026 +ms.topic: concept-article +ai-usage: ai-assisted +--- + +# Declaration, constant, and `var` 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 you haven't used C# patterns before. For complete language rules, see the [patterns reference](../../language-reference/operators/patterns.md). + +A pattern is applied to an *input expression*. C# evaluates the expression, then the pattern tests or captures the resulting value. Declaration, constant, and `var` patterns answer three practical questions: + +- **Declaration pattern:** Did the expression produce a non-null value of a compatible run-time type? If so, declare a variable for that value. +- **Constant pattern:** Did the expression produce one specific constant value? +- **`var` pattern:** What value did the expression produce? Capture it without first testing its type or value. + +## Test and capture a type with a declaration pattern + +A *declaration pattern* consists of a type and a *designation*. The type specifies what run-time type to test. The designation declares the variable that receives the matching value. + +The following example receives an `object`, so the expression might produce many different types. The declaration pattern lets the matching branch use a decimal amount without a separate type test and cast: + +:::code language="csharp" source="snippets/patterns/BasicPatterns.cs" ID="DeclarationPattern"::: + +In `value is decimal amount`: + +- `value` is the input expression. C# evaluates it first. +- `decimal` is the tested type. The pattern matches when the evaluated value is non-null and its run-time type is compatible with `decimal`. +- `amount` is the designation. When the pattern matches, it declares `amount` and assigns the decimal value to it. + +The compiler tracks whether a local variable receives a value before your code reads it. This tracking is called *definite assignment*. Inside the `if` block, the compiler knows that `amount` was assigned because the block runs only when the pattern matches. The compiler produces an error if your code tries to access `amount` outside the `if` block. If `value` isn't a `decimal` value, the variable `amount` isn't assigned to a value. + +Choose a declaration pattern when the matching branch needs to use the result as the tested type. It combines the test, conversion, and variable declaration, which avoids repeating the expression or writing a separate cast. + +You can also use declaration patterns when one expression might produce several useful types: + +:::code language="csharp" source="snippets/patterns/BasicPatterns.cs" ID="DeclarationSwitch"::: + +Each arm declares a variable of the matched type because the result needs that type's formatting behavior. A declaration pattern matches only when the evaluated value is non-null and already has a run-time type that's compatible with the tested type through the conversions permitted for patterns. `null` has no run-time type for the pattern to match. The pattern also doesn't run user-defined conversion operators: It's a type test and capture, not a request to convert the value to another type. For the complete compatibility rules, see [Declaration and type patterns](../../language-reference/operators/patterns.md#declaration-and-type-patterns). + +## Match a specific value with a constant pattern + +A *constant pattern* tests whether an expression produces a particular constant, such as a number, string, Boolean, enum member, declared `const` value, or `null`. + +Constant patterns fit a switch expression when several known values each produce a different result: + +:::code language="csharp" source="snippets/patterns/BasicPatterns.cs" ID="ConstantPatterns"::: + +`Command` is an enum, a type that defines a set of named constants. `Command.Start`, `Command.Stop`, and `Command.Pause` are its enum members, so each switch arm uses a constant pattern to test one named command value. + +Choose this form when the command can have several discrete meanings. The switch arms keep the values and their results together. For one simple equality comparison, an `if` statement such as `if (command == Command.Start)` is usually easier to read. + +Constant-pattern matching uses built-in language equality rules rather than a user-defined `==` operator. For the detailed equality and conversion rules, see the [constant pattern reference](../../language-reference/operators/patterns.md#constant-pattern). + +The `null` constant pattern is useful for a reliable null check: + +:::code language="csharp" source="snippets/patterns/BasicPatterns.cs" ID="ConstantNullPattern"::: + +Choose `is null` or `is not null` when you're checking null state. These patterns don't call a user-defined equality operator, even when the expression's type overloads `==`. + +## Capture a result for a guard with a `var` pattern + +A *`var` pattern* matches every result, including `null`, and declares a variable whose type is the input expression's compile-time type. It can capture a computed value while another pattern is already matching an object: + +:::code language="csharp" source="snippets/patterns/BasicPatterns.cs" ID="VarPatternWhen"::: + +The declaration pattern `ExpressDelivery express` first captures the delivery object as `express`. The method call `EstimateDays(express)` is the input expression for the `var` pattern. C# evaluates that method call, and `var days` captures the resulting estimate as `days` without testing its type or value. The estimate can be one or two days when the guard succeeds. The arm result needs the captured value to report the actual number of days. + +An ordinary local variable can't be declared between a switch-arm pattern and its `when` guard. Calling `EstimateDays(express)` again in the result would repeat the calculation. Choose this `var` pattern form when the code is already matching, and both the guard and result need a computed intermediate value. + +If you don't need the captured value, use the [discard pattern `_`](discards.md#pattern-matching-with-switch) instead of declaring a variable. + +## See also + +- [Pattern matching overview](pattern-matching.md) +- [Type patterns](type-patterns.md) +- [Discards and the discard pattern](discards.md). +- [Declaration and type pattern reference](../../language-reference/operators/patterns.md#declaration-and-type-patterns). +- [Constant pattern reference](../../language-reference/operators/patterns.md#constant-pattern). +- [`var` pattern reference](../../language-reference/operators/patterns.md#var-pattern). diff --git a/docs/csharp/fundamentals/patterns/discards.md b/docs/csharp/fundamentals/patterns/discards.md new file mode 100644 index 0000000000000..a9a96a3a5964d --- /dev/null +++ b/docs/csharp/fundamentals/patterns/discards.md @@ -0,0 +1,79 @@ +--- +title: "Discards and the discard pattern" +description: Learn the difference between a C# discard pattern, a discard that ignores a produced value, and an unused lambda parameter. +ms.date: 09/15/2026 +ms.topic: concept-article +ai-usage: ai-assisted +--- + +# Discards and the discard pattern + +> [!TIP] +> This article is part of the **Fundamentals** section for developers who already know at least one programming language and are learning C#. If you're new to patterns, start with the [pattern matching overview](pattern-matching.md). For complete syntax, see the [discard pattern reference](../../language-reference/operators/patterns.md#discard-pattern). + +The underscore token (`_`) communicates that a value isn't needed. Its exact meaning depends on where it appears: + +| Context | Meaning of `_` | +| --- | --- | +| A switch expression arm or a nested pattern | A *discard pattern* that matches without capturing the result | +| A deconstruction or `out` argument | A *discard* that ignores one produced value | +| An assignment such as `_ = expression` | A *discard assignment* that evaluates the expression and ignores its result | +| Two or more lambda parameters named `_` | *Discard parameters* whose inputs aren't used | +| `var _` in a pattern | A `var` pattern with a discard designation | + +These forms share spelling and intent, but they aren't interchangeable. + +## Pattern matching with `switch` + +In the following example, `statusCode` is an `int`. Each switch arm produces a `string` message, which the program writes to the console. The final `_` handles every status code other than `200` and `404`: + +:::code language="csharp" source="snippets/discards/Program.cs" ID="DiscardPattern"::: + +A discard pattern is applied to an input expression. C# evaluates the expression, and `_` matches the evaluated value without capturing it. Choose `_` as the final switch-expression arm when every value not handled earlier should use the same fallback. Put it last because it matches everything, including `null`. + +The form `var _` is a `var` pattern with a discard designation. It also matches every evaluated value, but it doesn't introduce a readable variable. Prefer the shorter `_` discard pattern for a switch catch-all. For more about `var` patterns and designations, see [Declaration, constant, and `var` patterns](declaration-constant-var-patterns.md#capture-a-result-for-a-guard-with-a-var-pattern). + +## Deconstruction declarations + +`GetForecast` returns a tuple with four components: a `string` city and three `int` values for the high temperature, low temperature, and rain chance. The deconstruction declaration retains `city` and `high` because the program displays them. It uses `_` for the low temperature and rain chance because naming those unused components would imply that the code needs them: + +:::code language="csharp" source="snippets/discards/Program.cs" ID="TupleDiscards"::: + +The same discard syntax works when an object's `Deconstruct` method produces several values. For those forms, see [Deconstructing tuples and other types](../functional/deconstruct.md). + +## Calls to methods with `out` parameters + +The method takes a `string` and returns a `bool` that reports whether parsing succeeded. It also produces the parsed `int` through its `out` parameter. This code prints only the Boolean result, so `out _` makes it clear that the integer isn't needed: + +:::code language="csharp" source="snippets/discards/Program.cs" ID="OutDiscard"::: + +## A standalone discard + +The following method receives a nullable `string`. The null-coalescing expression produces the non-null string or throws an . The caller needs only that validation or exception effect, not the produced string, so a discard assignment provides the required assignment target and ignores the result: + +:::code language="csharp" source="snippets/discards/Program.cs" ID="DiscardAssignment"::: + +A discard assignment fits when evaluating an expression matters but retaining its result doesn't. For ordinary parameter validation, communicates the intent more directly and should usually be preferred. + +> [!IMPORTANT] +> Don't use `_ = Task.Run(...)` or `_ = SomeAsyncMethod()` to discard a task in application code. Await the task so its completion and exceptions remain in the calling flow. A discard assignment doesn't make a task safe, observe its exception, or create a supported fire-and-forget operation. + +## Mark unused lambda parameters + +An receives an `object?` sender and an value. The following handler needs neither parameter; it only writes `"Timer tick"` to the console. Naming both parameters `_` makes their unused status visible without inventing names that the body never uses: + +:::code language="csharp" source="snippets/discards/Program.cs" ID="LambdaDiscards"::: + +Choose discard parameters when a delegate signature requires inputs that the lambda body doesn't use. If a lambda has only one parameter named `_`, `_` remains an ordinary parameter name for backward compatibility. + +## Avoid `_` as an identifier + +`_` can be an ordinary identifier in contexts where C# doesn't recognize a discard. An in-scope variable named `_` can receive an assignment that looks like a discard assignment. In a pattern context, an accessible constant or type named `_` can also change how `_` is interpreted. Avoid declaring your own variables, constants, or types named `_`; use `_` to communicate discard intent. + +## See also + +- [Pattern matching overview](pattern-matching.md) +- [Declaration, constant, and `var` patterns](declaration-constant-var-patterns.md) +- [Deconstructing tuples and other types](../functional/deconstruct.md) +- [Lambda expression parameters](../../language-reference/operators/lambda-expressions.md#input-parameters-of-a-lambda-expression) +- [Discard pattern reference](../../language-reference/operators/patterns.md#discard-pattern) diff --git a/docs/csharp/fundamentals/patterns/pattern-matching.md b/docs/csharp/fundamentals/patterns/pattern-matching.md new file mode 100644 index 0000000000000..60a7425c36495 --- /dev/null +++ b/docs/csharp/fundamentals/patterns/pattern-matching.md @@ -0,0 +1,95 @@ +--- +title: "Pattern matching overview" +description: Learn how C# patterns test the type, value, and shape of data, and how to use patterns with is expressions, switch statements, and switch expressions. +ms.date: 09/14/2026 +ms.topic: overview +ai-usage: ai-assisted +--- + +# Pattern matching overview + +> [!TIP] +> This article is part of the **Fundamentals** section for developers who already know at least one programming language and are learning C#. If you're new to programming, start with the [Get started](../../tour-of-csharp/tutorials/index.md) tutorials first. Experienced C# developers can use the [patterns reference](../../language-reference/operators/patterns.md) for the complete syntax and rules. +> +> **Coming from another language?** C# patterns serve a role similar to Java's pattern matching and Python's `match` cases. C# uses patterns in `is` expressions, `switch` statements, and `switch` expressions. + +*Pattern matching* applies a pattern to an expression. A *pattern* is a condition to test the input expression against. The *pattern input* is that expression. C# evaluates the input expression; the result is the *evaluated value*. The pattern tests whether that value has a particular type, equals a particular value, or has a particular shape. When a pattern matches, your code can use information learned by the test, such as a more specific type or a value extracted from an object. + +You can use a pattern in three contexts: + +- on the right side of the `is` operator, +- in a `case` label of a `switch` statement, or +- 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: + +:::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. + +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: + +- `null` is a *constant pattern*. It tests whether the `delivery` expression evaluates to `null`. +- `ExpressDelivery express` is a *declaration pattern* with two parts. `ExpressDelivery` is the type-test part. It tests whether the evaluated value is a non-null object whose run-time type is compatible with `ExpressDelivery`. `express` is the *variable designation*: it declares a variable named `express` and assigns the matched `ExpressDelivery` object to it. +- `StandardDelivery { Days: <= 2 }` begins with a type test. `StandardDelivery` tests whether the evaluated value is a non-null object of that type. The braces contain a *property pattern*. `Days` names the property to inspect, so the `Days` property expression becomes the input to the recursive pattern. The `<= 2` portion is a *relational pattern*, which tests whether the evaluated value is less than or equal to `2`. +- `_` (underscore) is the *discard pattern*. It matches every evaluated value, including `null`. Because earlier arms already handle `null`, express deliveries, and standard deliveries arriving within two days, this final arm handles every remaining value. + +An arm without a `when` guard is *unguarded*. All arms in the first example are unguarded. If an earlier unguarded arm matches every evaluated value that a later arm could match, the later arm is *subsumed*. A *subsumed* pattern is one where every possible input value that matches was already matched by an earlier switch arm. It can never match, so the compiler reports an error. The discard arm (`_`) must come last because it matches every input expression. A guarded arm doesn't subsume a later arm based on its pattern alone because the guard might be `false`. + +A switch expression is *exhaustive* when its arms handle every possible input expression. The first example is exhaustive because its final discard arm handles anything the earlier arms don't match. The compiler warns when it detects a potential input value that no arm handles. The compiler can't prove exhaustiveness for every combination of patterns, but this diagnostic helps you write correct pattern matching code. For detailed matching, subsumption, and exhaustiveness rules, see the [patterns reference](../../language-reference/operators/patterns.md). + +## Test one condition with `is` + +Use the `is` operator when you need one Boolean test. The following code evaluates the `delivery` expression and applies the declaration pattern `ExpressDelivery express`. The type portion matches when the evaluated value is non-null and its run-time type is compatible with `ExpressDelivery`. When the pattern matches, its variable designation declares `express`: + +:::code language="csharp" source="snippets/patterns/Overview.cs" ID="IsPatternOverview"::: + +The compiler tracks whether a local variable has been assigned before your code reads it. This tracking is called *definite assignment*. Inside the `if` block, the condition can be `true` only when the pattern assigned the matching object to `express`. The compiler therefore knows that `express` is definitely assigned there. Your code can safely use its `TrackingCode` property. You don't need a separate cast. If you only need the type test and don't need to declare a variable, use a [type pattern](type-patterns.md), such as `delivery is ExpressDelivery`. + +Use `is null` and `is not null` for null checks. These patterns don't call a user-defined `==` or `!=` operator: + +:::code language="csharp" source="snippets/patterns/Overview.cs" ID="NullPatterns"::: + +For more null-safety guidance, see [nullable reference types](../null-safety/nullable-reference-types.md). + +## Choose between a statement and an expression + +Patterns work with both forms of `switch`: + +- Use a [`switch` statement](../statements/selection.md#match-a-value-with-a-switch-statement) when each match should run one or more statements. +- Use a `switch` expression when each match should produce a value. + +The following switch statement reports a delivery update. The express-delivery branch writes two messages, so a statement fits naturally: + +:::code language="csharp" source="snippets/patterns/Overview.cs" ID="SwitchStatement"::: + +Each `case` applies a pattern to the `delivery` expression. The matching section can run any number of statements before `break` exits the switch. The `default` section handles anything that the earlier cases don't match. + +The following switch expression replaces an `if` / `else if` chain that assigns one result: + +:::code language="csharp" source="snippets/patterns/Overview.cs" ID="StatusMessage"::: + +A switch expression is concise because every arm has the same purpose: produce the value returned. Use a switch statement when branches perform actions, and use a switch expression when branches calculate one result. + +## Pattern categories + +C# includes patterns for common kinds of data tests: + +| Pattern category | What it 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 | +| [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). + +## See also + +- [Declaration, constant, and `var` patterns](declaration-constant-var-patterns.md) +- [Type patterns](type-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/snippets/discards/Program.cs b/docs/csharp/fundamentals/patterns/snippets/discards/Program.cs new file mode 100644 index 0000000000000..b7dcb2679058c --- /dev/null +++ b/docs/csharp/fundamentals/patterns/snippets/discards/Program.cs @@ -0,0 +1,63 @@ +namespace Discards; + +class Program +{ + static void Main() + { + ShowStatus(); + ShowForecast(); + ValidateNumber(); + ValidateLabel("ZX-42"); + ShowLambdaDiscards(); + } + + // + static void ShowStatus() + { + int statusCode = 503; + string message = statusCode switch + { + 200 => "Ready", + 404 => "Not found", + _ => "Another status" + }; + + Console.WriteLine(message); + } + // + + // + static void ShowForecast() + { + var (city, high, _, _) = GetForecast(); + Console.WriteLine($"{city}: high {high}°C"); + + static (string City, int High, int Low, int RainChance) GetForecast() => + ("Portland", 18, 9, 40); + } + // + + // + static void ValidateNumber() + { + bool isNumber = int.TryParse("42", out _); + Console.WriteLine($"The text is numeric: {isNumber}"); + } + // + + // + static void ValidateLabel(string? label) + { + _ = label ?? throw new ArgumentNullException(nameof(label)); + Console.WriteLine("Label accepted."); + } + // + + // + static void ShowLambdaDiscards() + { + EventHandler handler = (_, _) => Console.WriteLine("Timer tick"); + handler(null, EventArgs.Empty); + } + // +} diff --git a/docs/csharp/fundamentals/functional/snippets/discards/discards.csproj b/docs/csharp/fundamentals/patterns/snippets/discards/discards.csproj similarity index 57% rename from docs/csharp/fundamentals/functional/snippets/discards/discards.csproj rename to docs/csharp/fundamentals/patterns/snippets/discards/discards.csproj index 8d24c8834156d..38452f1b41daf 100644 --- a/docs/csharp/fundamentals/functional/snippets/discards/discards.csproj +++ b/docs/csharp/fundamentals/patterns/snippets/discards/discards.csproj @@ -2,11 +2,9 @@ Exe - net8.0 + net10.0 enable enable - Discards - Discards.Program diff --git a/docs/csharp/fundamentals/patterns/snippets/patterns/BasicPatterns.cs b/docs/csharp/fundamentals/patterns/snippets/patterns/BasicPatterns.cs new file mode 100644 index 0000000000000..64da06de2428d --- /dev/null +++ b/docs/csharp/fundamentals/patterns/snippets/patterns/BasicPatterns.cs @@ -0,0 +1,72 @@ +static class BasicPatterns +{ + public static void Run() + { + PrintPrice(19.95m); + Console.WriteLine(FormatSensorValue(21.5)); + Console.WriteLine(GetCommandMessage(Command.Start)); + Console.WriteLine(HasText("ready")); + Console.WriteLine(GetDeliveryMessage(new ExpressDelivery(800))); + } + + // + static void PrintPrice(object value) + { + if (value is decimal amount) + { + Console.WriteLine($"Price: {amount:C}"); + } + } + // + + // + static string FormatSensorValue(object reading) => + reading switch + { + int count => $"Count: {count}", + double temperature => $"Temperature: {temperature:F1}°C", + string message => $"Message: {message}", + _ => "Unsupported reading" + }; + // + + enum Command + { + Start, + Stop, + Pause + } + + // + static string GetCommandMessage(Command command) => + command switch + { + Command.Start => "Starting", + Command.Stop => "Stopping", + Command.Pause => "Pausing", + _ => "Unknown command" + }; + // + + // + static bool HasText(string? text) => text is not null; + // + + // + static string GetDeliveryMessage(object delivery) => + delivery switch + { + ExpressDelivery express + when EstimateDays(express) is var days && days <= 2 + => $"Arrives in {days} day{(days == 1 ? "" : "s")}", + ExpressDelivery => "Express delivery for your location takes more than two days", + _ => "Standard delivery" + }; + + static int EstimateDays(ExpressDelivery delivery) => + delivery.MilesAway <= 500 ? 1 : + delivery.MilesAway <= 1_000 ? 2 : 3; + + record ExpressDelivery(int MilesAway); + // +} diff --git a/docs/csharp/fundamentals/patterns/snippets/patterns/Overview.cs b/docs/csharp/fundamentals/patterns/snippets/patterns/Overview.cs new file mode 100644 index 0000000000000..b982f983df09d --- /dev/null +++ b/docs/csharp/fundamentals/patterns/snippets/patterns/Overview.cs @@ -0,0 +1,83 @@ +static class Overview +{ + public static void Run() + { + Console.WriteLine(GetDeliveryMessage(new ExpressDelivery("ZX-42"))); + PrintTrackingCode(new ExpressDelivery("ZX-42")); + PrintPackageStatus(null); + PrintDeliveryUpdate(new ExpressDelivery("ZX-42")); + Console.WriteLine(GetStatusMessage(new StandardDelivery(2))); + } + + // + static string GetDeliveryMessage(Delivery? delivery) => + delivery switch + { + null => "No delivery was scheduled.", + ExpressDelivery express => $"Express package {express.TrackingCode}", + StandardDelivery { Days: <= 2 } => "Standard delivery arriving soon", + _ => "Standard delivery" + }; + // + + // + static void PrintTrackingCode(Delivery delivery) + { + if (delivery is ExpressDelivery express) + { + Console.WriteLine($"Track express package {express.TrackingCode}"); + } + } + // + + // + static void PrintPackageStatus(Delivery? delivery) + { + if (delivery is null) + { + Console.WriteLine("No package is available."); + } + else if (delivery is not null) + { + Console.WriteLine("A package is ready to track."); + } + } + // + + // + static void PrintDeliveryUpdate(Delivery? delivery) + { + switch (delivery) + { + case null: + Console.WriteLine("No delivery was scheduled."); + break; + case ExpressDelivery express: + Console.WriteLine($"Express delivery {express.TrackingCode} is ready."); + Console.WriteLine("Notify the priority desk."); + break; + case StandardDelivery standard: + Console.WriteLine($"Standard delivery arrives in {standard.Days} days."); + break; + default: + Console.WriteLine("Another delivery type is scheduled."); + break; + } + } + // + + // + static string GetStatusMessage(StandardDelivery delivery) => + delivery.Days switch + { + 0 => "Delivered today", + 1 => "Arriving tomorrow", + <= 3 => "Arriving soon", + _ => "In transit" + }; + // +} + +abstract record Delivery; +sealed record ExpressDelivery(string TrackingCode) : Delivery; +sealed record StandardDelivery(int Days) : Delivery; diff --git a/docs/csharp/fundamentals/patterns/snippets/patterns/Program.cs b/docs/csharp/fundamentals/patterns/snippets/patterns/Program.cs new file mode 100644 index 0000000000000..c55bbc0a6b086 --- /dev/null +++ b/docs/csharp/fundamentals/patterns/snippets/patterns/Program.cs @@ -0,0 +1,3 @@ +Overview.Run(); +BasicPatterns.Run(); +TypePatterns.Run(); diff --git a/docs/csharp/fundamentals/patterns/snippets/patterns/TypePatterns.cs b/docs/csharp/fundamentals/patterns/snippets/patterns/TypePatterns.cs new file mode 100644 index 0000000000000..61cce9add8f12 --- /dev/null +++ b/docs/csharp/fundamentals/patterns/snippets/patterns/TypePatterns.cs @@ -0,0 +1,89 @@ +static class TypePatterns +{ + public static void Run() + { + Console.WriteLine(CanRoute(new CustomerAddress("15 Pine Street")) + ? "Add the destination to the route plan." + : "Reject the destination."); + ShowCompatibility(); + Console.WriteLine(RouteRequest(new PasswordResetRequest())); + + ShowConfidentialBatchHandling(); + } + + // + static bool CanRoute(object? destination) => + destination is IRouteStop; + // + + // + static string RouteRequest(object request) => + request switch + { + PasswordResetRequest => "Identity queue", + BillingQuestion => "Billing queue", + SupportRequest => "General support queue", + _ => "Intake queue" + }; + // + + // + interface IRouteStop { } + + abstract class RouteStop(string street) : IRouteStop + { + public string Street { get; } = street; + + public string GetDisplayName() => Street; + } + + sealed class ExpressRouteStop(string street) : RouteStop(street) + { + } + + static void ShowCompatibility() + { + object destination = new ExpressRouteStop("8 Oak Avenue"); + + Console.WriteLine($"Exact class: {destination is ExpressRouteStop}"); + Console.WriteLine($"Base class: {destination is RouteStop}"); + Console.WriteLine($"Interface: {destination is IRouteStop}"); + } + // + + // + static void ShowConfidentialBatchHandling() + { + object[] incomingRequests = [new BillingQuestion(), new ConfidentialRequest()]; + bool requiresConfidentialHandling = + ContainsRequestOfType(incomingRequests); + + Console.WriteLine(requiresConfidentialHandling + ? "Send the entire batch to confidential handling." + : "Send the batch to standard handling."); + } + + static bool ContainsRequestOfType(IEnumerable requests) + { + foreach (object request in requests) + { + if (request is TRequest) + { + return true; + } + } + + return false; + } + // + + sealed class CustomerAddress(string street) : IRouteStop + { + public string Street { get; } = street; + } + + abstract record SupportRequest; + sealed record PasswordResetRequest : SupportRequest; + sealed record BillingQuestion : SupportRequest; + sealed record ConfidentialRequest : SupportRequest; +} diff --git a/docs/csharp/fundamentals/functional/snippets/patterns/patterns.csproj b/docs/csharp/fundamentals/patterns/snippets/patterns/patterns.csproj similarity index 80% rename from docs/csharp/fundamentals/functional/snippets/patterns/patterns.csproj rename to docs/csharp/fundamentals/patterns/snippets/patterns/patterns.csproj index 58be7ac77af37..0ece068f6df1a 100644 --- a/docs/csharp/fundamentals/functional/snippets/patterns/patterns.csproj +++ b/docs/csharp/fundamentals/patterns/snippets/patterns/patterns.csproj @@ -2,7 +2,7 @@ Exe - net9.0 + net10.0 enable enable diff --git a/docs/csharp/fundamentals/patterns/type-patterns.md b/docs/csharp/fundamentals/patterns/type-patterns.md new file mode 100644 index 0000000000000..0a3b618d68a3b --- /dev/null +++ b/docs/csharp/fundamentals/patterns/type-patterns.md @@ -0,0 +1,61 @@ +--- +title: "Type patterns" +description: Learn when to use a C# type pattern for a yes-or-no run-time type test without declaring a variable. +ms.date: 09/15/2026 +ms.topic: concept-article +ai-usage: ai-assisted +--- + +# Type 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 compatibility rules, see [declaration and type patterns](../../language-reference/operators/patterns.md#declaration-and-type-patterns) in the language reference. + +A *type pattern* is applied to an input expression. C# evaluates the expression, then tests whether the resulting value is non-null and its run-time type is compatible with the specified type. A type pattern reports only whether the type test succeeds. It doesn't declare a variable. + +## Ask a yes-or-no type question + +In a delivery system, `IRouteStop` is a capability contract for destinations accepted by route planning. `CanRoute` tests whether the evaluated destination has that capability, and its Boolean result determines whether the destination enters the route-planning workflow. The workflow needs only a yes-or-no answer and doesn't read any route-stop members, so a type pattern without a captured variable fits. + +:::code language="csharp" source="snippets/patterns/TypePatterns.cs" ID="TypePattern"::: + +The input expression is `destination`, and `IRouteStop` is the type being tested. Choose a type pattern when the answer is only yes or no. If the matching branch needs to read an address or call another member through `IRouteStop`, choose a [declaration pattern](declaration-constant-var-patterns.md#test-and-capture-a-type-with-a-declaration-pattern) instead so the branch has a variable of that type. + +> [!NOTE] +> You might also see `destination is IRouteStop _`. That syntax is a declaration pattern in which `_` means that no variable is retained. It performs the same type test when both forms are valid, but `destination is IRouteStop` states the test-only intent more directly. + +## Match classes and interfaces + +In the delivery system, `IRouteStop` defines the capability contract for route-planning destinations, while `RouteStop` is a base class that provides common route-stop data and implementation. `ExpressRouteStop` is a specialized class derived from `RouteStop`. The example tests one evaluated value so its output can demonstrate all three compatible types. Each test needs only a Boolean result, so no captured variable is needed. + +:::code language="csharp" source="snippets/patterns/TypePatterns.cs" ID="ClassAndInterface"::: + +The evaluated value is an `ExpressRouteStop`. The output shows that it matches its exact class, its `RouteStop` base class, and the `IRouteStop` interface that `RouteStop` implements. + +For detailed compatibility rules and edge cases, see the [type pattern reference](../../language-reference/operators/patterns.md#declaration-and-type-patterns). + +## Route several types + +In a support system, `SupportRequest` is the base class for specialized request classes such as `PasswordResetRequest` and `BillingQuestion`. A *switch arm* pairs a pattern with the result to return when that pattern matches. Each arm returns the name of a processing queue, and the final arm provides a fallback queue for other evaluated values. The selected queue depends only on the run-time type of the evaluated value, so type patterns fit because no request member is read. + +:::code language="csharp" source="snippets/patterns/TypePatterns.cs" ID="TypePatternSwitch"::: + +Each arm answers a type question and returns the queue that handles that request. If an arm needed to read request members, use a declaration pattern to capture the matching value in a variable. + +Switch arms are considered from top to bottom. Put a specialized class before its base class. Otherwise, the base-class arm can match every instance of the specialized class, which makes the later arm unreachable. + +## Optional: use a type parameter as the tested type + +This optional example builds on [generic types and methods](../types/generics.md). A *type parameter* such as `TRequest` is a placeholder for a type that the caller supplies. An incoming-request batch can contain several request types, and the caller supplies `ConfidentialRequest` to test whether any request requires confidential handling. The Boolean result selects confidential handling for the entire batch and produces a visible status message. A type pattern with a type parameter fits because only the existence of a matching request matters, so the matching object doesn't need to be retained. + +:::code language="csharp" source="snippets/patterns/TypePatterns.cs" ID="GenericTypePattern"::: + +If the caller needed the matching request itself, a search or filter operation that returns matching items would be more appropriate. + +## See also + +- [Pattern matching overview](pattern-matching.md) +- [Declaration, constant, and `var` patterns](declaration-constant-var-patterns.md) +- [Generic types and methods](../types/generics.md) +- [Type-testing and cast operators](../../language-reference/operators/type-testing-and-cast.md) +- [Type pattern reference](../../language-reference/operators/patterns.md#declaration-and-type-patterns) diff --git a/docs/csharp/fundamentals/statements/selection.md b/docs/csharp/fundamentals/statements/selection.md index 58ebf2ed649f9..b50ce2aa0d45c 100644 --- a/docs/csharp/fundamentals/statements/selection.md +++ b/docs/csharp/fundamentals/statements/selection.md @@ -47,7 +47,7 @@ A `case` label isn't limited to constant values. It can test a *pattern*, which :::code language="csharp" source="./snippets/selection-statements/Program.cs" id="SwitchWhen"::: -Pattern-based cases are evaluated top to bottom, so more specific patterns belong before more general ones. For the full catalog of patterns, see [pattern matching](../functional/pattern-matching.md). +Pattern-based cases are evaluated top to bottom, so more specific patterns belong before more general ones. For the full catalog of patterns, see [pattern matching](../patterns/pattern-matching.md). ## Select a value with an expression @@ -61,11 +61,11 @@ For its syntax, short-circuit behavior, and guidance on choosing it instead of ` ### `switch` expression -A `switch` expression is the expression counterpart to the `switch` statement. Instead of running code for the matching case, it evaluates to a value. It's more concise than assigning a value in each arm of a `switch` statement, and the compiler warns you when the arms don't cover every possible input. The `switch` expression is a core part of pattern matching. To learn when and how to use it, see [pattern matching](../functional/pattern-matching.md) and the [`switch` expression](../../language-reference/operators/switch-expression.md) reference. +A `switch` expression is the expression counterpart to the `switch` statement. Instead of running code for the matching case, it evaluates to a value. It's more concise than assigning a value in each arm of a `switch` statement, and the compiler warns you when the arms don't cover every possible input. The `switch` expression is a core part of pattern matching. To learn when and how to use it, see [pattern matching](../patterns/pattern-matching.md) and the [`switch` expression](../../language-reference/operators/switch-expression.md) reference. ## See also - [Iteration statements](iteration.md) -- [Pattern matching](../functional/pattern-matching.md) +- [Pattern matching](../patterns/pattern-matching.md) - [Selection statements (language reference)](../../language-reference/statements/selection-statements.md) - [Conditional operator (language reference)](../../language-reference/operators/conditional-operator.md) diff --git a/docs/csharp/fundamentals/tutorials/safely-cast-using-pattern-matching-is-and-as-operators.md b/docs/csharp/fundamentals/tutorials/safely-cast-using-pattern-matching-is-and-as-operators.md index 3ca501163156e..a3af0e6c3d825 100644 --- a/docs/csharp/fundamentals/tutorials/safely-cast-using-pattern-matching-is-and-as-operators.md +++ b/docs/csharp/fundamentals/tutorials/safely-cast-using-pattern-matching-is-and-as-operators.md @@ -9,7 +9,7 @@ helpviewer_keywords: --- # How to safely cast by using pattern matching and the is and as operators -Because objects are polymorphic, it's possible for a variable of a base class type to hold a derived [type](../types/index.md). To access the derived type's instance members, it's necessary to [cast](../../programming-guide/types/casting-and-type-conversions.md) the value back to the derived type. However, a cast creates the risk of throwing an . C# provides [pattern matching](../functional/pattern-matching.md) statements that perform a cast conditionally only when it will succeed. C# also provides the [is](../../language-reference/operators/type-testing-and-cast.md#the-is-operator) and [as](../../language-reference/operators/type-testing-and-cast.md#the-as-operator) operators to test if a value is of a certain type. +Because objects are polymorphic, it's possible for a variable of a base class type to hold a derived [type](../types/index.md). To access the derived type's instance members, it's necessary to [cast](../../programming-guide/types/casting-and-type-conversions.md) the value back to the derived type. However, a cast creates the risk of throwing an . C# provides [pattern matching](../patterns/pattern-matching.md) statements that perform a cast conditionally only when it will succeed. C# also provides the [is](../../language-reference/operators/type-testing-and-cast.md#the-is-operator) and [as](../../language-reference/operators/type-testing-and-cast.md#the-as-operator) operators to test if a value is of a certain type. The following example shows how to use the pattern matching `is` statement: diff --git a/docs/csharp/fundamentals/tutorials/system-command-line.md b/docs/csharp/fundamentals/tutorials/system-command-line.md index 47809a2ab42e1..f5e08429e5143 100644 --- a/docs/csharp/fundamentals/tutorials/system-command-line.md +++ b/docs/csharp/fundamentals/tutorials/system-command-line.md @@ -115,7 +115,7 @@ A represents an a Each subcommand needs an *action*. An action is a [delegate](../../programming-guide/delegates/index.md) that runs when the user invokes that command. A delegate is a type that represents a reference to a method. Here, you pass a [lambda expression](../../language-reference/operators/lambda-expressions.md) (an inline anonymous function defined with `=>`) as the delegate. Call `SetAction` to assign each action. The delegate receives a that provides access to parsed values through `GetValue`. -1. Set the action for the `add` command. This action introduces [string interpolation](../../language-reference/tokens/interpolated.md) (`$"..."` strings that embed expressions in braces), the [conditional operator](../../language-reference/operators/conditional-operator.md) (`?:`), and a [type test pattern](../functional/pattern-matching.md) (`due is DateOnly dueDate`) that checks whether a nullable value has a value and assigns it to a new variable in one step: +1. Set the action for the `add` command. This action introduces [string interpolation](../../language-reference/tokens/interpolated.md) (`$"..."` strings that embed expressions in braces), the [conditional operator](../../language-reference/operators/conditional-operator.md) (`?:`), and a [type test pattern](../patterns/pattern-matching.md) (`due is DateOnly dueDate`) that checks whether a nullable value has a value and assigns it to a new variable in one step: :::code language="csharp" source="./snippets/system-commandline/TaskCli.cs" id="AddAction"::: @@ -128,7 +128,7 @@ Each subcommand needs an *action*. An action is a [delegate](../../programming-g 1. Set the action for the `complete` command. This action uses LINQ's to find: - A matching task. - - An [`is null` pattern](../functional/pattern-matching.md) to check whether the task exists. + - An [`is null` pattern](../patterns/pattern-matching.md) to check whether the task exists. - A [`with` expression](../../language-reference/operators/with-expression.md) to create a new record instance by copying the existing values first, and then applying the properties you set in the `with` initializer (here, `IsComplete = true`). Records are immutable by default, so this copy-and-update pattern is how you produce a modified value. Because the action can fail (for example, the task ID doesn't exist), the action returns an integer error code that becomes the app's exit code: diff --git a/docs/csharp/fundamentals/types/conversions.md b/docs/csharp/fundamentals/types/conversions.md index 503b85eeb607b..ae6bff26c8f73 100644 --- a/docs/csharp/fundamentals/types/conversions.md +++ b/docs/csharp/fundamentals/types/conversions.md @@ -91,5 +91,5 @@ For advanced conversion behavior and all overloads, review the API reference for - [Type system overview](index.md) - [Built-in types and literals](built-in-types.md) -- [Pattern matching](../functional/pattern-matching.md) +- [Pattern matching](../patterns/pattern-matching.md) - [How to safely cast by using pattern matching and the is and as operators](../tutorials/safely-cast-using-pattern-matching-is-and-as-operators.md) diff --git a/docs/csharp/fundamentals/types/delegates-lambdas.md b/docs/csharp/fundamentals/types/delegates-lambdas.md index df6ad896997f8..57519c9b5eee9 100644 --- a/docs/csharp/fundamentals/types/delegates-lambdas.md +++ b/docs/csharp/fundamentals/types/delegates-lambdas.md @@ -104,5 +104,5 @@ Subscribing is optional. The `?.Invoke(...)` in the `Publish` method means the e - [Type system overview](index.md) - [Methods](../../methods.md) -- [Pattern matching](../functional/pattern-matching.md) +- [Pattern matching](../patterns/pattern-matching.md) - [Events (C# programming guide)](../../programming-guide/events/index.md) diff --git a/docs/csharp/fundamentals/types/enums.md b/docs/csharp/fundamentals/types/enums.md index 3b628a2ffb8c1..703ee49ff3335 100644 --- a/docs/csharp/fundamentals/types/enums.md +++ b/docs/csharp/fundamentals/types/enums.md @@ -38,7 +38,7 @@ Enums work naturally with `switch` expressions and pattern matching. The compile :::code language="csharp" source="snippets/enums/Program.cs" ID="UsingSeason"::: -The discard pattern (`_`) handles any value not explicitly listed. Because an enum's underlying type is an integer, a variable can hold a value that doesn't correspond to any named member. For example, `(Season)99` is valid at runtime. The discard pattern ensures the switch expression handles those unexpected values safely. *Pattern matching* is a C# feature that tests a value against a shape or condition. In this example, each `case` checks whether the enum matches a specific member. Switch expressions are one of several pattern matching forms. For more information about pattern matching, see [Pattern matching](../functional/pattern-matching.md). +The discard pattern (`_`) handles any value not explicitly listed. Because an enum's underlying type is an integer, a variable can hold a value that doesn't correspond to any named member. For example, `(Season)99` is valid at runtime. The discard pattern ensures the switch expression handles those unexpected values safely. *Pattern matching* is a C# feature that tests a value against a shape or condition. In this example, each `case` checks whether the enum matches a specific member. Switch expressions are one of several pattern matching forms. For more information about pattern matching, see [Pattern matching](../patterns/pattern-matching.md). ## Bit flags @@ -72,5 +72,5 @@ Use and matches against a `Remainder` of 0: +You can also combine deconstruction with [pattern matching](../../fundamentals/patterns/pattern-matching.md) to inspect the characteristics of fields in a tuple. The following example loops through several integers and prints those that are divisible by 3. It deconstructs the tuple result of and matches against a `Remainder` of 0: :::code language="csharp" source="snippets/shared/ValueTuples.cs" id="DeconstructToPattern"::: diff --git a/docs/csharp/language-reference/compiler-messages/deconstruction-errors.md b/docs/csharp/language-reference/compiler-messages/deconstruction-errors.md index d6de21c767bc9..7b2148a3ebf4f 100644 --- a/docs/csharp/language-reference/compiler-messages/deconstruction-errors.md +++ b/docs/csharp/language-reference/compiler-messages/deconstruction-errors.md @@ -58,14 +58,14 @@ Provide an accessible instance or extension `Deconstruct` method that returns `v - **CS8183**: *Cannot infer the type of implicitly-typed discard.* - **CS8197**: *Cannot infer the type of implicitly-typed out variable 'variable'.* -Supply a typed, deconstructable expression on the right so the compiler can determine each implicitly typed variable (**CS8130**, **CS8131**). Cast or otherwise give a discarded expression a type; in a deconstruction, specify an element type when appropriate (**CS8183**). For an `out` variable, use a method parameter that supplies the type or specify the type explicitly in the `out` argument (**CS8197**). For more information, see [deconstruction](../../fundamentals/functional/deconstruct.md) and [calls with `out` parameters](../../fundamentals/functional/discards.md#calls-to-methods-with-out-parameters). +Supply a typed, deconstructable expression on the right so the compiler can determine each implicitly typed variable (**CS8130**, **CS8131**). Cast or otherwise give a discarded expression a type; in a deconstruction, specify an element type when appropriate (**CS8183**). For an `out` variable, use a method parameter that supplies the type or specify the type explicitly in the `out` argument (**CS8197**). For more information, see [deconstruction](../../fundamentals/functional/deconstruct.md) and [calls with `out` parameters](../../fundamentals/patterns/discards.md#calls-to-methods-with-out-parameters). ## Deconstruction cardinality - **CS8132**: *Cannot deconstruct a tuple of 'element count' elements into 'variable count' variables.* - **CS8134**: *Deconstruction must contain at least two variables.* -Use at least two variables in a deconstruction (**CS8134**). Match the number of variables on the left to the number of tuple elements on the right, and add a discard (`_`) for each value that you don't need (**CS8132**). For more information, see [discards in tuple and object deconstruction](../../fundamentals/functional/discards.md#tuple-and-object-deconstruction). +Use at least two variables in a deconstruction (**CS8134**). Match the number of variables on the left to the number of tuple elements on the right, and add a discard (`_`) for each value that you don't need (**CS8132**). For more information, see [discards in tuple and object deconstruction](../../fundamentals/patterns/discards.md#deconstruction-declarations). ## Deconstruction declaration and assignment syntax diff --git a/docs/csharp/language-reference/compiler-messages/expression-tree-restrictions.md b/docs/csharp/language-reference/compiler-messages/expression-tree-restrictions.md index fef6cab86db60..8eee3d2e767c4 100644 --- a/docs/csharp/language-reference/compiler-messages/expression-tree-restrictions.md +++ b/docs/csharp/language-reference/compiler-messages/expression-tree-restrictions.md @@ -178,7 +178,7 @@ The following expressions are prohibited: - [pattern matching](../operators/patterns.md) expressions aren't allowed. - [Tuple literals](../builtin-types/value-tuples.md) and many tuple operations, such as equality comparisons aren't allowed. - [`throw` expressions](../statements/exception-handling-statements.md#the-throw-expression) aren't allowed. -- [discard](../../fundamentals/functional/discards.md) (`_`) declarations. +- [discard](../../fundamentals/patterns/discards.md) (`_`) declarations. - The [index and range](../operators/member-access-operators.md#indexer-access) operators aren't allowed. - Non-destructive mutation using [`with`](../operators/with-expression.md) expressions aren't allowed. - You can't declare or access [inline arrays](../builtin-types/struct.md#inline-arrays). diff --git a/docs/csharp/language-reference/compiler-messages/record-declaration-errors.md b/docs/csharp/language-reference/compiler-messages/record-declaration-errors.md index 4f4fb86a11542..d0d4fd0662ecd 100644 --- a/docs/csharp/language-reference/compiler-messages/record-declaration-errors.md +++ b/docs/csharp/language-reference/compiler-messages/record-declaration-errors.md @@ -123,10 +123,10 @@ When you declare a [positional record](../builtin-types/record.md#positional-syn To correct these errors, apply the following changes to your positional record declarations: -- Change any explicitly declared member that corresponds to a positional parameter so it's a readable instance property or field with the same type as the parameter. The compiler needs the member to be readable and type-compatible so that the synthesized `Deconstruct` method and [positional pattern matching](../../fundamentals/functional/pattern-matching.md) can access the value correctly (**CS8866**). +- Change any explicitly declared member that corresponds to a positional parameter so it's a readable instance property or field with the same type as the parameter. The compiler needs the member to be readable and type-compatible so that the synthesized `Deconstruct` method and [positional pattern matching](../../fundamentals/patterns/pattern-matching.md) can access the value correctly (**CS8866**). - Ensure that each positional parameter initializes its corresponding property in the constructor body when you provide an explicit constructor. The compiler raises a warning when a parameter goes unused because it typically indicates a typo or a mismatch between the parameter name and the property name, which would leave the property uninitialized (**CS8907**). - Change the type of a field declared in a record to a type that's valid in that context. Certain types, such as `Span` or other `ref struct` types, can't be used as fields in a record because record types require all fields to be compatible with heap allocation and value-based equality (**CS8908**). -- Remove the `new` modifier from a member in a derived record that hides a positional member from the base record. When a positional member is hidden, the compiler can't match the positional parameter to its corresponding property, which breaks the synthesized `Deconstruct` method and positional [pattern matching](../../fundamentals/functional/pattern-matching.md) (**CS8913**). +- Remove the `new` modifier from a member in a derived record that hides a positional member from the base record. When a positional member is hidden, the compiler can't match the positional parameter to its corresponding property, which breaks the synthesized `Deconstruct` method and positional [pattern matching](../../fundamentals/patterns/pattern-matching.md) (**CS8913**). ## Equality members diff --git a/docs/csharp/language-reference/compiler-messages/tuple-errors.md b/docs/csharp/language-reference/compiler-messages/tuple-errors.md index 54b58e2bd7772..a1169536632e7 100644 --- a/docs/csharp/language-reference/compiler-messages/tuple-errors.md +++ b/docs/csharp/language-reference/compiler-messages/tuple-errors.md @@ -144,5 +144,5 @@ These errors relate to tuple expression formation. Tuples require at least two e - [Value tuples](../builtin-types/value-tuples.md) - [Deconstruction](../../fundamentals/functional/deconstruct.md) -- [Pattern matching](../../fundamentals/functional/pattern-matching.md) +- [Pattern matching](../../fundamentals/patterns/pattern-matching.md) - [Void](../builtin-types/void.md) diff --git a/docs/csharp/language-reference/operators/delegate-operator.md b/docs/csharp/language-reference/operators/delegate-operator.md index fb591430a677b..492476171f450 100644 --- a/docs/csharp/language-reference/operators/delegate-operator.md +++ b/docs/csharp/language-reference/operators/delegate-operator.md @@ -27,7 +27,7 @@ When you use the `delegate` operator, you can omit the parameter list. If you om :::code language="csharp" source="snippets/shared/DelegateOperator.cs" id="WithoutParameterList"::: -This functionality is the only feature of anonymous methods that lambda expressions don't support. In all other cases, use a lambda expression to write inline code. You can use [discards](../../fundamentals/functional/discards.md) to specify two or more input parameters of an anonymous method that the method doesn't use: +This functionality is the only feature of anonymous methods that lambda expressions don't support. In all other cases, use a lambda expression to write inline code. You can use [discards](../../fundamentals/patterns/discards.md) to specify two or more input parameters of an anonymous method that the method doesn't use: :::code language="csharp" source="snippets/shared/DelegateOperator.cs" id="SnippetDiscards" ::: diff --git a/docs/csharp/language-reference/operators/lambda-expressions.md b/docs/csharp/language-reference/operators/lambda-expressions.md index 6bd26191aa56b..fc989ad7d6cf3 100644 --- a/docs/csharp/language-reference/operators/lambda-expressions.md +++ b/docs/csharp/language-reference/operators/lambda-expressions.md @@ -89,7 +89,7 @@ The compiler typically infers the types for parameters to lambda expressions, wh Input parameter types must be all explicit or all implicit. Otherwise, a [CS0748](../compiler-messages/lambda-expression-errors.md#lambda-expression-parameters-and-returns) compiler error occurs. Before C# 14, you must include the explicit type on a parameter if it has any modifiers, such as `ref` or `out`. In C# 14, that restriction is removed. However, you must still declare the type if you use the `params` modifier. -Use [discards](../../fundamentals/functional/discards.md) to specify two or more input parameters of a lambda expression that aren't used in the expression: +Use [discards](../../fundamentals/patterns/discards.md) to specify two or more input parameters of a lambda expression that aren't used in the expression: :::code language="csharp" source="snippets/lambda-expressions/GeneralExamples.cs" id="SnippetDiscards"::: diff --git a/docs/csharp/language-reference/operators/member-access-operators.md b/docs/csharp/language-reference/operators/member-access-operators.md index df2cd51384910..353ecaac4f640 100644 --- a/docs/csharp/language-reference/operators/member-access-operators.md +++ b/docs/csharp/language-reference/operators/member-access-operators.md @@ -103,7 +103,7 @@ You also use square brackets to specify [attributes](/dotnet/csharp/advanced-top void TraceMethod() {} ``` -Additionally, use square brackets to designate [list patterns](../../fundamentals/functional/pattern-matching.md) for use in pattern matching or testing. +Additionally, use square brackets to designate [list patterns](patterns.md#list-patterns) for use in pattern matching or testing. ```csharp arr is ([1, 2, ..]) diff --git a/docs/csharp/language-reference/operators/patterns.md b/docs/csharp/language-reference/operators/patterns.md index 7d65eff105073..2159402a79371 100644 --- a/docs/csharp/language-reference/operators/patterns.md +++ b/docs/csharp/language-reference/operators/patterns.md @@ -400,5 +400,5 @@ For more information, see the [Patterns and pattern matching](~/_csharpstandard/ ## See also - [C# operators and expressions](index.md) -- [Pattern matching overview](../../fundamentals/functional/pattern-matching.md) +- [Pattern matching overview](../../fundamentals/patterns/pattern-matching.md) - [Tutorial: Use pattern matching to build type-driven and data-driven algorithms](../../fundamentals/tutorials/pattern-matching.md) diff --git a/docs/csharp/misc/cs0077.md b/docs/csharp/misc/cs0077.md index 48d6c38428f9f..e764a752fe257 100644 --- a/docs/csharp/misc/cs0077.md +++ b/docs/csharp/misc/cs0077.md @@ -14,7 +14,7 @@ The as operator must be used with a reference type or nullable type ('int' is a The [as](../language-reference/operators/type-testing-and-cast.md#the-as-operator) operator was passed a [value type](../language-reference/builtin-types/value-types.md). Because `as` can return [null](../language-reference/keywords/null.md), it can only be passed a [reference type](../language-reference/keywords/reference-types.md) or a [nullable value type](../language-reference/builtin-types/nullable-value-types.md). -However, using [pattern matching](../fundamentals/functional/pattern-matching.md) with the [is](../language-reference/operators/is.md) operator, we can directly perform type checking and assignments in one step. +However, using [pattern matching](../fundamentals/patterns/pattern-matching.md) with the [is](../language-reference/operators/is.md) operator, we can directly perform type checking and assignments in one step. The following sample generates CS0077: diff --git a/docs/csharp/toc.yml b/docs/csharp/toc.yml index ee0f9e535d015..e388ba15c7895 100644 --- a/docs/csharp/toc.yml +++ b/docs/csharp/toc.yml @@ -113,6 +113,16 @@ items: items: - name: Use string interpolation href: fundamentals/tutorials/string-interpolation.md + - name: Pattern matching + items: + - name: Overview + href: fundamentals/patterns/pattern-matching.md + - name: Declaration, constant, and var patterns + href: fundamentals/patterns/declaration-constant-var-patterns.md + - name: Type patterns + href: fundamentals/patterns/type-patterns.md + - name: Discards and the discard pattern + href: fundamentals/patterns/discards.md - name: Expressions and statements items: - name: Expressions overview @@ -154,10 +164,6 @@ items: # - language features: # - Lambdas # - Positional records as DTOs - - name: Pattern matching - href: fundamentals/functional/pattern-matching.md - - name: Discards - href: fundamentals/functional/discards.md - name: Deconstructing tuples and other types href: fundamentals/functional/deconstruct.md - name: Exceptions and errors diff --git a/docs/csharp/tour-of-csharp/index.yml b/docs/csharp/tour-of-csharp/index.yml index 9de7a28cedd2a..46b24a26979ea 100644 --- a/docs/csharp/tour-of-csharp/index.yml +++ b/docs/csharp/tour-of-csharp/index.yml @@ -56,8 +56,8 @@ landingContent: url: ../fundamentals/types/index.md - text: "Object oriented programming" url: ../fundamentals/object-oriented/index.md - - text: "Functional techniques" - url: ../fundamentals/functional/pattern-matching.md + - text: "Pattern matching" + url: ../fundamentals/patterns/pattern-matching.md - text: "Exceptions" url: ../fundamentals/exceptions/index.md - text: "Coding style" diff --git a/docs/csharp/tour-of-csharp/overview.md b/docs/csharp/tour-of-csharp/overview.md index c007db14acabb..4cd9150b74747 100644 --- a/docs/csharp/tour-of-csharp/overview.md +++ b/docs/csharp/tour-of-csharp/overview.md @@ -76,7 +76,7 @@ C# apps use [exceptions](../fundamentals/exceptions/index.md) to report and hand Some elements of C# might be less familiar. -C# provides [pattern matching](../fundamentals/functional/pattern-matching.md). Those expressions enable you to inspect data and make decisions based on its characteristics. Pattern matching provides a great syntax for control flow based on data. The following code shows how methods for the boolean *and*, *or*, and *xor* operations could be expressed using pattern matching syntax: +C# provides [pattern matching](../fundamentals/patterns/pattern-matching.md). Those expressions enable you to inspect data and make decisions based on its characteristics. Pattern matching provides a great syntax for control flow based on data. The following code shows how methods for the boolean *and*, *or*, and *xor* operations could be expressed using pattern matching syntax: :::code language="csharp" source="./snippets/shared/PatternMatching.cs" id="PatternExamples"::: @@ -121,7 +121,7 @@ Callers can iterate the collection by using an `await foreach` statement: Finally, as part of the .NET ecosystem, you can use [Visual Studio](https://visualstudio.microsoft.com/vs) or [Visual Studio Code](https://code.visualstudio.com) with the [C# Dev Kit](https://code.visualstudio.com/docs/csharp/get-started). These tools provide a rich understanding of C#, including the code you write. They also provide debugging capabilities. > [!TIP] -> To learn more about pattern matching, LINQ, and async programming, see the [functional techniques](../fundamentals/functional/pattern-matching.md), [LINQ overview](../linq/index.md), and [asynchronous programming](../asynchronous-programming/index.md) sections. +> To learn more about pattern matching, LINQ, and async programming, see the [pattern matching](../fundamentals/patterns/pattern-matching.md), [LINQ overview](../linq/index.md), and [asynchronous programming](../asynchronous-programming/index.md) sections. ## Next steps diff --git a/docs/csharp/tour-of-csharp/tips-for-java-developers.md b/docs/csharp/tour-of-csharp/tips-for-java-developers.md index 5dd3abd94e445..eeb394140e476 100644 --- a/docs/csharp/tour-of-csharp/tips-for-java-developers.md +++ b/docs/csharp/tour-of-csharp/tips-for-java-developers.md @@ -81,7 +81,7 @@ Learn more: [Nullable reference types](../fundamentals/null-safety/nullable-refe You can work productively in C# almost immediately because of the similarities. As you progress, learn features and idioms in C# that aren't available in Java: -1. [***Pattern matching***](../fundamentals/functional/pattern-matching.md): Pattern matching enables concise conditional statements and expressions based on the shape of complex data structures. The [`is` statement](../language-reference/operators/is.md) checks if a variable "is" some pattern. The pattern-based [`switch` expression](../language-reference/operators/switch-expression.md) provides a rich syntax to inspect a variable and make decisions based on its characteristics. +1. [***Pattern matching***](../fundamentals/patterns/pattern-matching.md): Pattern matching enables concise conditional statements and expressions based on the shape of complex data structures. The [`is` statement](../language-reference/operators/is.md) checks if a variable "is" some pattern. The pattern-based [`switch` expression](../language-reference/operators/switch-expression.md) provides a rich syntax to inspect a variable and make decisions based on its characteristics. 1. [***String interpolation***](../language-reference/tokens/interpolated.md) and [***raw string literals***](../language-reference/builtin-types/reference-types.md#string-literals): String interpolation enables you to insert evaluated expressions in a string, rather than using positional identifiers. Raw string literals provide a way to minimize escape sequences in text. 1. [***Nullable and non-nullable types***](../fundamentals/null-safety/nullable-reference-types.md): C# supports *nullable value types*, and *nullable reference types* by appending the `?` suffix to a type. For nullable types, the compiler warns you if you don't check for `null` before dereferencing the expression. For non-nullable types, the compiler warns you if you might be assigning a `null` value to that variable. Non-nullable reference types minimize programming errors that throw a . 1. [***Extensions***](../programming-guide/classes-and-structs/extension-methods.md): In C#, you can create members that *extend* a class or interface. Extensions provide new behavior for a type from a library, or all types that implement a given interface. diff --git a/docs/csharp/tour-of-csharp/tips-for-javascript-developers.md b/docs/csharp/tour-of-csharp/tips-for-javascript-developers.md index e1d71a5158b33..8f76f99bfc332 100644 --- a/docs/csharp/tour-of-csharp/tips-for-javascript-developers.md +++ b/docs/csharp/tour-of-csharp/tips-for-javascript-developers.md @@ -83,7 +83,7 @@ if (typeof value === "string") { /* ... */ } if (value is string s) { /* use s */ } ``` -To learn more, see [Pattern matching](../fundamentals/functional/pattern-matching.md). +To learn more, see [Pattern matching](../fundamentals/patterns/pattern-matching.md). ## Runtime model differences @@ -97,7 +97,7 @@ Even though C# and JavaScript look similar in syntax, they run very differently: As you learn C#, you encounter concepts that aren't part of JavaScript. Some of these concepts might be familiar to you if you use TypeScript: 1. [***C# Type System***](../fundamentals/types/index.md): C# is a strongly typed language. Every variable has a type, and that type can't change. You define `class` or `struct` types. You can define [`interface`](../fundamentals/types/interfaces.md) definitions that define behavior implemented by other types. TypeScript includes many of these concepts, but because TypeScript is built on JavaScript, the type system isn't as strict. -1. [***Pattern matching***](../fundamentals/functional/pattern-matching.md): Pattern matching enables concise conditional statements and expressions based on the shape of complex data structures. The [`is` expression](../language-reference/operators/is.md) checks if a variable "is" some pattern. The pattern-based [`switch` expression](../language-reference/operators/switch-expression.md) provides a rich syntax to inspect a variable and make decisions based on its characteristics. +1. [***Pattern matching***](../fundamentals/patterns/pattern-matching.md): Pattern matching enables concise conditional statements and expressions based on the shape of complex data structures. The [`is` expression](../language-reference/operators/is.md) checks if a variable "is" some pattern. The pattern-based [`switch` expression](../language-reference/operators/switch-expression.md) provides a rich syntax to inspect a variable and make decisions based on its characteristics. 1. [***String interpolation***](../language-reference/tokens/interpolated.md) and [***raw string literals***](../language-reference/builtin-types/reference-types.md#string-literals): String interpolation enables you to insert evaluated expressions in a string, rather than using positional identifiers. Raw string literals provide a way to minimize escape sequences in text. 1. [***Nullable and non-nullable types***](../fundamentals/null-safety/nullable-reference-types.md): C# supports *nullable value types* and *nullable reference types* by appending the `?` suffix to a type. For nullable types, the compiler warns you if you don't check for `null` before dereferencing the expression. For non-nullable types, the compiler warns you if you might be assigning a `null` value to that variable. These features can minimize your application throwing a . The syntax might be familiar from TypeScript's use of `?` for optional properties. 1. [***LINQ***](../linq/index.md): Language integrated query (LINQ) provides a common syntax to query and transform data, regardless of its storage. diff --git a/docs/csharp/tour-of-csharp/tips-for-python-developers.md b/docs/csharp/tour-of-csharp/tips-for-python-developers.md index 4f024f24bad7c..3d7582a3ff438 100644 --- a/docs/csharp/tour-of-csharp/tips-for-python-developers.md +++ b/docs/csharp/tour-of-csharp/tips-for-python-developers.md @@ -14,7 +14,7 @@ C# and Python share similar concepts. These familiar constructs help you learn C 1. ***Garbage collection***: Both languages employ automatic memory management through garbage collection. The runtime reclaims the memory from objects that aren't referenced. 1. ***Strongly typed***: Both Python and C# are strongly typed languages. Type coercion doesn't occur implicitly. There are differences described later, as C# is statically typed whereas Python is dynamically typed. 1. ***Async / Await***: Python's `async` and `await` feature was directly inspired by C#'s `async` and `await` support. -1. ***Pattern matching***: Python's `match` expression and pattern matching is similar to C#'s [pattern matching](../fundamentals/functional/pattern-matching.md) `switch` expression. You use them to inspect a complex data expression to determine if it matches a pattern. +1. ***Pattern matching***: Python's `match` expression and pattern matching is similar to C#'s [pattern matching](../fundamentals/patterns/pattern-matching.md) `switch` expression. You use them to inspect a complex data expression to determine if it matches a pattern. 1. ***Statement keywords***: Python and C# share many keywords, such as `if`, `else`, `while`, `for`, and many others. While not all syntax is the same, there's enough similarity that you can read C# if you know Python. ## Syntax at a glance diff --git a/docs/csharp/tour-of-csharp/tutorials/branches-and-loops.md b/docs/csharp/tour-of-csharp/tutorials/branches-and-loops.md index ec261735c7d7a..caee7d40d426d 100644 --- a/docs/csharp/tour-of-csharp/tutorials/branches-and-loops.md +++ b/docs/csharp/tour-of-csharp/tutorials/branches-and-loops.md @@ -215,7 +215,7 @@ Continue to the next tutorial in this series: Or explore related topics in C# Fundamentals: -- [Pattern matching](../../fundamentals/functional/pattern-matching.md) — A powerful alternative to complex `if`/`else` chains. +- [Pattern matching](../../fundamentals/patterns/pattern-matching.md) — A powerful alternative to complex `if`/`else` chains. - [Methods and program structure](../../fundamentals/program-structure/index.md) — Learn how to organize the methods you created in this tutorial. - [What you can build with C#](../what-you-can-build.md) — See the kinds of apps you can create with what you're learning. - [Selection statements](../../language-reference/statements/selection-statements.md) diff --git a/docs/csharp/tour-of-csharp/tutorials/pattern-matching.md b/docs/csharp/tour-of-csharp/tutorials/pattern-matching.md index 3054c2eeafadc..21c50932bcca6 100644 --- a/docs/csharp/tour-of-csharp/tutorials/pattern-matching.md +++ b/docs/csharp/tour-of-csharp/tutorials/pattern-matching.md @@ -56,7 +56,7 @@ To start a GitHub Codespace with the tutorial environment, open a browser window You could similarly construct the preceding sample by using the `==` operator to test that two `string` values are equal. Comparing a variable to a constant is a basic building block for pattern matching. Let's explore more of the building blocks that are part of pattern matching. > [!TIP] -> **Learn more:** Read about [pattern matching](../../fundamentals/functional/pattern-matching.md) in the C# Fundamentals section for a comprehensive overview of all pattern types. +> **Learn more:** Read the [pattern matching overview](../../fundamentals/patterns/pattern-matching.md) for an introduction and links to each pattern category. For complete syntax and rules, see the [patterns reference](../../language-reference/operators/patterns.md). ## Enum matches @@ -141,7 +141,7 @@ To finish this tutorial, explore one more building block for pattern matching: t Pattern matching provides a vocabulary to compare an expression against characteristics. Patterns can include the expression's type, values of types, property values, and combinations of them. Comparing expressions against a pattern can be clearer than multiple `if` comparisons. You explored some of the patterns you can use to match expressions. There are many more ways to use pattern matching in your applications. As you explore, you can learn more about pattern matching in C# in the following articles: -- [Pattern matching in C#](../../fundamentals/functional/pattern-matching.md) +- [Pattern matching in C#](../../fundamentals/patterns/pattern-matching.md) - [Explore pattern matching tutorial](../../tutorials/patterns-objects.md) - [Pattern matching scenario](../../fundamentals/tutorials/pattern-matching.md) - [The C# type system](../../fundamentals/types/index.md) — Understand the types you matched against in this tutorial. diff --git a/docs/csharp/whats-new/csharp-version-history.md b/docs/csharp/whats-new/csharp-version-history.md index f17e20d4a66ef..a69a33f8dceed 100644 --- a/docs/csharp/whats-new/csharp-version-history.md +++ b/docs/csharp/whats-new/csharp-version-history.md @@ -149,7 +149,7 @@ C# 9 continues three of the themes from previous releases: removing ceremony, se The introduction of [`records`](../language-reference/builtin-types/record.md) provides a concise syntax for reference types that follow value semantics for equality. You use these types to define data containers that typically define minimal behavior. [Init-only setters](../language-reference/keywords/init.md) provide the capability for nondestructive mutation (`with` expressions) in records. C# 9 also adds [covariant return types](~/_csharpstandard/standard/classes.md#1565-override-methods) so that derived records can override virtual methods and return a type derived from the base method's return type. -The [pattern matching](../fundamentals/functional/pattern-matching.md) capabilities expanded in several ways. Numeric types now support *range patterns*. Patterns can be combined using `and`, `or`, and `not` patterns. Parentheses can be added to clarify more complex patterns: +The [pattern matching](../fundamentals/patterns/pattern-matching.md) capabilities expanded in several ways. Numeric types now support *range patterns*. Patterns can be combined using `and`, `or`, and `not` patterns. Parentheses can be added to clarify more complex patterns: C# 9 includes new pattern matching improvements: @@ -303,7 +303,7 @@ C# version 7.0 was released with Visual Studio 2017. This version has some evolu - Out variables - [Tuples and deconstruction](../language-reference/builtin-types/value-tuples.md) -- [Pattern matching](../fundamentals/functional/pattern-matching.md) +- [Pattern matching](../fundamentals/patterns/pattern-matching.md) - [Local functions](../programming-guide/classes-and-structs/local-functions.md) - [Expanded expression bodied members](../language-reference/operators/lambda-operator.md#expression-body-definition) - [Ref locals](../language-reference/statements/declarations.md#reference-variables) @@ -311,7 +311,7 @@ C# version 7.0 was released with Visual Studio 2017. This version has some evolu Other features included: -- [Discards](../fundamentals/functional/discards.md) +- [Discards](../fundamentals/patterns/discards.md) - [Binary Literals and Digit Separators](../language-reference/builtin-types/integral-numeric-types.md#integer-literals) - [Throw expressions](../language-reference/statements/exception-handling-statements.md#the-throw-expression) diff --git a/docs/csharp/whats-new/tutorials/closed-hierarchies.md b/docs/csharp/whats-new/tutorials/closed-hierarchies.md index ae76e678cafa2..5339f899e980c 100644 --- a/docs/csharp/whats-new/tutorials/closed-hierarchies.md +++ b/docs/csharp/whats-new/tutorials/closed-hierarchies.md @@ -144,5 +144,5 @@ You built the sensor model of a smart-home telemetry monitor and, in the process ## Related content - [Union types tutorial](unions.md) -- [Pattern matching overview](../../fundamentals/functional/pattern-matching.md) +- [Pattern matching overview](../../fundamentals/patterns/pattern-matching.md) - [What's new in C# 15](../csharp-15.md) diff --git a/docs/csharp/whats-new/tutorials/unions.md b/docs/csharp/whats-new/tutorials/unions.md index 458a948b133c0..a83e6d2349b7c 100644 --- a/docs/csharp/whats-new/tutorials/unions.md +++ b/docs/csharp/whats-new/tutorials/unions.md @@ -159,5 +159,5 @@ You built the readings layer of a smart-home telemetry monitor and, in the proce ## Related content - [Closed hierarchies tutorial](closed-hierarchies.md) -- [Pattern matching overview](../../fundamentals/functional/pattern-matching.md) +- [Pattern matching overview](../../fundamentals/patterns/pattern-matching.md) - [What's new in C# 15](../csharp-15.md) diff --git a/docs/fundamentals/code-analysis/quality-rules/ca1801.md b/docs/fundamentals/code-analysis/quality-rules/ca1801.md index aabe7322e8984..d116cd8c70fb5 100644 --- a/docs/fundamentals/code-analysis/quality-rules/ca1801.md +++ b/docs/fundamentals/code-analysis/quality-rules/ca1801.md @@ -45,7 +45,7 @@ This rule does not examine the following kinds of methods: - Methods declared with the `extern` (`Declare` statement in Visual Basic) modifier. -This rule does not flag parameters that are named with the [discard](../../../csharp/fundamentals/functional/discards.md) symbol, for example, `_`, `_1`, and `_2`. This reduces warning noise on parameters that are needed for signature requirements, for example, a method used as a delegate, a parameter with special attributes, or a parameter whose value is implicitly accessed at runtime by a framework but is not referenced in code. +This rule does not flag parameters that are named with the [discard](../../../csharp/fundamentals/patterns/discards.md) symbol, for example, `_`, `_1`, and `_2`. This reduces warning noise on parameters that are needed for signature requirements, for example, a method used as a delegate, a parameter with special attributes, or a parameter whose value is implicitly accessed at runtime by a framework but is not referenced in code. > [!NOTE] > This rule has been deprecated in favor of [IDE0060](../style-rules/ide0060.md). For information about how to enforce the IDE0060 analyzer at build, see [code-style analysis](../overview.md#code-style-analysis). diff --git a/docs/fundamentals/code-analysis/style-rules/ide0019.md b/docs/fundamentals/code-analysis/style-rules/ide0019.md index 72359d50aa314..f2da51c7a2573 100644 --- a/docs/fundamentals/code-analysis/style-rules/ide0019.md +++ b/docs/fundamentals/code-analysis/style-rules/ide0019.md @@ -26,7 +26,7 @@ dev_langs: ## Overview -This style rule concerns the use of C# [pattern matching](../../../csharp/fundamentals/functional/pattern-matching.md) over an `as` expression followed by a `null` check. This rule is similar to [IDE0260](ide0078-ide0260.md), which flags the use of an `as` expression followed by a member read through the null-conditional operator. +This style rule concerns the use of C# [pattern matching](../../../csharp/fundamentals/patterns/pattern-matching.md) over an `as` expression followed by a `null` check. This rule is similar to [IDE0260](ide0078-ide0260.md), which flags the use of an `as` expression followed by a member read through the null-conditional operator. ## Options @@ -83,6 +83,6 @@ For more information, see [How to suppress code analysis warnings](../suppress-w ## See also - [Use pattern matching (IDE0078 and IDE0260)](ide0078-ide0260.md) -- [Pattern matching in C#](../../../csharp/fundamentals/functional/pattern-matching.md) +- [Pattern matching in C#](../../../csharp/fundamentals/patterns/pattern-matching.md) - [Code style language rules](language-rules.md) - [Code style rules reference](index.md) diff --git a/docs/fundamentals/code-analysis/style-rules/ide0020-ide0038.md b/docs/fundamentals/code-analysis/style-rules/ide0020-ide0038.md index 997c1acc0b533..3cab70a7f415b 100644 --- a/docs/fundamentals/code-analysis/style-rules/ide0020-ide0038.md +++ b/docs/fundamentals/code-analysis/style-rules/ide0020-ide0038.md @@ -39,7 +39,7 @@ This article describes two related rules, `IDE0020` and `IDE0038`. ## Overview -This style rule concerns the use of C# [pattern matching](../../../csharp/fundamentals/functional/pattern-matching.md), for example, `o is int i`, over an `is` check followed by a cast, for example, `if (o is int) { ... (int)o ... }`. Enable either `IDE0020` or `IDE0038` based on whether or not the cast expression should be saved into a separate local variable: +This style rule concerns the use of C# [pattern matching](../../../csharp/fundamentals/patterns/pattern-matching.md), for example, `o is int i`, over an `is` check followed by a cast, for example, `if (o is int) { ... (int)o ... }`. Enable either `IDE0020` or `IDE0038` based on whether or not the cast expression should be saved into a separate local variable: - `IDE0020`: Cast expression _is_ saved into a local variable. For example, `if (o is int) { var i = (int)o; }` saves the result of `(int)o` in a local variable. - `IDE0038`: Cast expression _is not_ saved into a local variable. For example, `if (o is int) { if ((int)o == 1) { ... } }` does not save the result of `(int)o` into a local variable. @@ -96,6 +96,6 @@ For more information, see [How to suppress code analysis warnings](../suppress-w ## See also -- [Pattern matching in C#](../../../csharp/fundamentals/functional/pattern-matching.md) +- [Pattern matching in C#](../../../csharp/fundamentals/patterns/pattern-matching.md) - [Code style language rules](language-rules.md) - [Code style rules reference](index.md) diff --git a/docs/fundamentals/code-analysis/style-rules/ide0058.md b/docs/fundamentals/code-analysis/style-rules/ide0058.md index 3f0bafa69cbed..ed54b4fea1ab7 100644 --- a/docs/fundamentals/code-analysis/style-rules/ide0058.md +++ b/docs/fundamentals/code-analysis/style-rules/ide0058.md @@ -45,7 +45,7 @@ You can take one of the following actions to fix this violation: - If the expression has no side effects, remove the entire statement. This improves performance by avoiding unnecessary computation. -- If the expression has side effects, replace the left side of the assignment with a [discard](../../../csharp/fundamentals/functional/discards.md) (C# only) or a local variable that's never used. This improves code clarity by explicitly showing the intent to discard an unused value. +- If the expression has side effects, replace the left side of the assignment with a [discard](../../../csharp/fundamentals/patterns/discards.md) (C# only) or a local variable that's never used. This improves code clarity by explicitly showing the intent to discard an unused value. ```csharp _ = Compute(); diff --git a/docs/fundamentals/code-analysis/style-rules/ide0059.md b/docs/fundamentals/code-analysis/style-rules/ide0059.md index a4712a7f63340..3b564cbf56443 100644 --- a/docs/fundamentals/code-analysis/style-rules/ide0059.md +++ b/docs/fundamentals/code-analysis/style-rules/ide0059.md @@ -48,7 +48,7 @@ You can take one of the following actions to fix this violation: int v = Compute2(); ``` -- If the expression on the right side of the assignment has side effects, replace the left side of the assignment with a [discard](../../../csharp/fundamentals/functional/discards.md) (C# only) or a local variable that's never used. Discards improve code clarity by explicitly showing the intent to discard an unused value. +- If the expression on the right side of the assignment has side effects, replace the left side of the assignment with a [discard](../../../csharp/fundamentals/patterns/discards.md) (C# only) or a local variable that's never used. Discards improve code clarity by explicitly showing the intent to discard an unused value. ```csharp _ = Compute(); diff --git a/docs/fundamentals/code-analysis/style-rules/ide0060.md b/docs/fundamentals/code-analysis/style-rules/ide0060.md index 93f85f7a0b48c..e8f40b754bd4b 100644 --- a/docs/fundamentals/code-analysis/style-rules/ide0060.md +++ b/docs/fundamentals/code-analysis/style-rules/ide0060.md @@ -29,7 +29,7 @@ dev_langs: This rule flags unused parameters. -This rule does not flag parameters that are named with the [discard](../../../csharp/fundamentals/functional/discards.md) symbol `_`. In addition, the rule ignores parameters that are named with the discard symbol followed by an integer, for example, `_1`. This behavior reduces warning noise on parameters that are needed for signature requirements, for example, a method used as a delegate, a parameter with special attributes, or a parameter whose value is implicitly accessed at runtime by a framework but is not referenced in code. +This rule does not flag parameters that are named with the [discard](../../../csharp/fundamentals/patterns/discards.md) symbol `_`. In addition, the rule ignores parameters that are named with the discard symbol followed by an integer, for example, `_1`. This behavior reduces warning noise on parameters that are needed for signature requirements, for example, a method used as a delegate, a parameter with special attributes, or a parameter whose value is implicitly accessed at runtime by a framework but is not referenced in code. ## Options diff --git a/docs/fundamentals/code-analysis/style-rules/ide0078-ide0260.md b/docs/fundamentals/code-analysis/style-rules/ide0078-ide0260.md index 9686bad0ef0dd..d3381d4be8a13 100644 --- a/docs/fundamentals/code-analysis/style-rules/ide0078-ide0260.md +++ b/docs/fundamentals/code-analysis/style-rules/ide0078-ide0260.md @@ -39,7 +39,7 @@ This article describes two related rules, `IDE0078` and `IDE0260`. ## Overview -This style rule concerns the use of C# [pattern matching](../../../csharp/fundamentals/functional/pattern-matching.md) constructs. +This style rule concerns the use of C# [pattern matching](../../../csharp/fundamentals/patterns/pattern-matching.md) constructs. IDE0260 specifically flags the use of an `as` expression followed by a member read through the [null-conditional operator](../../../csharp/language-reference/operators/member-access-operators.md#null-conditional-operators--and-). This rule is similar to [IDE0019](ide0019.md), which flags the use of an `as` expression followed by a `null` check. diff --git a/docs/fundamentals/code-analysis/style-rules/ide0083.md b/docs/fundamentals/code-analysis/style-rules/ide0083.md index d0199bcac0f59..41dad7374d542 100644 --- a/docs/fundamentals/code-analysis/style-rules/ide0083.md +++ b/docs/fundamentals/code-analysis/style-rules/ide0083.md @@ -26,7 +26,7 @@ dev_langs: ## Overview -This style rule concerns the use of C# 9.0 [`not` pattern](../../../csharp/fundamentals/functional/pattern-matching.md), when possible. +This style rule concerns the use of C# 9.0 [`not` pattern](../../../csharp/fundamentals/patterns/pattern-matching.md), when possible. ## Options