やってみる

アウトプットすべく己を導くためのブログ。その試行錯誤すらたれ流す。

C#8.0パターンマッチング

 パターンマッチングのうち新しくてイケてる構文。

成果物

前回

 簡単だが微妙な構文だった。

今回

 難しいが便利な構文。

4. 破棄パターン(非再帰

 _で値を破棄する。使える場面は以下のみ。

  • switch式内
  • 再帰パターン内(位置、プロパティ)
int M(object x) => x switch
{
    0 => 0,
    string s => s.Length,
    _ => -1
};

5. 位置パターン(再帰

int M(Point p) => p switch
{
    (1, 2) => 0,
    (var x, _) when x > 0 => x,
    _ => -1
};

5-0. Deconstruct

 型がPointで確定しているため、Point(1, 2)でなく(1, 2)のように型名を省略できる。

 位置パターンはDeconstructメソッドを呼んでメンバを取得してからマッチング判定する。

 上記コードは以下と同様の意味である。

p.Deconstruct(out var x, out var y);
if (x is 1 && y is 2) return 0;
if (x > 0) return x;
return -1;

5-1. 型の明示

 型を明示することもできる。

int M(Point p) => p switch
{
    Point(1, 2) => 0,
    Point(var x, _) when x > 0 => x,
    _ => -1
};

5-2. 名前付き引数

 型のメンバ名を指定することもできる。

int M(Point p) => p switch
{
    (x: 1, y: 2) => 0,
    (x: var x, y: _) when x > 0 => x,
    _ => -1
};

5-3. コンストラクタと同等

// 位置指定で構築できるんなら、位置指定でマッチングできるべき
var p1 = new Point(1, 2);
var r1 = p1 is Point (1, 2);
 
// 名前指定で構築できるんなら、名前指定でマッチングできるべき
var p2 = new Point(x: 1, y: 2);
var r2 = p2 is Point (x: 1, y: 2);
 
// 型推論が効く場合に new の後ろの型名は省略可能(になる予定)なら
// 型が既知なら型名を省略してマッチングできるべき
// Point p3 = new (1, 2); // error CS1031: 型が必要です。
Point p3 = new Point(1, 2);
var r3 = p3 is (1, 2);
 
// 階層的に new できるんなら、階層的にマッチングできるべき
var line = new Line(new Point(1, 2), new Point(3, 4));
var r4 = line is ((1, 2), (3, 4));

 Point p3 = new (1, 2);できたら嬉しかったのだが、コンパイルエラーになった。

 どうせならnewも省略したい。Point p3 = (1, 2)で生成したい。さらに=も省略したい。Point p3(1, 2);とかで生成したい。型が明示されているならタプルでないと判るし、できそうな気がするけど。タプルなら型も省略してp3(1, 2);で生成したい。

5-4. タプルswitch

int Compare(int? a, int? b)
{
    switch (a, b)
    {
        case (null, null): return 0;
        case (int _, null): return -1;
        case (null, int _): return -1;
        case (int a1, int b1): return a1.CompareTo(b1);
    }
}

 switch ((a, b))と書かずに済む。

6. プロパティパターン(再帰

int M(Point p) => p switch
{
    { X: 1, Y: 2 } => 0,
    { X: var x, Y: _ } when x > 0 => x,
    _ => -1
};

 オブジェクト初期化子new Point { X=1, X=2 };の分解版。

6.1 名前を省略しない

int M(Point p) => p switch
{
    Point { X: 1, Y: 2 } => 0,
    Point { X: var x, Y: _ } when x > 0 => x,
    _ => -1
};

6.2 フィールドも参照可

class X
{
    public int GetOnly { get; private set; }
    public int GetSet { get; set; }
    public int Field;
    public int SetOnly { set => GetOnly = value; }
}
var x = new X { GetSet = 1, Field = 2, SetOnly = 3 };
Console.WriteLine(x is { GetOnly: 3, GetSet: 1, Field: 2 });

6.2 オブジェクト初期化子の逆

// 初期化子でプロパティ指定できるなら、プロパティ指定でマッチングできるべき
var p1 = new Point { X = 1, Y = 2 };
var r1 = p1 is { X: 1, Y: 2 };
 
// 混在で構築できるなら、混在でマッチングできるべき
var p2 = new Point(x: 1) { Y = 2 };
var r2 = p2 is (1, _) { Y: 2 };

 混在させても無駄に難読化されるだけだと思われる。ただ、コンストラクタに無くてプロパティに有る値をマッチングさせたいときなど、組合せ次第では混在させざるを得ないだろう。

6.3 非nullマッチング

 以下において{ }は非nullを意味する。

if (x is { } nonNull) { Console.WriteLine("非nullだよ。"); }
else { Console.WriteLine("nullだよ。"); }
string M(object x) => x switch {
    { } nonNull => nonNull.ToString(),
    null => "null",
};

対象環境

$ uname -a
Linux raspberrypi 4.19.42-v7+ #1218 SMP Tue May 14 00:48:17 BST 2019 armv7l GNU/Linux