パターン マッチPattern Matching

パターンでは、値に特定の "図形" を含まれているかどうかをテストし、一致する図形が含まれている場合にその値から情報を "抽出" することができます。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. 値をテストする if ステートメントまたは switch ステートメントを記述します。You write if or switch statements that test values. その後、これらのステートメントで一致する値が見つかると、その値から情報を抽出して使用します。Then, when those statements match, you extract and use information from that value. 新しい構文要素は、既に使い慣れているステートメントの拡張機能 isswitch です。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.

これらの新しい表現形式を説明するために、パターン マッチングのステートメントを使用して幾何学的図形を表す構造体を見ていきましょう。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 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. 変数の値をテストする if ステートメントと switch ステートメントを既に記述しました。You already write if statements and switch that test a variable's value. また、変数の型をテストする is ステートメントも記述しました。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. 継承を使用していないことをさらに強調するために、各図形をクラスではなく struct にします。To further emphasize that we're not using inheritance, you'll make each shape a struct instead of a class. struct 型が異なると、共通のユーザー定義基本データ型を指定できないため、継承は可能な設計ではありません。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:

public class Square
{
    public double Side { get; }

    public Square(double side)
    {
        Side = side;
    }
}
public class Circle
{
    public double Radius { get; }

    public Circle(double radius)
    {
        Radius = radius;
    }
}
public struct Rectangle
{
    public double Length { get; }
    public double Height { get; }

    public Rectangle(double length, double height)
    {
        Length = length;
        Height = height;
    }
}

public struct Triangle
{
    public double Base { get; }
    public double Height { get; }

    public Triangle(double @base, double height)
    {
        Base = @base;
        Height = height;
    }
}

これらの構造から、特定の図形の面積を計算するメソッドを記述してみましょう。From these structures, let's write a method that computes the area of some shape.

is 型パターンの式The is type pattern expression

C# 7 より前では、一連の if ステートメントと is ステートメント内のそれぞれの型をテストする必要がありました。Before C# 7, you'd need to test each type in a series of if and is statements:

public static double ComputeArea(object shape)
{
    if (shape is Square)
    {
        var s = shape as Square;
        return s.Side * s.Side;
    } else if (shape is Circle)
    {
        var c = shape as Circle;
        return c.Radius * c.Radius * Math.PI;
    }
    // elided
    throw new ArgumentException(
        message: "shape is not a recognized shape",
        paramName: nameof(shape));
}

上記のコードは、従来の "型パターン" の式です。この場合、変数をテストしてその型を判別し、その型に基づいて別のアクションを実行しています。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.

このコードは、テストが成功した場合に is 式の拡張機能を使用して変数を代入することで、より簡潔になります。This code becomes simpler using extensions to the is expression to assign a variable if the test succeeds:

public static double ComputeAreaModernIs(object shape)
{
    if (shape is Square s)
        return s.Side * s.Side;
    else if (shape is Circle c)
        return c.Radius * c.Radius * Math.PI;
    else if (shape is Rectangle r)
        return r.Height * r.Length;
    // elided
    throw new ArgumentException(
        message: "shape is not a recognized shape",
        paramName: nameof(shape));
}

この更新したバージョンでは、変数のテストと適切な型の新しい変数への代入の両方を is 式が実行します。In this updated version, the is expression both tests the variable and assigns it to a new variable of the proper type. また、このバージョンには struct である Rectangle 型が含まれていることに注意してください。Also, notice that this version includes the Rectangle type, which is a struct. 新しい is 式は、値型と参照型で動作します。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. 上記の例では、変数 scr はスコープ内のみに存在し、それぞれのパターン マッチング式の結果が true のときに確実に代入されます。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.

この 2 つの規則を詳しく調べてみましょう。まずはスコープです。Let's examine both of those rules in detail, beginning with scope. c 変数は、最初の if ステートメントの else 分岐のスコープ内のみにあります。The variable c is in scope only in the else branch of the first if statement. s 変数は、メソッド ComputeArea のスコープ内にあります。The variable s is in scope in the method ComputeArea. これは、if ステートメントの各分岐によって変数に個別のスコープが確立されるためです。That's because each branch of an if statement establishes a separate scope for variables. ただし、if ステートメント自体はスコープを確立しません。However, the if statement itself does not. つまり、if ステートメントで宣言された変数は、if ステートメント (この場合はメソッド) と同じスコープにあります。この動作はパターン マッチングに固有のものではありませんが、変数スコープ、if ステートメント、else ステートメントに定義されている動作です。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.

c 変数と s 変数には、"true のときに確実に代入する" メカニズムにより、それぞれの if ステートメントが true のときに代入されます。The variables c and s are assigned when the respective if statements are true because of the definitely assigned when true mechanism.

ヒント

このトピックのサンプルでは、推奨されるコンストラクトを使用しています。この場合、パターン マッチングの is 式により、if ステートメントの true 分岐で一致変数に確実に代入されます。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. このロジックは、if (!(shape is Square s)) を記述することで反転できます。s 変数は、false 分岐でのみ、確実に代入されます。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. これは有効な C# ですが、ロジックの追跡がわかりにくくなるため、お勧めしません。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.

パターン マッチング switch ステートメントの使用Using pattern matching switch statements

時間が経過するにつれて、他の図形の種類をサポートすることが必要になる場合があります。As time goes on, you may need to support other shape types. テストする条件の数が増えるにつれ、is パターン マッチング式の使用が煩雑になることもわかります。As the number of conditions you are testing grows, you'll find that using the is pattern matching expressions can become cumbersome. 確認する各型に対して if ステートメントが必要になるほか、is 式は、入力が単一の型と一致するかどうかをテストすることに限定されます。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. この場合は、switch パターン マッチング式が適していることがわかります。In this case, you'll find that the switch pattern matching expressions becomes a better choice.

従来の switch ステートメントはパターン式であり、定数パターンをサポートしていました。The traditional switch statement was a pattern expression: it supported the constant pattern. 変数は、case ステートメントで使用されている任意の定数と比較することができました。You could compare a variable to any constant used in a case statement:

public static string GenerateMessage(params string[] parts)
{
    switch (parts.Length)
    {
        case 0:
            return "No elements to the input";
        case 1:
            return $"One element: {parts[0]}";
        case 2:
            return $"Two elements: {parts[0]}, {parts[1]}";
        default:
            return $"Many elements. Too many to write";
    }
}

switch ステートメントでサポートされるパターンは定数パターンのみでした。The only pattern supported by the switch statement was the constant pattern. さらに、このパターンは、数値型と string 型に限定されていました。It was further limited to numeric types and the string type. このような制限事項がなくなったため、型パターンを使用して switch ステートメントを記述できるようになりました。Those restrictions have been removed, and you can now write a switch statement using the type pattern:

public static double ComputeAreaModernSwitch(object shape)
{
    switch (shape)
    {
        case Square s:
            return s.Side * s.Side;
        case Circle c:
            return c.Radius * c.Radius * Math.PI;
        case Rectangle r:
            return r.Height * r.Length;
        default:
            throw new ArgumentException(
                message: "shape is not a recognized shape",
                paramName: nameof(shape));
    }
}

パターン マッチングの switch ステートメントでは、従来の C 形式の switch ステートメントを使用してきた開発者にとって使い慣れた構文を使用します。The pattern matching switch statement uses familiar syntax to developers who have used the traditional C-style switch statement. それぞれの case が評価され、入力変数に一致する条件の下にあるコードが実行されます。Each case is evaluated and the code beneath the condition that matches the input variable is executed. コードの実行では、ある case 式から次の case 式に "フォール スルーする" ことはできません。つまり、case ステートメントの構文では、それぞれの casebreakreturn、または goto で終わる必要があります。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.

注意

別のラベルに移動する goto ステートメントは、定数パターン (従来の switch ステートメント) のみに有効です。The goto statements to jump to another label are valid only for the constant pattern, the classic switch statement.

switch ステートメントを制御する重要な新しい規則があります。There are important new rules governing the switch statement. switch 式の変数の型に関する制限はなくなりました。The restrictions on the type of the variable in the switch expression have been removed. この例の object のように、どの型でも使用できます。Any type, such as object in this example, may be used. case 式は定数値に限定されなくなりました。The case expressions are no longer limited to constant values. この制限がなくなるということは、switch セクションの順序を変更すると、プログラムの動作が変わる可能性があることを意味します。Removing that limitation means that reordering switch sections may change a program's behavior.

定数値に限定されていたときは、switch 式の値と一致する case ラベルは 1 つだけでした。When limited to constant values, no more than one case label could match the value of the switch expression. switch セクションは次のセクションにフォール スルーできないという規則との組み合わせにより、switch セクションは、動作に影響しない任意の順序で並べ替えることができました。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. 現在は、より汎用的になった switch 式により、各セクションの順序が重要になります。Now, with more generalized switch expressions, the order of each section matters. switch 式は、テキストの順序で評価されます。The switch expressions are evaluated in textual order. 実行は、switch 式に一致する最初の switch ラベルに移ります。Execution transfers to the first switch label that matches the switch expression.
default ケースが実行されるのは、他の case ラベルが一致しない場合のみです。Note that the default case will only be executed if no other case labels match. default ケースは、テキストの順序に関係なく最後に評価されます。The default case is evaluated last, regardless of its textual order. default ケースがなく、他の case ステートメントのいずれも一致しない場合、実行は switch ステートメントの次のステートメントで続行されます。If there is no default case, and none of the other case statements match, execution continues at the statement following the switch statement. case ラベルのコードは実行されません。None of the case labels code is executed.

case 式の whenwhen clauses in case expressions

case ラベルで when 句を使用すると、面積が 0 の図形用に特殊なケースを作成できます。You can make special cases for those shapes that have 0 area by using a when clause on the case label. 辺の長さが 0 の正方形または半径が 0 の円は、面積が 0 になります。A square with a side length of 0, or a circle with a radius of 0 has a 0 area. その条件は、case ラベルで when 句を使用して指定します。You specify that condition using a when clause on the case label:

public static double ComputeArea_Version3(object shape)
{
    switch (shape)
    {
        case Square s when s.Side == 0:
        case Circle c when c.Radius == 0:
            return 0;

        case Square s:
            return s.Side * s.Side;
        case Circle c:
            return c.Radius * c.Radius * Math.PI;
        default:
            throw new ArgumentException(
                message: "shape is not a recognized shape",
                paramName: nameof(shape));
    }
}

この変更には、新しい構文に関するいくつかの重要なポイントが示されています。This change demonstrates a few important points about the new syntax. 最初に、複数の case ラベルを 1 つの switch セクションに適用できます。First, multiple case labels can be applied to one switch section. これらのラベルのいずれかが true のとき、ステートメント ブロックが実行されます。The statement block is executed when any of those labels is true. この例では、switch 式が面積が 0 の円または正方形である場合、このメソッドが定数 0 を返します。In this instance, if the switch expression is either a circle or a square with 0 area, the method returns the constant 0.

この例では、最初の switch ブロックの 2 つの case ラベルに異なる 2 つの変数を使用しています。This example introduces two different variables in the two case labels for the first switch block. この switch ブロック内のステートメントで変数 c (円) または s (正方形) が使用されていないことに注意してください。Notice that the statements in this switch block do not use either the variables c (for the circle) or s (for the square). この switch ブロックでは、これらの変数のいずれも確実に代入されません。Neither of those variables is definitely assigned in this switch block. これらのケースのいずれかが一致する場合は、変数の 1 つが明確に代入されています。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. そのため、同じブロックに複数の case ラベルを使用する場合のほとんどは、case ステートメントに新しい変数を導入しません。つまり、when 句の変数のみを使用します。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.

面積が 0 のこれらの図形を追加した後は、さらに図形の種類 (四角形と三角形) を追加してみましょう。Having added those shapes with 0 area, let's add a couple more shape types: a rectangle and a triangle:

public static double ComputeArea_Version4(object shape)
{
    switch (shape)
    {
        case Square s when s.Side == 0:
        case Circle c when c.Radius == 0:
        case Triangle t when t.Base == 0 || t.Height == 0:
        case Rectangle r when r.Length == 0 || r.Height == 0:
            return 0;

        case Square s:
            return s.Side * s.Side;
        case Circle c:
            return c.Radius * c.Radius * Math.PI;
        case Triangle t:
            return t.Base * t.Height * 2;
        case Rectangle r:
            return r.Length * r.Height;
        default:
            throw new ArgumentException(
                message: "shape is not a recognized shape",
                paramName: nameof(shape));
    }
}

この一連の変更により、低次元の場合用に case ラベル、新しい図形ごとにラベルとブロックが追加されます。This set of changes adds case labels for the degenerate case, and labels and blocks for each of the new shapes.

最後に、null ケースを追加して、引数が null にならないようにすることができます。Finally, you can add a null case to ensure the argument is not null:

public static double ComputeArea_Version5(object shape)
{
    switch (shape)
    {
        case Square s when s.Side == 0:
        case Circle c when c.Radius == 0:
        case Triangle t when t.Base == 0 || t.Height == 0:
        case Rectangle r when r.Length == 0 || r.Height == 0:
            return 0;

        case Square s:
            return s.Side * s.Side;
        case Circle c:
            return c.Radius * c.Radius * Math.PI;
        case Triangle t:
            return t.Base * t.Height * 2;
        case Rectangle r:
            return r.Length * r.Height;
        case null:
            throw new ArgumentNullException(paramName: nameof(shape), message: "Shape must not be null");
        default:
            throw new ArgumentException(
                message: "shape is not a recognized shape",
                paramName: nameof(shape));
    }
}

null パターンの特殊な動作に注目します。パターン内の定数 null は、型がありませんが、任意の参照型または null 許容型に変換できるためです。The special behavior for the null pattern is interesting because the constant null in the pattern does not have a type but can be converted to any reference type or nullable type. null を任意の型に変換するよりも、変数のコンパイル時の型に関係なく、null 値が任意の型パターンと一致しないことを言語で定義します。Rather than convert a null to any type, the language defines that a null value will not match any type pattern, regardless of the compile-time type of the variable. この動作により、新しい switch ベースの型パターンが is ステートメントと一貫性を持ちます。is ステートメントは、チェックされている値が null のとき、常に false を返します。This behavior makes the new switch based type pattern consistent with the is statement: is statements always return false when the value being checked is null. より簡単なのは、型をチェックしたら、追加の null チェックが必要ないことです。It's also simpler: once you have checked the type, you don't need an additional null check. 上記のサンプルの case ブロックのいずれにも null チェックがないことからわかるように、型パターンのマッチングにより null 以外の値が保証されるため、これらは必要ありません。You can see that from the fact that there are no null checks in any of the case blocks of the samples above: they are not necessary, since matching the type pattern guarantees a non-null value.

var内の宣言casevar declarations in case expressions

導入varようにパターン マッチに新しいルールを導入する一致式のいずれか。The introduction of var as one of the match expressions introduces new rules to the pattern match.

最初の規則は、var宣言標準型の推論規則に従います: switch 式の静的な型である型を推論します。The first rule is that the var declaration follows the normal type inference rules: The type is inferred to be the static type of the switch expression. そのルールから、型は常に一致します。From that rule, the type always matches.

2 番目のルールは、var宣言には、その他の型のパターン式を含む null チェックはありません。The second rule is that a var declaration does not have the null check that other type pattern expressions include. つまり、変数が null であるし、null チェックが必要な場合は。That means the variable may be null, and a null check is necessary in that case.

これら 2 つの規則からといって、さまざまな状況で、var内の宣言、case式と同じ条件に一致する、default式。Those two rules mean that in many instances, a var declaration in a case expression matches the same conditions as a default expression. 既定以外のすべてのケースがより優先するため、default場合も、defaultケースが実行されません。Because any non-default case is preferred to the default case, the default case will never execute.

注意

コンパイラはこのような場合に警告を生成しません場所、defaultケースが書き込まれましたが実行されません。The compiler does not emit a warning in those cases where a default case has been written but will never execute. これは、一貫性のある現在のswitchステートメントの動作が可能なすべてのケースが一覧されています。This is consistent with current switch statement behavior where all possible cases have been listed.

3 番目のルールに使用が導入されていますここで、varケースが役立つことがあります。The third rule introduces uses where a var case may be useful. 入力が文字列であり、使用されているコマンドの値を検索するパターン マッチを行っていることを想像してください。Imagine that you are doing a pattern match where the input is a string and you are searching for known command values. ようなものを記述する場合があります。You might write something like:

static object CreateShape(string shapeDescription)
{
    switch (shapeDescription)
    {
        case "circle":
            return new Circle(2);

        case "square":
            return new Square(4);
        
        case "large-circle":
            return new Circle(12);

        case var o when (o?.Trim()?.Length ?? 0) == 0:
            // whitespace
            return null;
        default:
            return "invalid shape description";
    }            
}

varと一致する case null、空の文字列、または空白のみを含む任意の文字列。The var case matches null, the empty string, or any string that contains only whitespace. 上記のコードを使用する通知、?.演算子には誤ってをスローしないことを確認する、NullReferenceExceptionです。Notice that the preceding code uses the ?. operator to ensure that it does not accidentally throw a NullReferenceException. defaultの場合は、このコマンドのパーサーで認識されないその他の文字列値を処理します。The default case handles any other string values that are not understood by this command parser.

これは、1 つの例を検討する可能性があります、var別個のものである式の場合、default式。This is one example where you may want to consider a var case expression that is distinct from a default expression.

まとめ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.

このサンプルのコードを、抽象的な Shape と特定の派生図形のクラス階層を作成し、それぞれに面積を計算するための仮想メソッドが独自に実装されている場合の設計と比較してください。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.