Visual Basic 初級講座 [改訂版]
VB2005 VB2008 VB2010 VB2012 VB2013 VB2015 VB2017 VB2019

第38回 LINQのさまざまな機能

2021/7/18

この記事が対象とする製品・バージョン

VB2019 Visual Basic 2019 対象です。
VB2017 Visual Basic 2017 対象です。
VB2015 Visual Basic 2015 対象です。
VB2013 Visual Basic 2013 対象です。
VB2012 Visual Basic 2012 対象です。
VB2010 Visual Basic 2010 対象です。
VB2008 Visual Basic 2008 対象ですが、使用できない機能も含んでいます。
VB2005 Visual Basic 2005 × 対象外です。
VB.NET 2003 Visual Basic.NET 2003 × 対象外です。
VB.NET 2002 Visual Basic.NET (2002) × 対象外です。
VB6対応 Visual Basic 6.0 × 対象外です。

 

目次

 

1.LINQのさまざまな機能

1-1.LINQのキーワード

 Where や Select 、Order By など、LINQのクエリ式で使える専用のキーワードは LINQ演算子 と呼ばれ、全部で14種類あります。

参考

クエリ - Visual Basic | Microsoft Docs

標準クエリ演算子の概要 - Visual Basic | Microsoft Docs

 

最も基本的なキーワードは From, Where, Select です。これらとLetの基本的な使い方については前の回で説明しています。

グループでの集計を扱うGroup By、Group Joinは次の回に説明することにして、 この回では残りのキーワードの使い方を中心に説明します。

今回は、LINQの考え方のような説明はなく、リファレンス的な内容になりますから、ざっと見ていただいた後は必要に応じて戻ってきて確認するとういスタンスが良いと思います。

 

1ー2.Order By


From 反復変数 In データソース
Order By 並び替えの基準になる値 [Descending]

 

Order By は並び替えを行います。

次の例では基準となる値として反復変数 animal を指定していますので、データソースのコレクションの値自体が基準となります。結果として文字順に並びます。

VB2008 VB2010 VB2012 VB2013 VB2015 VB2017 VB2019

'データソース作成
Dim animals = {"イノシシ", "エリマキトカゲ", "アメンボ", "ウマ", "オオカミ"}

'LINQで処理を定義
Dim results = From animal In animals Order By animal

For i As Integer = 0 To results.Count - 1
    Debug.WriteLine($"{i}番目は、{results(i)}")
    'Debug.WriteLine(i.ToString & "番目は、" & results(i)) '←VB2013以前の場合
Next

結果は、アメンボ、イノシシ、ウマ、エリマキトカゲ、オオカミという順番になります。

 

Descending (読み方:Descending=ディセンディング)を使えると、逆順になります。

VB2008 VB2010 VB2012 VB2013 VB2015 VB2017 VB2019

'データソース作成
Dim animals = {"イノシシ", "エリマキトカゲ", "アメンボ", "ウマ", "オオカミ"}

'LINQで処理を定義
Dim results = From animal In animals Order By animal Descending

For i As Integer = 0 To results.Count - 1
    Debug.WriteLine($"{i}番目は、{results(i)}")
    'Debug.WriteLine(i.ToString & "番目は、" & results(i)) '←VB2013以前の場合
Next

結果は、オオカミ、エリマキトカゲ、ウマ、イノシシ、アメンボという順番になります。

Descending の位置に Ascending(読み方:Ascending=アセンディング)を付けると昇順であることが明確になりますが、省略可能なので付ける人はまずいません。

 

次の例では要素のLengthプロパティを基準とし、Descendingを指定しているので、文字数が多い順に並びます。

VB2008 VB2010 VB2012 VB2013 VB2015 VB2017 VB2019

'データソース作成
Dim animals = {"イノシシ", "エリマキトカゲ", "アメンボ", "ウマ", "オオカミ"}

'LINQで処理を定義
Dim results = From animal In animals Order By animal.Length Descending

For i As Integer = 0 To results.Count - 1
    Debug.WriteLine($"{i}番目は、{results(i)}")
    'Debug.WriteLine(i.ToString & "番目は、" & results(i)) '←VB2013以前の場合
Next

結果は、エリマキトカゲ、イノシシ、アメンボ、オオカミ、ウマという順番になります。

 

いろいろと組み合わせて使うこともできます。

Windowsのダウンロードフォルダーにある一か月以上前のファイルのうち、サイズが大きいものを5つ抜き出してみます。

VB2010 VB2012 VB2013 VB2015 VB2017 VB2019

'ダウンロードフォルダーのファイルの一覧
Dim downloadFolder As New IO.DirectoryInfo(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile) & "\downloads")
Dim fileList As IEnumerable(Of IO.FileInfo) = downloadFolder.EnumerateFiles

'一か月以上前のファイルの中でサイズが大きいものを5つ取得する。
Dim files = From file As IO.FileInfo In fileList
            Where file.LastWriteTime < Now.AddMonths(-1)
            Order By file.Length Descending
            Take 5
            Select file.Name, MB = file.Length / 1024 / 1024

'結果表示
For Each file In files
    Debug.WriteLine(file.Name & " " & file.MB.ToString("0.0 MB"))
Next

これまで登場したWhere、Selectも組み合わせて使っています。結果を5つに限定するために Take 5 という指定もしています。(Takeはまだ説明していませんが、見れば使い方がわかると思います。)

ストレージを圧迫している古いファイルがないか探してみるのに良いですね。

 

1-3.Distinct


From 反復変数 In データソース Distinct

 

Distinct(読み方:Distinct=ディスティンクト)はデータソースから重複した要素を排除します。

 

次の例では重複した要素を省いて並び替えを行います。

VB2008 VB2010 VB2012 VB2013 VB2015 VB2017 VB2019

'データソース作成
Dim dataSource = {"A", "B", "A", "E", "C", "B", "E"}

'LINQで処理を定義
Dim results = From data In dataSource Distinct Order By data

'クエリを実行して結果を表示
results.ToList.ForEach(Sub(data) Debug.WriteLine(data))

Debug.WriteLineで出力される場所

結果は、"A", "B", "C", "E" と出力されます。

 

 

1-4.Take・Take While

Take (読み方:Take=テイク)と Take While (読み方:Take While=テイク ホワイル)は、データソースを先頭の方からいくつか抽出します。

先頭から抽出するので、並び替えを行うOrder Byと組み合わせると使い方の幅が広がります。

Take は抽出する数を指定します。

次の例では、先頭から 3 つの要素を抽出します。

VB2008 VB2010 VB2012 VB2013 VB2015 VB2017 VB2019

'データソース作成
Dim dataSource = {"A", "B", "C", "D", "E", "F", "G"}

'LINQで処理を定義
Dim results = From data In dataSource Take 3

'クエリを実行して結果を表示
results.ToList.ForEach(Sub(data) Debug.WriteLine(data))

Debug.WriteLineで出力される場所

実行すると、A, B, C が表示されます。

 

Take Whileの方は、数ではなく条件を指定して、一致するところまでを抽出します。

次の例では、先頭から順番に要素を抽出し、要素が"E"ではない限り抽出を続けます。

VB2008 VB2010 VB2012 VB2013 VB2015 VB2017 VB2019

'データソース作成
Dim dataSource = {"A", "B", "C", "D", "E", "F", "G"}

'LINQで処理を定義
Dim results = From data In dataSource Take While data <> "E"

'クエリを実行して結果を表示
results.ToList.ForEach(Sub(data) Debug.WriteLine(data))

Debug.WriteLineで出力される場所

実行すると、A, B, C, D が表示されます。

 

Order By と組み合わせるといろいろなバリエーションができます。

次の例では後ろから3つ抽出します。

 

VB2008 VB2010 VB2012 VB2013 VB2015 VB2017 VB2019

'データソース作成
Dim dataSource = {"A", "B", "C", "D", "E", "F", "G"}

'LINQで処理を定義
Dim results = From data In dataSource Order By data Descending Take 3

'クエリを実行して結果を表示
results.ToList.ForEach(Sub(data) Debug.WriteLine(data))

Debug.WriteLineで出力される場所

実行すると、G, F, E が表示されます。

結果が逆の順番で表示されているのが気に入らないようであれば、Take で抽出後にもう1回 Order By を使用して並び変えられます。

 

VB2008 VB2010 VB2012 VB2013 VB2015 VB2017 VB2019

'データソース作成
Dim dataSource = {"A", "B", "C", "D", "E", "F", "G"}

'LINQで処理を定義
Dim results = From data In dataSource Order By data Descending Take 3 Order By data

'クエリを実行して結果を表示
results.ToList.ForEach(Sub(data) Debug.WriteLine(data))

Debug.WriteLineで出力される場所

この結果は E, F, G になります。

 

1-5.Skip・Skip While

Skip (読み方:Skip=スキップ) と Skip While(読み方:Skip While=スキップ ホワイル)は、先頭からいくつかの要素を読み飛ばして、残りの要素を取得します。結果として Take とは逆の方が抽出されます。

次の例では、先頭から 3 つの要素を読み飛ばして、残りの要素を抽出します。

VB2008 VB2010 VB2012 VB2013 VB2015 VB2017 VB2019

'データソース作成
Dim dataSource = {"A", "B", "C", "D", "E", "F", "G"}

'LINQで処理を定義
Dim results = From data In dataSource Skip 3

'クエリを実行して結果を表示
results.ToList.ForEach(Sub(data) Debug.WriteLine(data))

Debug.WriteLineで出力される場所

実行すると、D, E, F, G が表示されます。

 

Skip Whileの方は、数ではなく条件を指定して、一致するところまでを読み飛ばします。

次の例では、要素が"E"ではない限り読み飛ばすので、先頭から順に A, B, C, D はスキップされます。残りの要素はすべて抽出されます。

VB2008 VB2010 VB2012 VB2013 VB2015 VB2017 VB2019

'データソース作成
Dim dataSource = {"A", "B", "C", "D", "E", "F", "G"}

'LINQで処理を定義
Dim results = From data In dataSource Skip While data <> "E"

'クエリを実行して結果を表示
results.ToList.ForEach(Sub(data) Debug.WriteLine(data))

Debug.WriteLineで出力される場所

実行すると、E, F, G が表示されます。

 

Take と Skip を組み合わせることもできます。先頭から2つ読み飛ばして、その後の3つを抽出するには次のようにします。

VB2008 VB2010 VB2012 VB2013 VB2015 VB2017 VB2019

'データソース作成
Dim dataSource = {"A", "B", "C", "D", "E", "F", "G"}

'LINQで処理を定義
Dim results = From data In dataSource Skip 2 Take 3

'クエリを実行して結果を表示
results.ToList.ForEach(Sub(data) Debug.WriteLine(data))

Debug.WriteLineで出力される場所

実行すると、C, D, E が表示されます。

 

 

2.Enumerableクラス

ここで少しLINQから離れて、Enumerableクラスの3つの便利な共有メソッドを紹介します。

便利な3つの共有メソッド

 

これらのメソッドはLINQとは直接関係はありませんが、簡単にデータソースを作ることができるので、コレクションを操作するプログラムでは便利に使用できることがあります。

Rangeメソッド (読み方:Range=レンジ)は、1,2,3,4,5,…というような連続したInteger型の数値のデータソースを生成します。このような連続したコレクションのことを「シーケンス」と呼ぶ場合もあります。

第1引数に最初の数値、第2引数に要素の数を指定します。

次の例では12から始まる連続した5つの数値のコレクションを生成します。

VB2008 VB2010 VB2012 VB2013 VB2015 VB2017 VB2019

'データソース作成
Dim dataSource = Enumerable.Range(12, 5) '12, 13, 14, 15, 16 というコレクションを生成

'LINQで処理を定義
Dim results = From data In dataSource Select data * 2

この例ではその後LINQで data * 2 を実行しているので、results は要素が2倍になって24, 26, 28, 30, 32 になります。

 

Enuemrableを使うと記述が短くなるので、次のようにLINQの中に直接組み込む書き方でもすっきりします。

VB2008 VB2010 VB2012 VB2013 VB2015 VB2017 VB2019


'LINQで処理を定義
Dim results = From data In Enumerable.Range(12, 5) Select data * 2

クエリ式を使わないで、次のようにメソッドチェーンを書くことを好む開発者もいます。この例では、値を出力することろまでを含みます。

VB2008 VB2010 VB2012 VB2013 VB2015 VB2017 VB2019

Enumerable.Range(12, 5).
    Select(Function(number) number * 2).
    ToList.
    ForEach(Sub(number) Debug.WriteLine(number))

 

Repeatメソッド(読み方:Repeat=リピート)は、同じ値の複数のコレクションを生成します。こちらは数値に限らず任意の型の値を扱えます。

第1引数に値、第2引数に要素の数を指定します。

VB2008 VB2010 VB2012 VB2013 VB2015 VB2017 VB2019


'データソース作成
Dim dataSource = Enumerable.Repeat("Apple", 5) 'Apple という要素を5つもつコレクションを生成

 

最後に Emptyメソッド(読み方:Empty=エンプティー)は、要素を持たない空のコレクションを生成します。型パラメーターを使ってコレクションの要素の型を指定できます。

VB2008 VB2010 VB2012 VB2013 VB2015 VB2017 VB2019


'データソース作成
Dim dataSource = Enumerable.Empty(Of Double) 'Double型の、要素を持たないコレクションを生成

 

 

3.Aggregate

3-1.集計操作

Aggregate(読み方:Aggregate=アグリゲイト)は合計や平均などの集計操作を行います。Aggregate は From ... In ... を必要としない唯一のクエリ式のキーワードです。

Into(読み方:Into=イントゥー)と組み合わせて次のような構文で使用します。


Aggregate 反復変数 In データソース Into 集計操作

この構文はもっとも単純な場合の例ですWhereなどの他のキーワードと組み合わせたり、複数の集計操作を行うこともできます。

 

単純な例として、合計を計算するには次のようにします。

VB2008 VB2010 VB2012 VB2013 VB2015 VB2017 VB2019

'データソース作成
Dim numbers = {1, 2, 3, 4, 5, 4}

'LINQで処理を定義
Dim result As Integer = Aggregate number In numbers Into Sum

'クエリを実行して結果を表示
Debug.WriteLine(result)

 

3-2.集計関数

集計操作として指定できる特別なキーワードは次の通りです。これらは集計関数と呼ばれます。

拡張メソッド 読み方 機能
Average アベレイジ 平均を計算します。
Count カウント 要素の数を数えます。結果をIntegerとして返します。
LongCount ロングカウント 要素の数を数えます。結果をLongとして返します。
Max マックス 最大値を求めます。
Min ミン 最小値を求めます。
Sum サム 合計値を求めます。

それぞれの集計関数は集計する対象の値を引数で指定することができます。何も指定しなかった場合、反復変数が集計対象になります。

たとえば、次の例ではデータソースの文字列の長さの平均を求めます。

VB2008 VB2010 VB2012 VB2013 VB2015 VB2017 VB2019

'データソース作成
Dim animals = {"アメンボ", "イノシシ", "ウマ", "エリマキトカゲ", "オオカミ"}

'LINQで処理を定義
Dim result As Double = Aggregate animal In animals Into Average(Len(animal))

'クエリを実行して結果を表示
Debug.WriteLine(result)

実行すると 4.2 と出力されます。

 

Count は単純に数を数えることもできますし、引数で条件を指定して条件を満たす要素の数を数えることもできます。

次の例では、4 未満の要素の数を数えます。

VB2008 VB2010 VB2012 VB2013 VB2015 VB2017 VB2019

'データソース作成
Dim numbers = {1, 2, 3, 4, 5, 4}

'LINQで処理を定義
Dim result As Integer = Aggregate number In numbers Into Count(number < 4)

'クエリを実行して結果を表示
Debug.WriteLine(result)

Max, Min, Sum は単純に最大値・最小値・合計を求める以外に、各要素に変換を適用して適用後の最大値・最小値・合計を求めることができます。

たとえば、次の例では要素の逆数の合計値を求めます。

※逆数とは 1を割った数です。たとえば、8の逆数は 0.125 (= 1 ÷ 8)です。

VB2008 VB2010 VB2012 VB2013 VB2015 VB2017 VB2019

'データソース作成
Dim numbers = {1, 2, 3, 4, 5, 4}

'LINQで処理を定義
Dim result As Double = Aggregate number In numbers Into Sum(1 / number)

'クエリを実行して結果を表示
Debug.WriteLine(result)

 

3-3.拡張メソッドによる集計操作

この他に、IEnumerable(Of T)の拡張メソッドを集計操作に指定することもできます。たとえば、 Any を使って、要素のどれか1つ以上が条件を満たすか調べるLINQを記述できます。

次の例はデータソースの中に偶数が含まれるか確認します。

VB2008 VB2010 VB2012 VB2013 VB2015 VB2017 VB2019

'データソース作成
Dim numbers = {1, 2, 3, 4, 5, 4}

'LINQで処理を定義
Dim result As Boolean = Aggregate number In numbers Into Any(number mod 2 = 0)

'クエリを実行して結果を表示
If result Then
    Debug.WriteLine("偶数が含まれています。")
Else
    Debug.WriteLine("偶数は含まれていません。")
End If

 

3-4.集計操作の自作

IEnumerable(Of T)の拡張メソッドを自作することで、Into に自作の関数を指定することができます。

次の例では、IEnumerable(Of T)の拡張メソッド Mode を作成しています。

この Mode メソッドは 最も多く出現する要素を取得します。(このような値をモード・最頻値と呼びます。)

VB2008 VB2010 VB2012 VB2013 VB2015 VB2017 VB2019

Public Module LINQExtension

    <Runtime.CompilerServices.Extension>
    Private Function Mode(Of T)(source As IEnumerable(Of T)) As T

    Dim counts As New Dictionary(Of T, Integer) '項目と出現回数の一覧
    Dim maxItem As T '現在までに最多出現した項目 Dim maxCount As Integer '現在までに最多出現した項目の出現数

    Dim itor = source.GetEnumerator
    While itor.MoveNext '現在の項目が既に一覧に含まれている確認する
        If counts.ContainsKey(itor.Current) Then '一覧に含まれている場合は、出現数に 1 をたす counts(itor.Current) += 1 Else '一覧に含まれていない場合は、一覧に追加し、出現数を 1 にする counts(itor.Current) = 1 End If
'現在までの最多出現数をこの項目が上回っているかチェック If maxCount < counts(itor.Current) Then '上回っているなら、今の項目を最多出現項目として記録 maxCount = counts(itor.Current) maxItem = itor.Current
        End If

    End While
'最多出現項目を返す
    Return maxItem

End Function End Module
 

この拡張メソッドが使用できる場所では、次のような LINQ を記述できます。

 

VB2008 VB2010 VB2012 VB2013 VB2015 VB2017 VB2019

'データソース作成
Dim numbers = {1, 2, 3, 4, 5, 4}

'LINQで処理を定義
Dim result As Boolean = Aggregate number In numbers Into Mode

'クエリを実行して結果を表示
Debug.WriteLine(result)

データソースでは 4 だけが 2回出現するので、結果は 4 と出力されます。

メモ メモ  - 拡張メソッド

拡張メソッドについては 第32回 モジュール で説明しています。

 

3-5.複数の集計

Into句には複数の集計を記述できます。この場合、結果はそれぞれの集計結果をプロパティとしてもつ匿名オブジェクトになります。

VB2010 VB2012 VB2013 VB2015 VB2017 VB2019

'データソース作成
Dim person1 = New With {.Name = "徳川家康", .Age = 20}
Dim person2 = New With {.Name = "豊臣秀吉", .Age = 25}
Dim person3 = New With {.Name = "織田信長", .Age = 30}

Dim persons = {person1, person2, person3}

'LINQで処理を定義
Dim result = Aggregate person In persons
                 Into Max(person.Age), Average(person.Age), Count

'クエリを実行して結果を表示
Debug.WriteLine($"最年長は{result.Max}歳")
Debug.WriteLine($"平均年齢は{result.Average}歳")
Debug.WriteLine($"集計母数は{result.Count}人")

'↓ VB2013以前の場合
'Debug.WriteLine("最年長は" & result.Max & "歳")
'Debug.WriteLine("平均年齢は" & result.Average & "歳")
'Debug.WriteLine("集計母数は" & result.Count & "人")

Selectと同じようにプロパティには名前を付けることもできます。