【VB.NET】.NET 9でLINQに追加されたメソッドを使ってみる

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

11月13日に「.NET 9.0」 がリリースされました。
VB.NETは新機能の対応はしない方針ではあるが、記法に関係ない部分は対応します。

# .NET 9.0でLINQに追加されたメソッド
.NET 9でLINQに追加された各メソッド`CountBy`と`AggregateBy`と`Index`ができるか確認してみました。

https://qiita.com/RyotaMurohoshi/items/595b87e1db93768d0d44

サンプルが分かりにくかったので下記サイトを参考に変更してみました。

https://steven-giesel.com/blogPost/0594ba85-356b-47f1-89a9-70e9761c582e


# 共通モジュール
```vb
Public Class Person
    Public FirstName As String
    Public LastName As String
    Public Point As Integer
End Class

Sub Main(args As String())
    Dim people As New List(Of Person) From {
        New Person With {.FirstName = "鈴木", .LastName = "一郎", .Point = 78},
        New Person With {.FirstName = "鈴木", .LastName = "花子", .Point = 92},
        New Person With {.FirstName = "山田", .LastName = "太郎", .Point = 85},
        New Person With {.FirstName = "佐藤", .LastName = "健太", .Point = 95},
        New Person With {.FirstName = "佐藤", .LastName = "美咲", .Point = 88}
    }
```

# CountBy
CountByメソッドは、グループ分けしてグループごとの個数を求めるメソッドです。

```vb
For Each sameFirstName In people.CountBy(Function(p) p.FirstName)
    Console.WriteLine($"同じ苗字が {sameFirstName} 人、{sameFirstName}")
Next
```

## 結果
```
同じ苗字が 2 人、鈴木
同じ苗字が 1 人、山田
同じ苗字が 2 人、佐藤
```

# AggregateBy
AggregateByはGroupByとAggregateを合体させたようなメソッドです。グルーピングしつつ、グループごとにアグリゲーション(まとめる、集計)メソッドです。

```vb
Dim aggregateBy = people.AggregateBy(Function(p) p.Point \ 10, Function(x) 0, Function(x, y) x + y.Point)
For Each kvp In aggregateBy
    Console.WriteLine($"同一ポイント {kvp.Key * 10}点台 の集計結果 {kvp.Value}")
Next
```

## 結果
```
同一ポイント 70点台 の集計結果 78
同一ポイント 90点台 の集計結果 187
同一ポイント 80点台 の集計結果 173
```

# Index
Indexメソッドは各要素をインデックスと合わせてタプルにした返すメソッドです。

```vb
For Each p In people.Index()
    Console.WriteLine($"エントリー {p.Index + 1}: : Person {{ FirstName = {p.Item.FirstName}, LastName = {p.Item.LastName}, Point = {p.Item.Point} }}")
Next
```

Selectのオーバーロードを利用して同じことができる。

```vb
For Each item In people.Select(Function(Value, Index) New With {Value, Index})
    Console.WriteLine($"エントリー {item.Index + 1}: Person {{ FirstName = {item.Value.FirstName}, LastName = {item.Value.LastName}, Point = {item.Value.Point} }}")
Next
```

## 結果
```
エントリー 1: Person { FirstName = 鈴木, LastName = 一郎, Point = 78 }
エントリー 2: Person { FirstName = 鈴木, LastName = 花子, Point = 92 }
エントリー 3: Person { FirstName = 山田, LastName = 太郎, Point = 85 }
エントリー 4: Person { FirstName = 佐藤, LastName = 健太, Point = 95 }
エントリー 5: Person { FirstName = 佐藤, LastName = 美咲, Point = 88 }
```

# その他
LINQ以外でも`OrderedDictionary`が追加されています。

## OrderedDictionary
OrderedDictionaryクラスは順序を保証します。

https://qiita.com/prozolic/items/3fbdbcf8442d84d8f244

Swift版の`Dictionary`クラスのように直ぐ順序に変更されるなら分かるのですが…

https://zenn.dev/treastrain/articles/e4bac83e2cc5a6

.NET版の通常の`Dictionary`クラスは順序を保証しない実装ですが、結構一致します。

https://ameblo.jp/masaru-run/entry-12848895934.html

```vb
' インスタンス作成
Dim dict As New OrderedDictionary(Of String, Integer) From {
    {"A", 1},
    {"B", 2},
    {"C", 3}
}

' キーと要素を追加
dict.Add("D", 4)

' キーに紐づく要素を削除
dict.Remove("D")

' キーと要素を追加
dict.Add("E", 5)

' キーに紐づく要素を検索し、見つかった場合には要素を取得する
Dim value As Integer
If dict.TryGetValue("A", value) Then
    Console.WriteLine(value) ' 1
End If

' 保持しているキーと要素を列挙する
' 追加した順番（登録した順番）で列挙する
For Each pair In dict
    Console.WriteLine($"key = {pair.Key}, Value = {pair.Value}")
Next
```

## 結果
```
1
key = A, Value = 1
key = B, Value = 2
key = C, Value = 3
key = E, Value = 5
```

# 最後に
C# で作成して、ChatGPTでVB.NET に変換してみたんですが、まだちゃんとした対応されていないようで変な変換をしてしまう。
よって、ちゃんと理解した上で記事を修正しました。