11
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

C#Advent Calendar 2021

Day 23

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

Last updated at Posted at 2021-12-22

C# Advent Calendar 2021 を見てると、C# のパターンマッチングが便利らしい。

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

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

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

image.png

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

Visual Studio 2019 だと .NET 5 まで対応。C# 9.0 までのパターンマッチングで遊ぶことができます。デフォルトだと C# 10.0 の「プロパティパターンの拡張」は利用できないはずです。

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

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

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 のコードです。完全に生成されたコードが一致していました。なんで、見た目が違うだけで同じコードだとわかりました。

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");
        }
    }
}

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

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");
    }
}

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

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 チェック。

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");

    }
}

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

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");
        }
    }
}

型パターン

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

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);
        }
    }
}
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 みたいに、データを object 型にして渡してくるやつと格闘する機会に恵まれると思います。そういうときに型パターンやパターンマッチングを利用すると、(ガード節の部分なんかで)マシになると思います。

switch 文のパターンマッチング1

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

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);
    }
}

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);
    }
}

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

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);
    }
}
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 文のパターンマッチング2

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

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;
    }
}
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) でも、生成コードは変わらなかったです。

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;
    }
}
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";
    }
}

個人的な感想

デコンパイルしたコードを確認してわかったことは、パターンマッチングのコードから見たこともない恐ろしいコードが生成されることはありませんでした。パターンマッチングのコードは、覚えきれない何千もの恐怖の形状ではなかったので、僕は狂気に陥らずにすみそうです。

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

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

参考

11
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
11
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?