【入門編】VB.NETのYieldキーワードを用いたイテレータ(Iterator)の実装:大規模データをメモリ効率よく遅延評価で返すカスタム列挙の作り方 – Visual Basic (VB / VB.NET)解析バイブル

スポンサーリンク

こんにちは!開発現場を渡り歩いてきた、あなたの専属エンジニアの先輩です。

今回は、Visual Basic(VB.NET)の中級者へのステップアップとして、避けて通れないけれど最高にクールな技術「Yieldキーワードを用いたイテレータ(Iterator)」についてお話しします。

「マクロの記録から一歩抜け出して、本格的なシステムを作りたい!」
「数百万件の巨大データを扱ったらメモリがパンクしてアプリが落ちた……」

そんな悩みを抱えたことはありませんか?ここをクリアすれば、あなたの書くVB.NETコードは一気にプロフェッショナルな領域へと進化します。メモリ効率を極限まで高める「遅延評価」の世界へ、一緒に出発しましょう!

1. なぜ「全件メモリ読み込み」は悪なのか?

皆さんは、データベースや巨大なCSVファイルからデータを取得するとき、次のようなコードを書いていませんか?

.net
‘ 【アンチパターン】全件を一度にメモリにロードする例
Public Function GetHugeData() As List(Of String)
Dim results As New List(Of String)()

‘ 例えば100万件のデータをファイルから読み込むとする
For i As Integer = 1 To 1000000
results.Add(“データ #” & i.ToString())
Next

Return results ‘ 100万件分のオブジェクトが一度にメモリに乗る!
End Function

このコード、数千件程度なら問題ありません。しかし、100万件、1000万件とデータが膨れ上がった瞬間、サーバーやクライアントのメモリ(RAM)を激しく消費し、最悪の場合は `OutOfMemoryException`(メモリ不足例外)でアプリがクラッシュします。

さらに悪いことに、呼び出し側は「最初の1件目」が欲しいだけなのに、システムは100万件全部をメモリに読み込み終わるまで待たされることになります。これではユーザー体験(UX)も最悪ですよね。

2. 救世主「Yield」とイテレータの仕組み

ここで登場するのが、VB.NETの `Iterator``Yield` です。

これらを使うと、データを「まとめてドンッ」と返すのではなく、「必要なときに、1件ずつ渡す(遅延評価:Lazy Evaluation)」という賢い処理ができるようになります。

イメージ図:コンベアベルト方式

  • 従来のやり方(List等):

巨大なダンボール箱に全データ(100万個)を詰め込んで、ドスンと相手に投げつける(重い・遅い)。

  • Yieldのやり方(イテレータ):

工場長のあなたと、作業員の呼び出し側がコンベアベルトで繋がっているイメージ。「次の1件ちょうだい」と言われたら、その場で1個だけ作って流す(軽い・速い)。

3. 実装してみよう:Yieldを使ったカスタム列挙

それでは、実際にVB.NETでコードを書いてみましょう。
無限に連番データを生成する「無限シーケンス」を `Yield` を使って作ってみます。

.net
Imports System.Collections.Generic

Module IteratorSample

Sub Main()
Console.WriteLine(“— データを1件ずつ遅延評価で取得します —“)

‘ GetInfiniteNumbers() は「無限」にデータを返す定義だが、
‘ メモリは全くパンクしない!
For Each number As Integer In GetInfiniteNumbers()
Console.WriteLine($”取得した値: {number}”)

‘ 5件取得したらもう十分なので抜ける
If number >= 5 Then
Console.WriteLine(“もう十分なのでループを抜けます。”)
Exit For
End If
Next

Console.ReadLine()
End Sub

‘ Iterator Function と Yield ステートメントの組み合わせ
Public Iterator Function GetInfiniteNumbers() As IEnumerable(Of Integer)
Dim current As Integer = 1

While True
‘ Yield は、呼び出し元に値を1つ返し、処理の「一時停止」を行う
Yield current

‘ 次回、呼び出し元から次のデータが求められたら、ここから再開する
current += 1
End While

End Function

End Module

このコードの何がスゴいのか?

1. `While True` なのに無限ループで固まらない
普通、条件なしの `While True` を書いたらCPU使用率が100%になりアプリがフリーズします。しかし、`Yield` があることで、呼び出し側が次のデータを要求するまで、この関数は眠った状態(サスペンド)になります。
2. メモリ消費量が常に一定(O(1))
100万回ループしようが、10億回ループしようが、メモリ上に展開されるのは「今処理している1つの値」だけです。

4. 現場で使える!巨大ファイル行読み取りの例

理論が分かったところで、実務でよくある「巨大なテキストファイルを1行ずつメモリ効率よく処理する」実用的なコードを見てみましょう。

.net
Imports System.IO
Imports System.Collections.Generic

Public Class LogReader

‘ 巨大なログファイルから、特定のキーワードを含む行だけを遅延評価で返す
Public Iterator Function ReadErrorLogs(filePath As String) As IEnumerable(Of String)

‘ Usingを使うことで、ファイルを安全に開閉する
Using reader As New StreamReader(filePath)

While Not reader.EndOfStream
Dim line As String = reader.ReadLine()

‘ 「ERROR」という文字が含まれている行だけをフィルタリングして返す
If line IsNot Nothing AndAlso line.Contains(“ERROR”) Then
Yield line
End If

End While

End Using

End Function

End Class

この書き方をしていければ、数GBある巨大なログファイルであっても、ファイルを一気にメモリへ読み込むことなく、エラー行が見つかったその瞬間に処理することが可能です。

5. 陥りやすい罠と注意点(ここテストに出ます!)

非常に強力な `Yield` ですが、VB.NETプログラマがやりがちなミスがいくつかあります。ここをしっかり押さえておきましょう。

注意点1: 戻り値の型は必ず `IEnumerable(Of T)` や `IEnumerator(Of T)` にする

`Iterator Function` の戻り値として、`List(Of T)` や単体の型を指定することはできません。必ず列挙可能なインターフェースである `IEnumerable(Of T)`(または `IEnumerable`)を指定してください。

注意点2: `Yield` のある関数内では `Return` に値を書けない

通常の関数であれば `Return result` のように値を返しますが、Iterator関数の中では値を返すのは `Yield` の役目です。関数を終了させたい場合は、値を持たない `Return`(または単に処理の終端への到達)を使います。

注意点3: `Try-Catch-Finally` との組み合わせに注意

`Yield` ステートメントは `Try…Catch` の `Try` ブロック内には書けますが、`Catch` や `Finally` ブロックの中には直接書くことができません。例外処理と組み合わせる際は、構造に少し気をつける必要があります。

まとめ:ここをクリアすればVB.NETの基本はバッチリ!

今回は、VB.NETの `Yield` キーワードとイテレータについて解説しました。

  • 全件一括読み込みはメモリリークやクラッシュの原因になる
  • `Iterator Function` と `Yield` を使うと、必要な分だけデータを生成する「遅延評価」ができる
  • 無限シーケンスや巨大ファイルの処理で圧倒的なメモリ効率を発揮する

マクロの記録や、ただ画面からコピペするだけのコーディングから一歩抜け出し、こうした「裏側の仕組み(メモリやパフォーマンス)」まで意識できるようになれば、あなたはもう立派な優秀なエンジニアです。

ぜひ、日々の開発やちょっとしたバッチ処理のツール作成に `Yield` を取り入れて、キレッキレのコードを書いちゃってくださいね!応援しています!

タイトルとURLをコピーしました