C# パターンマッチングのデコンパイルコードを確認する


[C# Advent Calendar 2021](https://qiita.com/advent-calendar/2021/csharplang) を見てると、C# のパターンマッチングが便利らしい。

- [C#のアプデでめちゃくちゃ便利になったswitch文（パターンマッチング）の紹介](https://qiita.com/toRisouP/items/18b31b024b117009137a)
- [C#のプロパティによるパターンマッチングを使おう](https://qiita.com/emoacht/items/dc1c40769dc6cdc1ef44)

でも、パターンマッチングの中身が実際どんな判定（動き）してるのかよくわからないし、なんか怖い。わけわからん動きをするバグの狂気に陥りそう。

そんなわけでパターンマッチングのデコンパイルコードを確認してみました。そこそこ実際のうごきを把握できる（はずな）ので経験値をかせげると思います。

> デコンパイルコードの確認には下図のとおり「[sharplab](https://sharplab.io/)」を利用しています。（あと IL は長くなるので記事では掲載外）

<img width="600" alt="image.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/55937/5d2dc078-13d4-be9c-f07d-a5470412f7a2.png">


C# は 2021 年末現在 C# 10.0 ですが、パターンマッチングは C# 7.0 から 10.0 のなかでアップデートが進んでいます。最新の .NET 6 などでテストしてみるとよいと思います。

> Visual Studio 2019 だと .NET 5 まで対応。C# 9.0 までのパターンマッチングで遊ぶことができます。デフォルトだと C# 10.0 の「[プロパティパターンの拡張](https://ufcpp.net/study/csharp/datatype/patterns/?p=2#sub-pattern-name)」は利用できないはずです。

# パターンマッチングの比較

古式ゆかしい if 文と、パターンマッチングを利用した if 文です。二つの式は同じうごきをしますが、デコンパイルした C# のコードに違いはあるのかチェックしてみる。（クラスとメソッドはなくてもいいのですが、デコンパイルコードと比較しやすいように掲載）

```cs
using System;

public class Test
{
    public static void Main() 
    {
        var a = (x:1, y:2);
        
        if (a.x == 1 && a.y == 2)
        {
            Console.WriteLine("A");
        }
        
        if (a is { x: 1, y: 2 })
        {
            Console.WriteLine("B");
        }
    }
}
```

下が、C# Decompile のコードです。完全に生成されたコードが一致していました。なんで、見た目が違うだけで同じコードだとわかりました。

```cs
public class Test
{
    public static void Main()
    {
        ValueTuple<int, int> valueTuple = new ValueTuple<int, int>(1, 2);
        if (valueTuple.Item1 == 1 && valueTuple.Item2 == 2)
        {
            Console.WriteLine("A");
        }
        if (valueTuple.Item1 == 1 && valueTuple.Item2 == 2)
        {
            Console.WriteLine("B");
        }
    }
}
```

パターンマッチングのなかで `_` をつかって、なんでもいい値を表現することがあると思います。

```cs
using System;

public class Test
{
    public static void Main() 
    {
        var p = (x: 10, y: 20);
        
        if (p.Item1 == 10) Console.WriteLine("A");
        if (p is {x: 10, y: _}) Console.WriteLine("B");
        if (p is {x: 10 }) Console.WriteLine("C");
    }
}

```

`B` と `C` を出力する式は、同じ意味だとなんとなくわかると思います。下のように生成コードも同じです。（逆にこの場合は書いていても人の読みやすさだけに効果があり、実行時の処理コストは無い）

```cs
public class Test
{
    public static void Main()
    {
        ValueTuple<int, int> valueTuple = new ValueTuple<int, int>(10, 20);
        if (valueTuple.Item1 == 10)
        {
            Console.WriteLine("A");
        }
        if (valueTuple.Item1 == 10)
        {
            Console.WriteLine("B");
        }
        if (valueTuple.Item1 == 10)
        {
            Console.WriteLine("C");
        }
    }
}
```


# null チェックの比較

知らないと絶対意味がわからないパターンマッチングの `null` チェック。

```cs
using System;

public class Test
{
    public static void Main() 
    {
        int? p = null;
        
        if (p != null) Console.WriteLine("A");
        if (p is not null) Console.WriteLine("B");
        if (p is {}) Console.WriteLine("C");

    }
}
```

完全に同じコードになります。安心した。わけのわからない追加コードはありません。

```cs
public class Test
{
    public static void Main()
    {
        Nullable<int> num = null;
        if (num.HasValue)
        {
            Console.WriteLine("A");
        }
        if (num.HasValue)
        {
            Console.WriteLine("B");
        }
        if (num.HasValue)
        {
            Console.WriteLine("C");
        }
    }
}
```

# 型パターン

型パターンも知らなかったら、コードが読めなくなるやつだと思います。
従来書いてたコードとすこし異なっていて機械的なコードが生成されました。が、常識の範囲内のごく普通のコードです。

```cs
using System;

public class Test
{
    public static void Main() 
    {
        object i = 1;
        
        if (i is int)
        {
            int i2 = (int)i;
            Console.WriteLine(i2);
        }
        
        if (i is int i3)
        {
            Console.WriteLine(i3);
        }
    }
}
```

```cs
public class Test
{
    public static void Main()
    {
        object obj = 1;

        // i2 のテストコード
        if (obj is int)
        {
            int value = (int)obj;
            Console.WriteLine(value);
        }

        // i3 のテストコード
        int value2 = default(int);
        int num;
        if (obj is int)
        {
            value2 = (int)obj;
            num = 1;
        }
        else
        {
            num = 0;
        }
        if (num != 0)
        {
            Console.WriteLine(value2);
        }
    }
}
```

> WPF なんかだと [IValueConverter](https://docs.microsoft.com/ja-jp/dotnet/api/system.windows.data.ivalueconverter?view=net-6.0) みたいに、データを object 型にして渡してくるやつと格闘する機会に恵まれると思います。そういうときに型パターンやパターンマッチングを利用すると、（ガード節の部分なんかで）マシになると思います。

# switch 文のパターンマッチング１

これもチェックしておきます。

```cs
using System;

public class Test
{
    public static void Main() 
    {
        var p = (x: 10, y: 20);
        
        var p1 = p switch
        {
            { x: 10 } => "A",
            { y: >= 20 } => "B",
            _ => "X"
        };
        
        Console.WriteLine(p1);
    }
}

```

```cs
public class Test
{
    public static void Main()
    {
        ValueTuple<int, int> valueTuple = new ValueTuple<int, int>(10, 20);
        int item = valueTuple.Item1;
        string text;
        if (item != 10)
        {
            int item2 = valueTuple.Item2;
            text = ((item2 < 20) ? "X" : "B");
        }
        else
        {
            text = "A";
        }
        string value = text;
        Console.WriteLine(value);
    }
}
```

もうひとつ似たようなパターンを見ておきます。ほとんど同じコードが生成されています。

```cs
using System;

public class Test
{
    public static void Main() 
    {
        var p = (x: 10, y: 20);
        string p2 = "";
        
        switch (p)
        {
            case var (x, _) when x == 10:
                p2 = "A";
                break;
            case var (_, y) when y >= 20:
                p2 = "B";
                break;
            default:
                p2 = "X";
                break;
        }
        
        Console.WriteLine(p2);
    }
}
```

```cs
public class Test
{
    public static void Main()
    {
        ValueTuple<int, int> valueTuple = new ValueTuple<int, int>(10, 20);
        string text = "";
        ValueTuple<int, int> valueTuple2 = valueTuple;
        ValueTuple<int, int> valueTuple3 = valueTuple2;
        int item = valueTuple3.Item1;
        if (item != 10)
        {
            int item2 = valueTuple3.Item2;
            text = ((item2 < 20) ? "X" : "B");
        }
        else
        {
            text = "A";
        }
        Console.WriteLine(text);
    }
}
```

# switch 文のパターンマッチング２

型をチェックするパターンマッチングの例です。先の例と変わらないかも。

```cs
public class Test
{
    public static void Main() 
    {
        int a = 1;
        
        Method(a);
    }
    
    public static string Method(object o)
    {
        string result = "";
        
        switch (o)
        {
            case int i when i > 0:
                result = "A";
                break;
            case double d when d > 10:
                result = "B";
                break;
            default:
                result = "X";
                break;
        }
        
        return result;
    }
}
```

```cs
public class Test
{
    public static void Main()
    {
        int num = 1;
        Method(num);
    }

    public static string Method(object o)
    {
        string text = "";
        if (o is int)
        {
            int num = (int)o;
            if (num > 0)
            {
                return "A";
            }
        }
        else if (o is double)
        {
            double num2 = (double)o;
            if (num2 > 10.0)
            {
                return "B";
            }
        }
        return "X";
    }
}
```

ちなみに、when を and に変えたコード (C# 9.0) でも、生成コードは変わらなかったです。

```cs
using System;

public class Test
{
    public static void Main() 
    {
        int a = 1;
        
        Method(a);
    }
    
    public static string Method(object o)
    {
        string result = "";
        
        switch (o)
        {
            case int i and > 0:
                result = "A";
                break;
            case double d and > 10:
                result = "B";
                break;
            default:
                result = "X";
                break;
        }
        
        return result;
    }
}
```
```cs
public class Test
{
    public static void Main()
    {
        int num = 1;
        Method(num);
    }

    public static string Method(object o)
    {
        string text = "";
        if (o is int)
        {
            int num = (int)o;
            if (num > 0)
            {
                return "A";
            }
        }
        else if (o is double)
        {
            double num2 = (double)o;
            if (num2 > 10.0)
            {
                return "B";
            }
        }
        return "X";
    }
}
```

# 個人的な感想

デコンパイルしたコードを確認してわかったことは、パターンマッチングのコードから見たこともない**恐ろしいコードが生成されることはありませんでした**。パターンマッチングのコードは、[覚えきれない何千もの恐怖の形状](http://www.asahi-net.or.jp/~yz8h-td/misc/TheUnnamableJ.html)ではなかったので、僕は狂気に陥らずにすみそうです。

というよりも、パターンマッチングのコードは、今風にイメージチェンジしただけの既存コードでした。C# の非同期、async/await とコルーチンを[整理した](https://shikaku-sh.hatenablog.com/entry/unity-organize-async-await-coroutine)ときほどの苦労はありません。何か今までのコードと違っていたり、添加されている新しいコードもないとわかって安心できました。

それから、パターンマッチングのコードが読めないときは、デコンパイルした C# コードを確認してみるのは勉強と助けになると思いました。

# 参考

- [Microsoft Docs - パターン マッチングの概要](https://docs.microsoft.com/ja-JP/dotnet/csharp/fundamentals/functional/pattern-matching)
- [sharplab](https://sharplab.io/)
- [++C++; パターン マッチング](https://ufcpp.net/study/csharp/datatype/patterns/?p=2#sub-pattern-name)