【VB.NET】拡張コンパイラModVBの紹介(使い方編)

# はじめに
これは、[Visual Basic Advent Calendar 2022](https://qiita.com/advent-calendar/2022/vb)の23日目の記事となります。

前回、ModVBのインストールの記事を書きました。今回は使い方になります。

https://qiita.com/yaju/items/2c154a939b9896c855e2

# 使い方
使用するコードは、デフォルトのJSON タイプとして`Newtonsoft.Json`を使用します。

## 参照
* [Introducing ModVB—A free, independently-created “mod” for VB.NET that gives you new features just by installing a VSIX](https://anthonydgreen.net/2022/08/20/introducing-modvb/)
* [ModVB “Wave 1”—JSON Literals & Pattern Matching | Anthony's blog (anthonydgreen.net)](https://anthonydgreen.net/2022/08/20/modvb-wave-1-json-literals-and-pattern-matching/)

## JSON定数
変数をXML定数で初期化できるように、ModVBはJSON定数で変数の初期化をサポートしており、その使用法はXML定数と多少似ています。
たとえば、NuGetツールバージョンのクエリ結果のスニペットを見てみましょう。
[https://dist.nuget.org/tools.json](https://dist.nuget.org/tools.json)リクエストは、次のようなJSONを取得して変数に格納できます。

```vb
Module Program

    Sub Main()
        Dim latestVersion = "6.3.0"
        Dim stableVersions = {
            (ver:="6.2.1", uploaded:="2022-06-14T17:00:00.0000000Z"),
            (ver:="6.2.0", uploaded:="2022-05-10T13:00:00.0000000Z"),
            (ver:="6.1.0", uploaded:="2022-02-15T13:00:00.0000000Z")
        }

        Dim nugetVersions = {
            "nuget.exe": [
                {
                    "version": latestVersion,
                    "url": $"https://dist.nuget.org/win-x86-commandline/v{latestVersion}/nuget.exe",
                    "stage": "Released",
                    "uploaded": "2022-08-09T13:00:00.0000000Z"
                },
                From verInfo In stableVersions
                Select {
                    "version": verInfo.ver,
                    "url": $"https://dist.nuget.org/win-x86-commandline/v{verInfo.ver}/nuget.exe",
                    "stage": "ReleasedAndBlessed",
                    "uploaded": verInfo.uploaded
                }
            ]
        }

        Console.WriteLine(nugetVersions)
    End Sub
End Module
```

このコードを実行した結果は次のとおりです。

```json:実行結果
{
  "nuget.exe": [
    {
      "version": "6.3.0",
      "url": "https://dist.nuget.org/win-x86-commandline/v6.3.0/nuget.exe",
      "stage": "Released",
      "uploaded": "2022-08-09T13:00:00.0000000Z"
    },
    {
      "version": "6.2.1",
      "url": "https://dist.nuget.org/win-x86-commandline/v6.2.1/nuget.exe",
      "stage": "ReleasedAndBlessed",
      "uploaded": "2022-06-14T17:00:00.0000000Z"
    },
    {
      "version": "6.2.0",
      "url": "https://dist.nuget.org/win-x86-commandline/v6.2.0/nuget.exe",
      "stage": "ReleasedAndBlessed",
      "uploaded": "2022-05-10T13:00:00.0000000Z"
    },
    {
      "version": "6.1.0",
      "url": "https://dist.nuget.org/win-x86-commandline/v6.1.0/nuget.exe",
      "stage": "ReleasedAndBlessed",
      "uploaded": "2022-02-15T13:00:00.0000000Z"
    }
  ]
}
```

上記の例で、`nugetVersions`の変数を抽出できます。
JSON定数は、オブジェクト階層を深くすることなく、複数のJSONオブジェクトを1つにマージするために使用できるフラット化操作をサポートします。

次のコードは最初のバージョン情報から2つの属性を変数に抽出し、残りのバージョン情報から別の変数を抽出します。
プログラムの実行結果は上記と変わりません。


```vb
Module Program

    Sub Main()
        Dim latestVersion = "6.3.0"

        Dim stableVersions = {
            (ver:="6.2.1", uploaded:="2022-06-14T17:00:00.0000000Z"),
            (ver:="6.2.0", uploaded:="2022-05-10T13:00:00.0000000Z"),
            (ver:="6.1.0", uploaded:="2022-02-15T13:00:00.0000000Z")
        }

        Dim stable = From verInfo In stableVersions
                     Select {
                      "version": verInfo.ver,
                      "url": $"https://dist.nuget.org/win-x86-commandline/v{verInfo.ver}/nuget.exe",
                      "stage": "ReleasedAndBlessed",
                      "uploaded": verInfo.uploaded
                    }

        Dim releasedStage = {
            "stage": "Released",
            "uploaded": "2022-08-09T13:00:00.0000000Z"
        }

        Dim nugetVersions = {
            "nuget.exe": [
                {
                    "version": latestVersion,
                    "url": $"https://dist.nuget.org/win-x86-commandline/v{latestVersion}/nuget.exe",
                    {releasedStage}
                },
                stable
            ]
        }

        Console.WriteLine(nugetVersions)
    End Sub

End Module
```

## JSON パターン マッチング
JSON パターンマッチングは、JSONの構造や内容を判断しながら、必要な情報を変数に抽出することができます。

例
JSON 定数の例で JSON を例に取ります。
例えば、`stable`が`ReleasedAndBlessed`である`nuget.exe`のすべてのバージョンとその他の情報を取得する必要があるとします。
次のコードを使用できます。

上記の**JSON定数**コードの続きに記述してください。

```vb
    Console.WriteLine("リリース済みのすべての安定版：")

    Select Case ShapeOf nugetVersions
        Case {"nuget.exe": nodes}
            For Each node In nodes
                Select Case ShapeOf node
                    Case {
                        "version": ver As Version,
                        "url": url As String,
                        "stage": "ReleasedAndBlessed",
                        "uploaded": relaseDate As Date
                    }

                    Console.WriteLine($"{ver} リリース {relaseDate:F}， ダウンロード先 {url}")
                End Select
            Next
    End Select
```

コンソール

```text:出力結果
リリース済みのすべての安定版：
6.2.1 リリース 2022年6月15日 2:00:00， ダウンロード先 https://dist.nuget.org/win-x86-commandline/v6.2.1/nuget.exe
6.2.0 リリース 2022年5月10日 22:00:00， ダウンロード先 https://dist.nuget.org/win-x86-commandline/v6.2.0/nuget.exe
6.1.0 リリース 2022年2月15日 22:00:00， ダウンロード先 https://dist.nuget.org/win-x86-commandline/v6.1.0/nuget.exe
```

### Select Case ShapeOf ステートメント
`Select Case ShapeOf`は、`Select Case`内の`Case`の意味を変え、通常の`Select Case`の構文と矛盾することなくパターンマッチを行うことができる、パターンマッチに特化した`Select Case`です。

**JSON オブジェクトに一致**
JSONオブジェクトのマッチングには、以下のコードのように、JSONマッチパターンを中括弧で囲む必要があります。

```vb
Case {
    "属性1": "値1",
    "属性2": "値2",
    "属性3": null,
    "属性4"?: "null可能属性"
```

* マッチングの順番は列の上から下、左から右の順です。コードでは、属性1が先にマッチングされ、属性2が後にマッチングされる。
* 属性を除外するには、値がNULLの属性にマッチさせることができます。この図では、属性3が空であることを要求しています。このようにJSONは属性3を書かないか、属性3をNULLで書くことで一致させる。
* コロンの左側にクエスチョンマークが書かれている場合、そのプロパティが空でもマッチします。コード中の属性4は空でもよい。これには、属性が書き込まれていない場合や、属性値がNULLで書き込まれている場合も含まれる。
* JSONに別の属性があり、その属性のマッチングルールが中括弧の中に書かれている場合、残りの属性はマッチング結果に影響を及ぼさない。
中括弧の中に何も書かれていない場合は、空のJSONオブジェクトのみがマッチングされます。

### JSON配列に一致
JSON配列のマッチングには、下コードのように、JSONマッチパターンを2つの括弧で囲むことが必要です。

```vb
Case [ "要素1", "要素2", 345, true, false, null ]
```

* 上コードの意味は，少なくともこれらの要素が指定された配列の先頭から順に， "要素1", "要素2", 345, true, false, null とマッチすることです． もし配列が予想より長くなっても，マッチは成功します．
* 括弧の中に何も書かれていない場合は、空の配列のみがマッチングされる。

### JSON定数に一致
ここでは、VBの定数式であれば何でも使用できます。JSON定数（true, false, null）も使用可能です。

**変数の種類によるマッチングと定義**

```vb
Case {
    "属性1": value1 As Version,
    "属性2": value1 As String,
    "属性3": value3
    }
```

* 値がNULLでなく、指定された型に変換できる場合、マッチする。
* この変換では、`TryCast`、VBの組み込み変換ルール、および`TryParse`などの他の変換方法が考慮されます。
* コードのプロパティ1は、JSONコンテンツを文字列として解析し、`TryParse`を試行します。
* コード中のプロパティ2は、JSONを文字列として解析します。
* コードのプロパティ3は、プロパティ3が空でなければ、JSONの値をそのまま返します。

### 疑問符を使用してオプションであることを示します
基本的にすべてのパターンマッチング式は、最後にクエスチョンマークを書くことができ、それによって式がNULLにもマッチするようになります。ここでは、属性`undefined`と値`null`は同じケースと見なします。以下にいくつかの例を示します。

**`null`になる可能性のあるオブジェクトにマッチする**

```vb
Case {
    "属性1": "値1",
    "属性2": "値2"
    }?
```

**null 許容値に一致する**

```vb
Case {
    "属性1": "値1",
    "属性2": "値2"?
    }
```

**属性1は空にすることができ、属性2は属性に一致して変数を定義し、属性値は空にすることができます。**

```vb
Case {
    "属性1": [ "値1" ]?,
    "属性2": value1?
    }
```
属性値が空の場合、定義された変数にはデフォルト値があります。

## ランタイム拡張機能とAPIの互換性
JSONの定数とパターンマッチは、`LINQ`や`For Each`ループと同様、パターンベースです。
特定のライブラリやタイプに縛られることはありません。
これらの関数は、コードの解析やコンパイル時に、特定の名前空間からメンバを使用して適格な型を検索します。
ModVBコンパイラを含むパッケージをインストールするだけで、特定のパターンマッチング拡張を自分で記述することも可能です。
JSON定数やパターンマッチを使用しているプロジェクトをデコンパイルすることで、JSON定数拡張パッケージやパターンマッチ拡張パッケージの書き方を学ぶことができます。

# 最後に
この記事は下記のModVB紹介記事を参考にDeepLなどを駆使して書きました。

https://www.cnblogs.com/Nukepayload2/p/16633559.html

ModVBでは、このように拡張機能で使えるようになります。今回はWave1のみです。
今後のModVBの機能強化に期待しましょう。

## ModVBの機能予定
現時点(2022/12/22)では、Wave 1は今年の夏にリリースされました。WAVE 2が12月予定とのことでしたが、まだ未リリースです。

|バージョン|機能内容|
| ---- | ---- |
|Wave 1|JSON リテラルとパターン マッチングを中心に展開|
|Wave 2|XML リテラルの強化|
|Wave 3|不明 Wave 2 と Wave 4の中間の機能強化|
|Wave 4|Async ストーリー強化|
|Wave 5|LINQ のより深い統合|