こんにちは!開発現場で日々、コードの海と格闘している先輩エンジニアです。
マクロの記録や、決まったセルをコピーするだけのコードから一歩抜け出し、「いよいよ本格的な業務システムをVB.NETで構築するぞ!」という段階に来たあなた。素晴らしい挑戦ですね。
今回は、実務で必ず直面する「データの並び順をどうコントロールするか」という壁を華麗に突破するための武器、`Stack(Of T)`(スタック)と`Queue(Of T)`(キュー)の世界へご案内します。
ここをクリアすれば、あなたの書くプログラムの「データ処理の美しさと効率」は一段と洗練されます。ぜひ最後までお付き合いくださいね。
—
1. なぜ「リスト(List)」だけでは現場で戦えないのか?
VB.NETを学び始めると、まずは `List(Of T)`(可変長配列)の便利さに感動しますよね。何でもポンポン追加できて、インデックスで取り出せる。
しかし、実際の業務アプリケーションを考えてみてください。
- ユーザーが画面で操作した「元に戻す(Undo)」機能。最後にやった操作から順に消していく必要がありますよね。
- バックグラウンドで順番に処理すべき「メール送信キュー」や「印刷ジョブ」。最初に来た依頼から順に処理しなければ大混乱です。
これらを普通の `List` でやろうとすると、「何番目を削除して、全体を詰めて……」と、バグの温床になる面倒なコードを書く羽目になります。
そこで登場するのが、データの出し入れのルール(構造)をあらかじめ決めたコレクション、`Stack` と `Queue` です。
—
2. 概念を図解! Stack(LIFO)と Queue(FIFO)の正体
難しく考える必要はありません。日常の身近な例でイメージしましょう。
Stack(スタック):LIFO(後入れ先出し)
- イメージ図: お盆の上に積み重ねた「お皿の山」
- 特徴: 最後に積んだ(Pushed)お皿が、一番最初に取られます(Popped)。
- 業務での使い道: 「元に戻す(Undo)」機能、ブラウザの「戻る」ボタン、アルゴリズムの再帰処理の代替など。
[データの出し入れイメージ:Stack]
↓ 追加 (Push) ↑ 取出し (Pop)
+———————–+
ビスケット3 (一番最後に入れた) -> 一番最初に出てくる
+———————–+
ビスケット2
+———————–+
ビスケット1 (一番最初に入れた)
+———————–+
Queue(キュー):FIFO(先入れ先出し)
- イメージ図: 銀行や役所の「順番待ちの窓口の行列」
- 特徴: 先に並んだ(Enqueued)人が、一番先に呼ばれます(Dequeued)。
- 業務での使い道: タスクの順次実行(非同期処理の制御)、ログの順次書き込み、印刷スプーラー。
[データの出し入れイメージ:Queue]
↓ 追加 (Enqueue) ↑ 取出し (Dequeue)
[お客様C] -> [お客様B] -> [お客様A (一番最初に来た)] -> 一番最初に呼ばれる!
—
3. 【実践】Stack(Of T)で実装する「業務システムのUndo(元に戻す)機能」
それでは、実務でそのまま使えるコードで動きを見てみましょう。
画面上のテキスト入力や、データの状態を「1つ前の状態に戻す」シチュエーションを想定したサンプルです。
Imports System.Collections.Generic
Module StackSample
Sub Main()
‘ 操作履歴(テキストの状態)を保存するStackを定義
‘ 型安全な (Of String) を指定するのがプロの作法です
Dim history As New Stack(Of String)()
Console.WriteLine(“=== Stackによる履歴管理(Undo機能)のデモ ===”)
‘ 1. ユーザーがテキストを次々に入力・変更していく
history.Push(“第1稿:お世話になっております。”)
Console.WriteLine(“操作: 「第1稿」を入力しました。”)
history.Push(“第2稿:お世話になっております。修正版です。”)
Console.WriteLine(“操作: 「第2稿」に書き換えました。”)
history.Push(“最終稿:ご確認よろしくお願いいたします。”)
Console.WriteLine(“操作: 「最終稿」に完成させました。”)
Console.WriteLine(vbCrLf & “— ユーザーが「元に戻す (Ctrl + Z)」を押しました —” & vbCrLf)
‘ 2. Undo操作:現在地を捨てて、1つ前の状態に戻る
If history.Count > 0 Then
‘ Pop() は「取り出しつつ、Stackから削除する」メソッドです
Dim discarded As String = history.Pop()
Console.WriteLine($”破棄された状態: {discarded}”)
End If
‘ 3. 残った最新の状態(ひとつ前の状態)を確認する
‘ Peek() は「削除せずに、一番上のデータを見るだけ」の便利なメソッドです
If history.Count > 0 Then
Dim current As String = history.Peek()
Console.WriteLine($”現在のテキスト状態: {current}”)
End If
Console.ReadLine()
End Sub
End Module
💡 ここがエンジニアの急所!
`Pop()` と `Peek()` の使い分けがポイントです。
- `Pop()`: 「取り出して消す」。履歴を戻すときはこれを使います。
- `Peek()`: 「消さずに覗き見る」。現在の最新状態を確認したいだけなのに、うっかり `Pop()` してしまうと履歴が消えてしまうので注意しましょう。
—
4. 【実践】Queue(Of T)で実装する「タスクの順次処理(FIFO)」
続いて、溜まった仕事を古い順に片付けていく「キューイング処理」の実装です。
バックグラウンドでメールを一斉送信するようなシーンをイメージしてください。
Imports System.Collections.Generic
Module QueueSample
Sub Main()
‘ 送信待ちタスクを格納するQueueを定義
Dim mailQueue As New Queue(Of String)()
Console.WriteLine(“=== Queueによるタスク順次処理のデモ ===”)
‘ 1. 処理すべきタスクを次々とキューに登録する (Enqueue)
mailQueue.Enqueue(“user01@example.com 宛の注文確認メール”)
mailQueue.Enqueue(“user02@example.com 宛の出荷通知メール”)
mailQueue.Enqueue(“user03@example.com 宛のサンクスメール”)
Console.WriteLine($”現在の未処理タスク数: {mailQueue.Count}” & vbCrLf)
‘ 2. 順番にタスクを取り出して処理していく (Dequeue)
‘ Queueが空になるまでループを回す
While mailQueue.Count > 0
‘ Dequeue() で「一番古くに追加されたタスク」を取り出す
Dim currentTask As String = mailQueue.Dequeue()
‘ 実際の業務処理をシミュレート
Console.WriteLine($”[処理中] {currentTask} を送信しました。”)
‘ 残りのタスク数を表示
Console.WriteLine($” (残りのタスク: {mailQueue.Count}件)”)
End Sub
Console.WriteLine(vbCrLf & “すべてのタスクが正常に完了しました!”)
Console.ReadLine()
End Sub
End Module
💡 ここがエンジニアの急所!
`Queue` を使った処理で一番恐ろしいのは、「処理中にエラーが発生してループが止まったとき、残りのデータがどうなるか」です。
実務では必ず `Try…Catch` 構文を組み込み、途中で例外が発生しても、どのタスクで失敗したのかをログに記録しつつ、次のキュー処理へ安全に継続(あるいは安全にロールバック)できる堅牢な設計を心がけましょう。
—
5. 陥りやすい罠とパフォーマンスの知見
最後に、中級者へのステップアップとして、パフォーマンスとメモリ管理の視点をいくつか授けておきます。
1. インデックスアクセスはできない
- `Stack` や `Queue` は、`List(Of T)` のように `myQueue(0)` のような指定で途中要素にアクセスすることはできません(設計思想上、意図的に隠蔽されています)。途中の要素を探す必要がある場合は、データの設計自体を見直すか、`List` の利用を検討してください。
2. スレッドセーフティ(マルチスレッド環境での注意)
- 画面のUIスレッドから `Queue` にタスクを追加しつつ、別のバックグラウンドスレッド(`Task` や `BackgroundWorker`)で `Dequeue` するような非同期処理を行う場合、そのままではコレクションが破損し例外(`InvalidOperationException`など)が発生します。
- .NETの標準機能や `SyncLock` などの排他制御、あるいは安全にスレッド間の橋渡しができる `ConcurrentQueue(Of T)` の活用を視野に入れましょう。
—
まとめ
いかがでしたでしょうか?
- 戻る・進むの履歴管理には `Stack(Of T)`(LIFO)
- 順番待ちのタスク処理には `Queue(Of T)`(FIFO)
この2つを適切に使い分けられるようになると、あなたの書くVB.NETコードの品質は、単なる「動くスクリプト」から、堅牢で美しい「プロフェッショナルなアプリケーション」へと一気に格上げされます。
ここをクリアできれば、VB.NETのデータ構造の基礎はもうバッチリです!
日々の開発業務に、ぜひこの強力な武器を取り入れてみてくださいね。それでは、また次の現場でお会いしましょう!
