目次

概要

前項で説明した通り、C# 7.0で、is演算子とswtichステートメントが拡張されて、is/caseの後ろにパターンを書けるようになりました。 パターンには以下のようなものがあります。

パターン バージョン 概要
型パターン C# 7.0 型の判定 int istring s
定数パターン C# 7.0 定数との比較 null1
var パターン C# 7.0 何にでもマッチ・変数で受け取り var x
破棄パターン C# 8.0 何にでもマッチ・無視 _
位置パターン C# 8.0 分解と同じ要領で、再帰的にマッチングする (1, var i, _)
プロパティ パターン C# 8.0 プロパティに対して再帰的にマッチングする { A: 1, B: var i }

サンプル コード: https://github.com/ufcpp/UfcppSample/tree/master/Chapters/Data/Patterns

非再帰パターン

Ver. 7.0

C# の文法上の区別する意味はないんですが、 パターンのうち、C# 7.0 で入ったものと 8.0 で入ったものの一番の差は再帰があるかどうかです。 C# 7.0 からあるパターンは1層限り、8.0 で追加されたパターンは再帰的に何層もマッチできます。 (再帰がある方が難しいので後からの追加になりました。)

ここではまず、文法が簡単な再帰のないパターンから説明していきます。

型パターン (宣言パターン)

C# 6.0以前から元々あった is 演算子の自然な拡張になっているのが型パターン(type pattern)です。 以下のように、型の後ろに続けて、マッチした結果を変数で受け取れます。

static void M(object x)
{
    if (x is int i) Console.WriteLine("int " + i);
    else if (x is string s) Console.WriteLine("string " + s);
}

iscase の後ろで変数宣言をしているような形なので、宣言パターン(declaration pattern)とも呼びます。 (というか、C# 8.0以降は宣言パターンの方が正式な呼び方に変わっていそうです。)

型パターンは、旧来からある is 演算子や as 演算子とほぼ同じ挙動です。 上記の例は、概ね以下のコードと同じ動作になります。

if (x is int)
{
    var i = (int)x;
    Console.WriteLine("int " + i);
}
else
{
    string s = x as string;
    if (s != null)
    {
        Console.WriteLine("string " + s);
    }
}

as + != null になっていることからわかる通り、 型パターンは null にはマッチしません。 (以下のように、たとえ変数の型が一致していたとしても、null にはマッチしません。)

static void Main()
{
    M("abc"); // matched abc
    M(null);  // 何も表示されない
}
 
static void M(string x)
{
    if (x is string s) Console.WriteLine("matched " + s);
}

定数パターン

iscaseの後ろには定数も書けます。これを定数パターン(constant pattern)と言います。 単体で見ると普通に == を使えば済むことも多いわけですが、 定数パターンであれば他のパターンとの混在ができます。

switch (x)
{
    // 定数パターン
    case 0: return 0;
    // 型パターン
    case string s: return s.Length;
    default: return -1;
}

名前通り定数しか使えません。 変数との値比較がしたければ、when句を使うなどが必要です。

static int M(object x, int comparand)
{
    switch (x)
    {
        // case comparand: とは書けない。
        // 型パターン + when 句を使う。
        case int i when i == comparand: return 0;
        default: return -1;
    }
}

ちなみに、定数パターンでは、ユーザー定義演算子を見ません。 以下のように、==isで挙動が違う場合があります。

using System;
 
class X
{
    // 全てのインスタンスが等しいという挙動。
    // 当然、x == null も常に true。
    public static bool operator ==(X a, X b) => true;
    public static bool operator !=(X a, X b) => false;
}
 
class Program
{
    static void Main()
    {
        var x = new X();
 
        // なんでも true なので、== null も true
        Console.WriteLine(x == null);
 
        // ユーザー定義の == は見ない。x が本当に null かどうかを見て、false になる
        Console.WriteLine(x is null);
    }
}

var パターン

型パターンと似ていますが、具体的な型名の代わりに var キーワードを使うと、 任意の型にマッチするパターンになります。 これを var パターン (var pattern)と言います。

switch の最後に書いて「その他全部」な分岐に使ったりします。

static int M(object x)
{
    switch(x)
    {
        case 0: return 0;
        case string s: return s.Length;
        case var other: return other.GetHashCode();
        // あるいは、変数で受け取る必要がないときは _ にしておけば破棄の意味なる
        // case var _:
    }
}

あと、少し悪用気味ではありますが、式中での変数宣言に使えたりします。

while (Console.ReadLine() is var line && !string.IsNullOrEmpty(line))
{
    Console.WriteLine(line);
}

1つ注意が必要な点として、var パターンは型パターンと違って、null にもマッチします。

string s = null;
Console.WriteLine(s is string x); // false
Console.WriteLine(s is var y);    // true

null をはじきたい場合は、var ではなく、後述するプロパティ パターンを使ってx is {} nonNullと書いたりします。

破棄パターン

Ver. 8.0

何にでもマッチして、マッチ結果を受け取る必要がない場合、_ を使って値を破棄できます。これを破棄パターン(discard pattern)と言います。

再帰はしないんですが、switch式の中と、再帰パターン内でしか使えないので C# 8.0 での実装になります。 isやステートメントの方のswitchcaseの後ろではvar _と書く必要がありますが、switch式の場合は_だけで値を破棄します。

static int M(object x)
    => x switch
    {
        0 => 0,
        string s => s.Length,
        _ => -1
    };

余談: 破棄パターンが C# 8.0 からな理由

ちなみに、isswitchステートメント内で _ だけでの値の破棄ができないのは既存コードとの互換性のためです。 普通書かないようなコードですが、一応、以下のようなコードが元々合法なため、意味を変えることができませんでした。

using System;
 
class _Type
{
    class _ { }
 
    static void M(object x)
    {
        Console.WriteLine(x is _); // class _ とのマッチ
    }
}
 
class _Constant
{
    const int _ = 0;
 
    static void M(object x)
    {
        switch (x)
        {
            case _: // 定数 _ とのマッチ
                break;
        }
    }
}

(あまりにも紛らわしいので、このコードを C# 8.0 でコンパイルすると警告が出ます。)

更新履歴

ブログ