Skip to content

Commit

Permalink
Csharp pattern matching (dotnet#1315)
Browse files Browse the repository at this point in the history
* outline done

* Finish first draft and samples.

* remove wrench from Pattern Matching article.

* add section on definitely assigned.

* proofread

* first review pass.

* respond to feedback.

* respond to feedback

Next is reworking the sample.

* Update samples and explanations.

Based on review feedback.
Describe the different patterns better.
Show how the new syntax is an extension of existing statements.
Contrast this code with a OO approach better, and emphasize that one
advantage of this approach is separating data and behavior.

* fix a build error

* outline done

* Finish first draft and samples.

* remove wrench from Pattern Matching article.

* add section on definitely assigned.

* proofread

* first review pass.

* respond to feedback.

* respond to feedback

Next is reworking the sample.

* Update samples and explanations.

Based on review feedback.
Describe the different patterns better.
Show how the new syntax is an extension of existing statements.
Contrast this code with a OO approach better, and emphasize that one
advantage of this approach is separating data and behavior.

* fix a build error

* update publish date.

* respond to feedback.

* update per feedback

* Respond to feedback.

* Respond to final review.
  • Loading branch information
BillWagner authored Feb 14, 2017
1 parent 1c5bef6 commit 9b1bca1
Show file tree
Hide file tree
Showing 9 changed files with 577 additions and 18 deletions.
232 changes: 215 additions & 17 deletions docs/csharp/pattern-matching.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,31 +2,229 @@
title: Pattern Matching | C# Guide
description: Learn about pattern matching expressions in C#
keywords: .NET, .NET Core, C#
ms.date: 10/04/2016
ms.date: 01/24/2017
ms.author: wiwagn
ms.topic: article
ms.prod: .net
ms.technology: devlang-csharp
ms.devlang: csharp
ms.assetid: 1e575c32-2e2b-4425-9dca-7d118f3ed15b
---

# 🔧 Pattern Matching
# Pattern Matching #

> **Note**
>
> This topic hasn’t been written yet!
>
> We welcome your input to help shape the scope and approach. You can track the status and provide input on this
> [issue](https://github.com/dotnet/docs/issues/1114) at GitHub.
>
> If you would like to review early drafts and outlines of this topic, please leave a note with your contact information in the issue.
>
> Learn more about how you can contribute on [GitHub](https://github.com/dotnet/docs/blob/master/CONTRIBUTING.md).
>
Patterns test that a value has a certain *shape*, and can *extract*
information from the value when it has the matching shape. Pattern
matching provides more concise syntax for algorithms you already use
today. You already create pattern matching algorithms using existing
syntax. You write `if` or `switch` statements that test values. Then,
when those statements match, you extract and use information from that
value. The new syntax elements are extensions to statements you are already
familiar with: `is` and `switch`. These new extensions combine testing
a value and extracting that information.

<!--
In this topic, we'll look at the new syntax to show you how it enables
readable, concise code. Pattern matching enables idioms where data and
the code are separated, unlike object oriented designs where data
and the methods that manipulate them are tightly coupled.

<< Build the sample in an additive way >>
<< Do the shape thing for the https://docs.microsoft.com/en-us/dotnet/articles/fsharp/language-reference/discriminated-unions shapes >>
To illustrate these new idioms, let's work with structures that represent
geometric shapes using pattern matching statements. You are probably
familiar with building class hierarchies and creating
[virtual methods and overridden methods](methods.md#inherited) to
customize object behavior based on the runtime type of the object.

Those techniques aren't possible for data that isn't structured in a class
hierarchy. When data and methods are separate, you need other tools. The new
*pattern matching* constructs enable cleaner syntax to examine data
and manipulate control flow based on any condition of that data. You already
write `if` statements and `switch` that test a variable's value. You write `is`
statements that test a variable's type. *Pattern matching* adds new capabilities
to those statements.

In this topic, you'll build a method that computes the area of
different geometric shapes. But, you'll do it without resorting to object
oriented techniques and building a class hierarchy for the different shapes.
You'll use *pattern matching* instead. To further emphasize that we're not
using inheritance, you'll make each shape a `struct` instead of a class.
Note that different `struct` types cannot specify a common user defined
base type, so inheritance is not a possible design.
As you go through this sample, contrast this code with how it would
be structured as an object hierarchy. When the data you must
query and manipulate is not a class hierarchy, pattern matching enables
very elegant designs.

Rather than starting with an abstract shape definition and adding different
specific shape classes, let's start instead with simple data only definitions
for each of the geometric shapes:

[!code-csharp[ShapeDefinitions](../../samples/csharp/PatternMatching/PatternMatching/Shapes.cs#01_ShapeDefinitions "Shape definitions")]

From these structures, let's write a method that computes the area
of some shape.

## The `is` type pattern expression

Before C# 7, you'd need to test each type in a series of `if` and `is`
statements:

[!code-csharp[ClassicIsExpression](../../samples/csharp/PatternMatching/PatternMatching/GeometricUtilities.cs#02_ClassicIsExpression "Classic type pattern using is")]

That code above is a classic expression of the *type pattern*: You're testing a variable
to determine its type and taking a different action based on that type.

This code becomes simpler using extensions to the `is` expression to assign
a variable if the test succeeds:

[!code-csharp[IsPatternExpression](../../samples/csharp/PatternMatching/PatternMatching/GeometricUtilities.cs#03_IsPatternExpression "is pattern expression")]

In this updated version, the `is` expression both tests the variable and assigns
it to a new variable of the proper type. Also, notice that this version includes
the `Rectangle` type, which is a `struct`. The new `is` expression works with
value types as well as reference types.

Language rules for pattern matching expressions help you avoid misusing
the results of a match expression. In the example above, the variables `s`,
`c`, and `r` are only in scope and definitely assigned when the respective
pattern match expressions have `true` results. If you try to use either
variable in another location, your code generates compiler errors.

Let's examine both of those rules in detail, beginning with scope. The variable
`c` is in scope only in the `else` branch of the first `if` statement. The variable
`s` is in scope in the method `ComputeArea`. That's because each
branch of an `if` statement establishes a separate scope for variables. However, the `if` statement
itself does not. That means variables declared in the `if` statement are in the
same scope as the `if` statement (the method in this case.) This behavior is not
specific to pattern matching, but is the defined behavior for variable scopes
and `if` and `else` statements.

The variables `c` and `s` are assigned when the respective `if` statements are true
because of the definitely assigned when true mechanism.

> [!TIP]
> The samples in this topic use the recommended construct where
> a pattern match `is` expression definitely assigns the match
> variable in the `true` branch of the `if` statement.
> You could reverse the logic by saying `if (!(shape is Square s))`
> and the variable `s` would be definitely assigned only in the
> `false` branch. While this is valid C#, it is not recommended
> because it is more confusing to follow the logic.
These rules mean that you are unlikely to accidentally access the result
of a pattern match expression when that pattern was not met.

## Using pattern matching `switch` statements

As time goes on, you may need to support other shape types. As the number
of conditions you are testing grows, you'll find that using the `is` pattern
matching expressions can become cumbersome. In addition to requiring `if`
statements on each type you want to check, the `is` expressions are limited
to testing if the input matches a single type. In this case, you'll find that the `switch` pattern
matching expressions becomes a better choice.

The traditional `switch`
statement was a pattern expression: it supported the constant pattern.
You could compare a variable to any constant used in a `case` statement:

[!code-csharp[ClassicSwitch](../../samples/csharp/PatternMatching/PatternMatching/GeometricUtilities.cs#04_ClassicSwitch "Classic switch statement")]

The only pattern supported by the `switch` statement was the constant
pattern. It was further limited to numeric types and the `string` type.
Those restrictions have been removed, and you can now write a `switch`
statement using the type pattern:

[!code-csharp[Switch Type Pattern](../../samples/csharp/PatternMatching/PatternMatching/GeometricUtilities.cs#05_SwitchTypePattern "Compute with `switch` expression")]

The pattern matching `switch` statement uses familiar syntax to developers
who have used the traditional C-style `switch` statement. Each `case` is evaluated
and the code beneath the condition that matches the input variable is
executed. Code execution cannot "fall through" from one case expression
to the next; the syntax of the `case` statement requires that each `case`
end with a `break`, `return`, or `goto`.

> [!NOTE]
> The `goto` statements to jump to another label are valid only
> for the constant pattern, the classic switch statement.
There are important new rules governing the `switch` statement. The restrictions
on the type of the variable in the `switch` expression have been removed.
Any type, such as `object` in this example, may be used. The case expressions
are no longer limited to constant values. Removing that limitation means
that reordering `switch` sections may change a program's behavior.

When limited to constant values, no more than one `case`
label could match the value of the `switch` expression. Combine that with the
rule that every `switch` section must not fall through to the next section, and
it followed that the
`switch` sections could be rearranged in any order without affecting behavior.
Now, with more generalized `switch` expressions, the order of each section
matters. The `switch` expressions are evaluated in textual order. Execution
transfers to the first `switch` label that matches the `switch` expression.
Note that the `default` case will only be executed if no other
case labels match. The `default` case is evaluated last, regardless
of its textual order. If there is no `default` case, and none of the
other `case` statements match, execution continues at the statement
following the `switch` statement. None of the `case` labels code is
executed.

## `when` clauses in `case` expressions

You can make special cases for those shapes that have 0 area by using
a `when` clause on the `case` label. A square with a side length of 0, or
a circle with a radius of 0 has a 0 area. You specify that condition
using a `when` clause on the `case` label:

[!code-csharp[ComputeDegenerateShapes](../../samples/csharp/PatternMatching/PatternMatching/GeometricUtilities.cs#07_ComputeDegenerateShapes "Compute shapes with 0 area")]

This change demonstrates a few important points about the new syntax. First,
multiple `case` labels can be applied to one `switch` section. The statement
block is executed when any of those labels is `true`. In this instance,
if the `switch` expression is either a circle or a square with 0 area, the
method returns the constant 0.

This example introduces two different variables in the two `case` labels
for the first `switch` block. Notice that the statements in this `switch` block
do not use either the variables `c` (for the circle) or `s` (for the square).
Neither of those variables is definitely assigned in this `switch` block.
If either of these cases match, clearly one of the variables has been assigned.
However, it is impossible to tell *which* has been assigned at compile-time,
because either case could match at runtime. For that reason,
most times when you use multiple `case` labels for the same block, you won't
introduce a new variable in the `case` statement, or you will only use the
variable in the `when` clause.

Having added those shapes with 0 area, let's add a couple more shape types:
a rectangle and a triangle:

[!code-csharp[AddRectangleAndTriangle](../../samples/csharp/PatternMatching/PatternMatching/GeometricUtilities.cs#09_AddRectangleAndTriangle "Add rectangle and triangle")]

This set of changes adds `case` labels for the degenerate case, and labels
and blocks for each of the new shapes.

Finally, you can add a `null` case to ensure the argument is not `null`:

[!code-csharp[NullCase](../../samples/csharp/PatternMatching/PatternMatching/GeometricUtilities.cs#10_NullCase "Add null case")]

The special case for the `null` pattern is interesting because the constant `null` does
not have a type, but can be converted to any reference type or nullable
type.

## Conclusions

*Pattern Matching constructs* enable you to easily manage control flow
among different variables and types that are not related by an inheritance
hierarchy. You can also control logic to use any condition you test on
the variable. It enables patterns and idioms that you'll need more often
as you build more distributed applications, where data and the methods that
manipulate that data are separate. You'll notice that the shape structs
used in this sample do not contain any methods, just read-only properties.
Pattern Matching works with any data type. You write expressions that examine
the object, and make control flow decisions based on those conditions.

Compare the code from this sample with the design that would follow from
creating a class hierarchy for an abstract `Shape` and specific derived
shapes each with their own implementation of a virtual method to calculate
the area. You'll often find that pattern matching expressions can be a very
useful tool when you are working with data and want to separate the data
storage concerns from the behavior concerns.

-->
2 changes: 1 addition & 1 deletion docs/toc.md
Original file line number Diff line number Diff line change
Expand Up @@ -249,7 +249,7 @@
### [Language Integrated Query (LINQ)](csharp/linq/)
### [Asynchronous programming](csharp/async.md)
### [🔧 Parallel programming](csharp/parallel.md)
### [🔧 Pattern Matching](csharp/pattern-matching.md)
### [Pattern Matching](csharp/pattern-matching.md)
### [Expression Trees](csharp/expression-trees.md)
#### [Expression Trees Explained](csharp/expression-trees-explained.md)
#### [Framework Types Supporting Expression Trees](csharp/expression-classes.md)
Expand Down
22 changes: 22 additions & 0 deletions samples/csharp/PatternMatching/PatternMatching.sln
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
VisualStudioVersion = 15.0.26006.2
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PatternMatching", "PatternMatching\PatternMatching.csproj", "{6FD56CA8-402F-4897-8C1F-AD830211918A}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{6FD56CA8-402F-4897-8C1F-AD830211918A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{6FD56CA8-402F-4897-8C1F-AD830211918A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{6FD56CA8-402F-4897-8C1F-AD830211918A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{6FD56CA8-402F-4897-8C1F-AD830211918A}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
EndGlobal
6 changes: 6 additions & 0 deletions samples/csharp/PatternMatching/PatternMatching/App.config
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5.2" />
</startup>
</configuration>
Loading

0 comments on commit 9b1bca1

Please sign in to comment.