次の方法で共有


パターン マッチング - パターン内のis式とswitch式、および演算子andor、およびnot

isswitch ステートメント、および switch 式を使用して入力式を任意の数の特性と照合します。 C# は、宣言、型、定数、リレーショナル、プロパティ、リスト、var、破棄など、複数のパターンをサポートしています。 ブール論理キーワード andornot を使って、複数のパターンを組み合わせることができます。

次の C# 式およびステートメントでは、パターン マッチングがサポートされています。

それらの構造体では、入力式を次の任意のパターンと一致させることができます。

  • 宣言パターン: 式のランタイム型をチェックし、一致が成功した場合は、宣言された変数に式の結果を代入します。
  • 型パターン: 式のランタイム型をチェックします。
  • 定数パターン: 式の結果が指定した定数と等しいことをテストします。
  • リレーショナル パターン: 式の結果と指定された定数を比較します。
  • 論理パターン: 式がパターンの論理組み合わせと一致することをテストします。
  • プロパティ パターン: 式のプロパティまたはフィールドが入れ子になったパターンと一致することをテストします。
  • 位置指定パターン: 式の結果を分解し、結果の値が入れ子になったパターンに一致するかどうかをテストします。
  • var パターン: 任意の式に一致させ、その結果を宣言された変数に代入します。
  • 破棄パターン: 任意の式に一致させます。
  • リスト パターン: 要素のシーケンスが、対応する入れ子になったパターンと一致することをテストします。 C# 11 で導入されました。

論理プロパティ位置指定、およびリスト パターンは ''再帰的な'' パターンです。 つまり、それらには入れ子になったパターンを含めることができます。

これらのパターンを使用してデータ ドリブン アルゴリズムを構築する方法の例については、「チュートリアル: パターン マッチングを使用して、型ドリブンおよびデータ ドリブンのアルゴリズムを構築する」を参照してください。

宣言パターンと型パターン

宣言パターンと型パターンを使用して、式のランタイム型が指定された型と互換性があるかどうかをチェックします。 宣言パターンでは、新しいローカル変数を宣言することもできます。 宣言パターンが式に一致すると、次の例に示すように、その変数に変換された式の結果が代入されます。

object greeting = "Hello, World!";
if (greeting is string message)
{
    Console.WriteLine(message.ToLower());  // output: hello, world!
}

式の結果が null 以外で、次のいずれかの条件が満たされている場合に、T 型の宣言パターンが式に一致します。

  • 式の結果のランタイム型は T です。

  • 式の結果のランタイム型は T 型から派生するか、インターフェイス T を実装するか、または、それから T への暗黙的な参照変換が別に存在します。 次の例は、この条件が満たされている場合の 2 つのケースを示しています。

    var numbers = new int[] { 10, 20, 30 };
    Console.WriteLine(GetSourceLabel(numbers));  // output: 1
    
    var letters = new List<char> { 'a', 'b', 'c', 'd' };
    Console.WriteLine(GetSourceLabel(letters));  // output: 2
    
    static int GetSourceLabel<T>(IEnumerable<T> source) => source switch
    {
        Array array => 1,
        ICollection<T> collection => 2,
        _ => 3,
    };
    

    前の例では、GetSourceLabel メソッドへの最初の呼び出しで、引数 のランタイム型 int[]Array 型から派生しているため、最初のパターンが引数値と一致します。 GetSourceLabel メソッドへの 2 つ目の呼び出しでは、引数のランタイム型 List<T> は、Array 型から派生していませんが、ICollection<T> インターフェイスを実装しています。

  • 式の結果のランタイム型は、基になる型 T を持つ Null 許容値型です。

  • 式の結果のランタイム型から T 型への、ボックス化変換またはボックス化解除変換が存在します。

次の例は、最後の 2 つの条件を示しています。

int? xNullable = 7;
int y = 23;
object yBoxed = y;
if (xNullable is int a && yBoxed is int b)
{
    Console.WriteLine(a + b);  // output: 30
}

式の型のみをチェックする場合は、次の例に示すように、変数名の代わりに破棄 _ を使用できます。

public abstract class Vehicle {}
public class Car : Vehicle {}
public class Truck : Vehicle {}

public static class TollCalculator
{
    public static decimal CalculateToll(this Vehicle vehicle) => vehicle switch
    {
        Car _ => 2.00m,
        Truck _ => 7.50m,
        null => throw new ArgumentNullException(nameof(vehicle)),
        _ => throw new ArgumentException("Unknown type of a vehicle", nameof(vehicle)),
    };
}

その目的のために、次の例に示すように "型パターン" を使用できます。

public static decimal CalculateToll(this Vehicle vehicle) => vehicle switch
{
    Car => 2.00m,
    Truck => 7.50m,
    null => throw new ArgumentNullException(nameof(vehicle)),
    _ => throw new ArgumentException("Unknown type of a vehicle", nameof(vehicle)),
};

宣言パターンと同様に、式の結果が null 以外で、その実行時の型が上記の条件のいずれかを満たす場合、型パターンは式と一致します。

null ではないことを調べるには、次の例に示すように、否定された null 定数パターンを使用できます。

if (input is not null)
{
    // ...
}

詳細については、機能提案ノートの「宣言パターン」と「型パターン」セクションを参照してください。

定数パターン

次の例に示すように、定数パターンを使用して、式の結果が指定された定数と等しいかどうかをテストします。

public static decimal GetGroupTicketPrice(int visitorCount) => visitorCount switch
{
    1 => 12.0m,
    2 => 20.0m,
    3 => 27.0m,
    4 => 32.0m,
    0 => 0.0m,
    _ => throw new ArgumentException($"Not supported number of visitors: {visitorCount}", nameof(visitorCount)),
};

定数パターンでは、次のような任意の定数式を使用できます。

式は、定数型に変換できる型である必要がありますが、例外が 1 つあります。C# 11 以降のバージョンでは、型が Span<char> または ReadOnlySpan<char> である式は、定数文字列と一致させることができます。

次の例に示すように、定数パターンを使用して null をチェックします。

if (input is null)
{
    return;
}

コンパイラにより、式 x is null が評価されるときに、ユーザーがオーバーロードした等値演算子 == が呼び出されないことが保証されます。

次の例で示すように、否定された null 定数パターンを使って、null ではないことを調べることができます。

if (input is not null)
{
    // ...
}

詳細については、機能提案ノートの「定数パターン」セクションを参照してください。

リレーショナル パターン

次の例に示すように、"リレーショナル パターン" を使用して、式の結果を定数と比較します。

Console.WriteLine(Classify(13));  // output: Too high
Console.WriteLine(Classify(double.NaN));  // output: Unknown
Console.WriteLine(Classify(2.4));  // output: Acceptable

static string Classify(double measurement) => measurement switch
{
    < -4.0 => "Too low",
    > 10.0 => "Too high",
    double.NaN => "Unknown",
    _ => "Acceptable",
};

リレーショナル パターンでは、関係演算子 <><=、または >= のいずれかを使用できます。 リレーショナル パターンの右側の部分は、定数式である必要があります。 定数式には、整数浮動小数点char、または列挙型を指定できます。

式の結果が特定の範囲内にあるかどうかをチェックするには、次の例に示すように、接続的andパターンに対して、それを一致させます。

Console.WriteLine(GetCalendarSeason(new DateTime(2021, 3, 14)));  // output: spring
Console.WriteLine(GetCalendarSeason(new DateTime(2021, 7, 19)));  // output: summer
Console.WriteLine(GetCalendarSeason(new DateTime(2021, 2, 17)));  // output: winter

static string GetCalendarSeason(DateTime date) => date.Month switch
{
    >= 3 and < 6 => "spring",
    >= 6 and < 9 => "summer",
    >= 9 and < 12 => "autumn",
    12 or (>= 1 and < 3) => "winter",
    _ => throw new ArgumentOutOfRangeException(nameof(date), $"Date with unexpected month: {date.Month}."),
};

式の結果が null であるか、null 許容変換またはボックス化解除変換によって定数の型に変換できない場合、リレーショナル パターンは式に一致しません。

詳細については、機能提案ノートの「リレーショナル パターン」セクションを参照してください。

論理パターン

notand、および or パターン連結子を使用して、次の "論理パターン" を作成します。

  • 否定されたパターンが式に一致しない場合に、式に一致する否定notパターン。 次の例に、定数 null パターンを否定して、式が null でないかどうかを確認する方法を示します。

    if (input is not null)
    {
        // ...
    }
    
  • 両方のパターンが式に一致する場合に、式に一致する接続的and パターン。 次の例に、リレーショナル パターンを組み合わせて、値が特定の範囲内にあるかどうかをチェックする方法を示します。

    Console.WriteLine(Classify(13));  // output: High
    Console.WriteLine(Classify(-100));  // output: Too low
    Console.WriteLine(Classify(5.7));  // output: Acceptable
    
    static string Classify(double measurement) => measurement switch
    {
        < -40.0 => "Too low",
        >= -40.0 and < 0 => "Low",
        >= 0 and < 10.0 => "Acceptable",
        >= 10.0 and < 20.0 => "High",
        >= 20.0 => "Too high",
        double.NaN => "Unknown",
    };
    
  • 次の例に示すように、いずれかのパターンが式に一致する場合に、式に一致する "離接的" or パターン。

    Console.WriteLine(GetCalendarSeason(new DateTime(2021, 1, 19)));  // output: winter
    Console.WriteLine(GetCalendarSeason(new DateTime(2021, 10, 9)));  // output: autumn
    Console.WriteLine(GetCalendarSeason(new DateTime(2021, 5, 11)));  // output: spring
    
    static string GetCalendarSeason(DateTime date) => date.Month switch
    {
        3 or 4 or 5 => "spring",
        6 or 7 or 8 => "summer",
        9 or 10 or 11 => "autumn",
        12 or 1 or 2 => "winter",
        _ => throw new ArgumentOutOfRangeException(nameof(date), $"Date with unexpected month: {date.Month}."),
    };
    

前の例に示すように、パターンでパターンの連結子を繰り返し使用できます。

優先順位とチェックの順序

パターン結合子は、式のバインド順序に基づいて次のように並べ替えられます。

  • not
  • and
  • or

not パターンは、最初にそのオペランドにバインドされます。 and パターンは、任意のnotパターン式バインドの後にバインドされます。 or パターンは、すべてのnotパターンとand パターンがオペランドにバインドされた後にバインドされます。 次の例では、小文字ではないすべての文字 ( a - z) の照合を試みます。 and パターンの前にnot パターンがバインドされるため、エラーが発生します。

// Incorrect pattern. `not` binds before `and`
static bool IsNotLowerCaseLetter(char c) => c is not >= 'a' and <= 'z';

既定のバインドは、前の例が次の例のように解析されていることを意味します。

// The default binding without parentheses is shows in this method. `not` binds before `and`
static bool IsNotLowerCaseLetterDefaultBinding(char c) => c is ((not >= 'a') and <= 'z');

これを修正するには、 not>= 'a' and <= 'z' 式にバインドするように指定する必要があります。

// Correct pattern. Force `and` before `not`
static bool IsNotLowerCaseLetterParentheses(char c) => c is not (>= 'a' and <= 'z');

パターンが複雑になるにつれて、かっこの追加がより重要になります。 一般に、次の例に示すように、かっこを使用して他の開発者のパターンを明確にします。

static bool IsLetter(char c) => c is (>= 'a' and <= 'z') or (>= 'A' and <= 'Z');

Note

同じバインド順序を持つパターンがチェックされる順序は未定義です。 実行時に、複数の or パターンと複数の and パターンの右側の入れ子になったパターンを最初に確認できます。

詳細については、機能提案ノートの「パターン連結子」セクションを参照してください。

プロパティ パターン

次の例に示すように、プロパティ パターンを使用して、入れ子になったパターンに対して、式のプロパティまたはフィールドを一致させます。

static bool IsConferenceDay(DateTime date) => date is { Year: 2020, Month: 5, Day: 19 or 20 or 21 };

式の結果が null 以外で、すべての入れ子になったパターンが、式の結果の対応するプロパティまたはフィールドと一致する場合、プロパティ パターンは式に一致します。

次の例に示すように、ランタイム型チェックと変数宣言をプロパティ パターンに追加することもできます。

Console.WriteLine(TakeFive("Hello, world!"));  // output: Hello
Console.WriteLine(TakeFive("Hi!"));  // output: Hi!
Console.WriteLine(TakeFive(new[] { '1', '2', '3', '4', '5', '6', '7' }));  // output: 12345
Console.WriteLine(TakeFive(new[] { 'a', 'b', 'c' }));  // output: abc

static string TakeFive(object input) => input switch
{
    string { Length: >= 5 } s => s.Substring(0, 5),
    string s => s,

    ICollection<char> { Count: >= 5 } symbols => new string(symbols.Take(5).ToArray()),
    ICollection<char> symbols => new string(symbols.ToArray()),

    null => throw new ArgumentNullException(nameof(input)),
    _ => throw new ArgumentException("Not supported input type."),
};

プロパティ パターンは、再帰的パターンです。 つまり、入れ子になったパターンとして任意のパターンを使用できます。 次の例に示すように、入れ子になったパターンに対して、データの一部を一致させるには、プロパティ パターンを使用します。

public record Point(int X, int Y);
public record Segment(Point Start, Point End);

static bool IsAnyEndOnXAxis(Segment segment) =>
    segment is { Start: { Y: 0 } } or { End: { Y: 0 } };

前の例では、or パターン連結子レコード型を使用しています。

C# 10 以降、入れ子になったプロパティまたはプロパティ パターン内のフィールドを参照できます。 この機能は "拡張プロパティ パターン" と呼ばれます。 たとえば、前の例のメソッドを次の同等のコードにリファクタリングできます。

static bool IsAnyEndOnXAxis(Segment segment) =>
    segment is { Start.Y: 0 } or { End.Y: 0 };

詳細については、機能提案ノートの「プロパティ パターン」セクションと「拡張プロパティのパターン」機能提案ノートを参照してください。

ヒント

プロパティ パターンの簡略化 (IDE0170) スタイル ルールを使用すると、拡張プロパティ パターンを使用する場所を提案することで、コードの読みやすさを向上させることができます。

位置指定パターン

次の例に示すように、位置指定パターンを使用して、式の結果を分解し、結果の値を、対応する入れ子になったパターンに対して一致させます。

public readonly struct Point
{
    public int X { get; }
    public int Y { get; }

    public Point(int x, int y) => (X, Y) = (x, y);

    public void Deconstruct(out int x, out int y) => (x, y) = (X, Y);
}

static string Classify(Point point) => point switch
{
    (0, 0) => "Origin",
    (1, 0) => "positive X basis end",
    (0, 1) => "positive Y basis end",
    _ => "Just a point",
};

前の例では、式の型に Deconstruct メソッドが含まれており、これは、式の結果を分解するために使用されます。

重要

位置指定パターンのメンバーの順序は、Deconstruct メソッドのパラメーターの順序と一致する必要があります。 これは、位置指定パターン用に生成されたコードによって Deconstruct メソッドが呼び出されるためです。

また、位置指定パターンに対してタプル型の式を一致させることもできます。 このようにして、次の例に示すように、複数の入力をさまざまなパターンに一致させることができます。

static decimal GetGroupTicketPriceDiscount(int groupSize, DateTime visitDate)
    => (groupSize, visitDate.DayOfWeek) switch
    {
        (<= 0, _) => throw new ArgumentException("Group size must be positive."),
        (_, DayOfWeek.Saturday or DayOfWeek.Sunday) => 0.0m,
        (>= 5 and < 10, DayOfWeek.Monday) => 20.0m,
        (>= 10, DayOfWeek.Monday) => 30.0m,
        (>= 5 and < 10, _) => 12.0m,
        (>= 10, _) => 15.0m,
        _ => 0.0m,
    };

前の例では、リレーショナル パターン論理パターンを使用しています。

次の例に示すように、位置指定パターンでタプル要素と Deconstruct パラメーターの名前を使用できます。

var numbers = new List<int> { 1, 2, 3 };
if (SumAndCount(numbers) is (Sum: var sum, Count: > 0))
{
    Console.WriteLine($"Sum of [{string.Join(" ", numbers)}] is {sum}");  // output: Sum of [1 2 3] is 6
}

static (double Sum, int Count) SumAndCount(IEnumerable<int> numbers)
{
    int sum = 0;
    int count = 0;
    foreach (int number in numbers)
    {
        sum += number;
        count++;
    }
    return (sum, count);
}

また、次のいずれかの方法で位置指定パターンを拡張することもできます。

  • 次の例に示すように、ランタイム型チェックと変数宣言を追加します。

    public record Point2D(int X, int Y);
    public record Point3D(int X, int Y, int Z);
    
    static string PrintIfAllCoordinatesArePositive(object point) => point switch
    {
        Point2D (> 0, > 0) p => p.ToString(),
        Point3D (> 0, > 0, > 0) p => p.ToString(),
        _ => string.Empty,
    };
    

    前の例では、Deconstruct メソッドを暗黙的に提供する位置指定レコードを使用しています。

  • 次の例に示すように、位置指定パターン内でプロパティ パターンを使用します。

    public record WeightedPoint(int X, int Y)
    {
        public double Weight { get; set; }
    }
    
    static bool IsInDomain(WeightedPoint point) => point is (>= 0, >= 0) { Weight: >= 0.0 };
    
  • 次の例に示すように、前の 2 つの使用方法を組み合わせます。

    if (input is WeightedPoint (> 0, > 0) { Weight: > 0.0 } p)
    {
        // ..
    }
    

位置指定パターンは、再帰的パターンです。 つまり、入れ子になったパターンとして任意のパターンを使用できます。

詳細については、機能提案ノートの「位置指定パターン」セクションを参照してください。

var パターン

次の例に示すように、var パターンを使用して、null を含む任意の式に一致させ、その結果を新しいローカル変数に代入します。

static bool IsAcceptable(int id, int absLimit) =>
    SimulateDataFetch(id) is var results 
    && results.Min() >= -absLimit 
    && results.Max() <= absLimit;

static int[] SimulateDataFetch(int id)
{
    var rand = new Random();
    return Enumerable
               .Range(start: 0, count: 5)
               .Select(s => rand.Next(minValue: -10, maxValue: 11))
               .ToArray();
}

var パターンは、中間計算の結果を保持するためにブール式内に一時変数が必要な場合に便利です。 さらに、次の例に示すように、switch 式またはステートメントの when case guard で追加のチェックを実行する必要がある場合は、var パターンを使用することもできます。

public record Point(int X, int Y);

static Point Transform(Point point) => point switch
{
    var (x, y) when x < y => new Point(-x, y),
    var (x, y) when x > y => new Point(x, -y),
    var (x, y) => new Point(x, y),
};

static void TestTransform()
{
    Console.WriteLine(Transform(new Point(1, 2)));  // output: Point { X = -1, Y = 2 }
    Console.WriteLine(Transform(new Point(5, 2)));  // output: Point { X = 5, Y = -2 }
}

前の例で、パターン var (x, y)位置指定パターン (var x, var y) と同じです。

var パターンでは、宣言された変数の型は、パターンに一致する式のコンパイル時の型になります。

詳細については、機能提案ノートの「Var パターン」セクションを参照してください。

破棄パターン

次の例に示すように、"破棄パターン" _ を使って、null を含む任意の式に一致させます。

Console.WriteLine(GetDiscountInPercent(DayOfWeek.Friday));  // output: 5.0
Console.WriteLine(GetDiscountInPercent(null));  // output: 0.0
Console.WriteLine(GetDiscountInPercent((DayOfWeek)10));  // output: 0.0

static decimal GetDiscountInPercent(DayOfWeek? dayOfWeek) => dayOfWeek switch
{
    DayOfWeek.Monday => 0.5m,
    DayOfWeek.Tuesday => 12.5m,
    DayOfWeek.Wednesday => 7.5m,
    DayOfWeek.Thursday => 12.5m,
    DayOfWeek.Friday => 5.0m,
    DayOfWeek.Saturday => 2.5m,
    DayOfWeek.Sunday => 2.0m,
    _ => 0.0m,
};

前の例では、破棄パターンを使用して、null と、DayOfWeek 列挙型の対応するメンバーを持たない任意の整数値を処理しています。 これにより、例の switch 式ですべての可能な入力値が処理されることが保証されます。 switch 式で破棄パターンを使用せず、式のパターンが入力に一致しない場合、ランタイムによって、例外がスローされます。 switch 式ですべての可能な入力値が処理されない場合、コンパイラで警告が生成されます。

破棄パターンを、is 式または switch ステートメントでパターンにすることはできません。 そのような場合、任意の式に一致させるには、破棄を含む var パターンvar _ を使用します。 破棄パターンを、switch 式でパターンにすることはできません。

詳細については、機能提案ノートの「破棄パターン」セクションを参照してください。

かっこで囲まれたパターン

任意のパターンをかっこで囲むことができます。 一般にそれを行うのは、次の例に示すように、論理パターンの優先順位を強調または変更するためです。

if (input is not (float or double))
{
    return;
}

リスト パターン

C# 11 以降では、次の例に示すように、配列またはリストをパターンの ''シーケンス'' と照合できます。

int[] numbers = { 1, 2, 3 };

Console.WriteLine(numbers is [1, 2, 3]);  // True
Console.WriteLine(numbers is [1, 2, 4]);  // False
Console.WriteLine(numbers is [1, 2, 3, 4]);  // False
Console.WriteLine(numbers is [0 or 1, <= 2, >= 3]);  // True

前の例に示すように、入れ子になった各パターンが入力シーケンスの対応する要素と一致すると、リスト パターンが照合されます。 リスト パターン内では任意のパターンを使用できます。 任意の要素を照合するには、次の例に示すように、破棄パターンを使用するか、要素を取り込む場合は、var パターンを使用します。

List<int> numbers = new() { 1, 2, 3 };

if (numbers is [var first, _, _])
{
    Console.WriteLine($"The first element of a three-item list is {first}.");
}
// Output:
// The first element of a three-item list is 1.

上記の例では、入力シーケンス全体をリスト パターンと照合しています。 次の例に示すように、入力シーケンスの先頭や末尾でのみ要素を照合するには、"スライス パターン" .. を使用します。

Console.WriteLine(new[] { 1, 2, 3, 4, 5 } is [> 0, > 0, ..]);  // True
Console.WriteLine(new[] { 1, 1 } is [_, _, ..]);  // True
Console.WriteLine(new[] { 0, 1, 2, 3, 4 } is [> 0, > 0, ..]);  // False
Console.WriteLine(new[] { 1 } is [1, 2, ..]);  // False

Console.WriteLine(new[] { 1, 2, 3, 4 } is [.., > 0, > 0]);  // True
Console.WriteLine(new[] { 2, 4 } is [.., > 0, 2, 4]);  // False
Console.WriteLine(new[] { 2, 4 } is [.., 2, 4]);  // True

Console.WriteLine(new[] { 1, 2, 3, 4 } is [>= 0, .., 2 or 4]);  // True
Console.WriteLine(new[] { 1, 0, 0, 1 } is [1, 0, .., 0, 1]);  // True
Console.WriteLine(new[] { 1, 0, 1 } is [1, 0, .., 0, 1]);  // False

スライス パターンでは 0 個以上の要素を照合します。 リスト パターンでは最大 1 つのスライス パターンを使用できます。 スライス パターンは、リスト パターンでのみ表示できます。

次の例に示すように、スライス パターン内でサブパターンを入れ子にすることもできます。

void MatchMessage(string message)
{
    var result = message is ['a' or 'A', .. var s, 'a' or 'A']
        ? $"Message {message} matches; inner part is {s}."
        : $"Message {message} doesn't match.";
    Console.WriteLine(result);
}

MatchMessage("aBBA");  // output: Message aBBA matches; inner part is BB.
MatchMessage("apron");  // output: Message apron doesn't match.

void Validate(int[] numbers)
{
    var result = numbers is [< 0, .. { Length: 2 or 4 }, > 0] ? "valid" : "not valid";
    Console.WriteLine(result);
}

Validate(new[] { -1, 0, 1 });  // output: not valid
Validate(new[] { -1, 0, 0, 1 });  // output: valid

詳細については、「リスト パターン」の機能提案メモを参照してください。

C# 言語仕様

詳細については、C# 言語仕様パターンとパターン マッチングに関するセクションを参照してください。

C# 8 以降に追加された機能の詳細については、機能の提案に関する次の記述を参照してください。

関連項目