C# Queueの使い方を初心者向けに解説|基本操作・実装例・Stackとの違いまでわかる

はじめに

C#でデータを順番に処理したいときに便利なのが、Queueです。Queueは「キュー」と読み、データを入れた順番どおりに取り出せるコレクションです。

たとえば、受付の順番待ち、印刷ジョブの待ち行列、タスク処理、ゲーム内イベントの管理など、「先に来たものから順番に処理する」場面でよく使われます。

C#では主にジェネリック型のQueue<T>を使います。intstringだけでなく、自分で作成したクラスのオブジェクトも格納できます。

この記事では、C# Queueの基本的な使い方から、EnqueueDequeuePeekなどの主要メソッド、StackListとの違い、実践的な活用例まで初心者向けにわかりやすく解説します。

1. C#のQueueとは?先入れ先出し(FIFO)を初心者向けに解説

C#のQueueは、複数のデータを順番に管理するためのコレクションです。最大の特徴は、最初に追加したデータが最初に取り出されることです。

この仕組みは「FIFO」と呼ばれます。FIFOは「First In, First Out」の略で、日本語では「先入れ先出し」と訳されます。

1-1. Queueは「最初に入れたデータを最初に取り出す」コレクション

Queueでは、データを後ろに追加し、前から取り出します。

C#
Queue<string> queue = new Queue<string>();

queue.Enqueue("A");
queue.Enqueue("B");
queue.Enqueue("C");

Console.WriteLine(queue.Dequeue()); // A
Console.WriteLine(queue.Dequeue()); // B
Console.WriteLine(queue.Dequeue()); // C

この例では、ABCの順番でデータを追加しています。取り出すときも、追加した順番と同じくABCの順番になります。

つまり、Queueは「順番を守って処理したいデータ」に向いています。

1-2. Queue<T>で扱えるデータ型と基本イメージ

C#でよく使うのはQueue<T>です。Tには、格納したいデータ型を指定します。

C#
Queue<int> numbers = new Queue<int>();
Queue<string> names = new Queue<string>();
Queue<Person> people = new Queue<Person>();

Queue<int>なら整数、Queue<string>なら文字列、Queue<Person>ならPersonクラスのインスタンスを格納できます。

Queue<T>を使うと、格納できる型が明確になるため、型の間違いを防ぎやすくなります。

C#
Queue<int> numbers = new Queue<int>();

numbers.Enqueue(10);
numbers.Enqueue(20);
numbers.Enqueue(30);

この場合、numbersにはint型の値だけを追加できます。文字列を追加しようとするとコンパイルエラーになるため、安全に扱えます。

1-3. Queueを使うと便利な場面

Queueは、以下のような場面で便利です。

順番待ちの処理、タスクの順次実行、プリンターの印刷待ち、ゲームやアプリのイベント処理、幅優先探索などのアルゴリズムでよく使われます。

たとえば、複数の処理を順番に実行したい場合、Queueにタスクを追加しておき、先に追加されたタスクから順番に取り出して処理できます。

C#
Queue<string> tasks = new Queue<string>();

tasks.Enqueue("メール送信");
tasks.Enqueue("ファイル保存");
tasks.Enqueue("ログ出力");

while (tasks.Count > 0)
{
string task = tasks.Dequeue();
Console.WriteLine(task + "を実行しました");
}

このように、Queueを使うと「次に処理すべきデータ」を自然に管理できます。

1-4. Queueを理解するための日常例

Queueは、日常生活の「行列」をイメージすると理解しやすいです。

たとえば、コンビニのレジに人が並んでいる場面を考えてみましょう。最初に並んだ人が最初に会計を済ませ、次に並んだ人がその次に会計します。後から来た人が先に処理されることはありません。

この仕組みが、まさにQueueです。

先頭 ← [Aさん] [Bさん] [Cさん] ← 末尾

最初に取り出されるのはAさんです。その次にBさん、最後にCさんが処理されます。

プログラムでも同じように、先に追加したデータから順番に処理したい場合にQueueを使います。

2. C#でQueueを使う準備

C#でQueue<T>を使うには、必要な名前空間を読み込み、キューを宣言して初期化します。

2-1. Queue<T>を使うために必要な名前空間

Queue<T>を使うには、System.Collections.Generic名前空間が必要です。

C#
using System.Collections.Generic;

.NETのプロジェクトテンプレートによっては、暗黙的なusingによって明示的に書かなくても使える場合があります。ただし、初心者のうちは次のように書いておくとわかりやすいです。

C#
using System;
using System.Collections.Generic;

class Program
{
static void Main()
{
Queue<string> queue = new Queue<string>();
queue.Enqueue("Hello");

Console.WriteLine(queue.Dequeue());
}
}

2-2. Queueの宣言と初期化の基本構文

Queue<T>の基本構文は次のとおりです。

C#
Queue<> 変数名 = new Queue<>();

たとえば、整数を格納するQueueは次のように宣言します。

C#
Queue<int> numbers = new Queue<int>();

文字列を格納するQueueは次のように書きます。

C#
Queue<string> messages = new Queue<string>();

また、最初から値を入れて初期化することもできます。

C#
Queue<string> queue = new Queue<string>(new[] { "A", "B", "C" });

Console.WriteLine(queue.Dequeue()); // A

この場合も、取り出される順番はABCです。

2-3. int・string・独自クラスをQueueに格納する例

Queue<T>にはさまざまな型を格納できます。

intを格納する例です。

C#
Queue<int> numbers = new Queue<int>();

numbers.Enqueue(1);
numbers.Enqueue(2);
numbers.Enqueue(3);

Console.WriteLine(numbers.Dequeue()); // 1

stringを格納する例です。

C#
Queue<string> names = new Queue<string>();

names.Enqueue("田中");
names.Enqueue("佐藤");
names.Enqueue("鈴木");

Console.WriteLine(names.Dequeue()); // 田中

独自クラスを格納することもできます。

C#
class Job
{
public string Name { get; set; }

public Job(string name)
{
Name = name;
}
}

class Program
{
static void Main()
{
Queue<Job> jobs = new Queue<Job>();

jobs.Enqueue(new Job("データ取込"));
jobs.Enqueue(new Job("集計処理"));
jobs.Enqueue(new Job("レポート作成"));

Job job = jobs.Dequeue();
Console.WriteLine(job.Name); // データ取込
}
}

このように、Queue<T>は単純な値だけでなく、業務処理やアプリケーションで使うオブジェクトの管理にも利用できます。

2-4. 非ジェネリックQueueとQueue<T>の違い

C#には、古い形式の非ジェネリックなQueueもあります。これはSystem.Collections名前空間にあります。

C#
using System.Collections;

Queue queue = new Queue();

queue.Enqueue(100);
queue.Enqueue("文字列");

非ジェネリックのQueueは、さまざまな型の値を入れられます。しかし、取り出すときに型変換が必要になり、型の間違いが起きやすくなります。

C#
int number = (int)queue.Dequeue();

一方、Queue<T>は最初に型を指定するため、安全に扱えます。

C#
Queue<int> numbers = new Queue<int>();

numbers.Enqueue(100);

int number = numbers.Dequeue();

基本的には、特別な理由がない限りQueue<T>を使うのがおすすめです。

3. C# Queueの基本操作

C#のQueue<T>には、要素を追加する、取り出す、確認する、削除するなどの基本操作が用意されています。

ここでは、よく使うメソッドを順番に解説します。

3-1. Enqueueで要素を追加する

Enqueueは、Queueの末尾に要素を追加するメソッドです。

C#
Queue<string> queue = new Queue<string>();

queue.Enqueue("A");
queue.Enqueue("B");
queue.Enqueue("C");

このコードでは、ABCの順番でQueueに追加されます。

先頭 ← A, B, C ← 末尾

Enqueueした要素は、追加した順番どおりにDequeueで取り出されます。

C#
Console.WriteLine(queue.Dequeue()); // A

3-2. Dequeueで先頭の要素を取り出して削除する

Dequeueは、Queueの先頭にある要素を取り出し、同時にQueueから削除するメソッドです。

C#
Queue<string> queue = new Queue<string>();

queue.Enqueue("A");
queue.Enqueue("B");

string value = queue.Dequeue();

Console.WriteLine(value); // A
Console.WriteLine(queue.Count); // 1

Dequeueを実行すると、先頭のAが取り出され、Queueの中から削除されます。そのため、残りの要素数は1になります。

Dequeueは「取り出して消す」操作だと覚えるとわかりやすいです。

3-3. Peekで先頭の要素を削除せずに確認する

Peekは、Queueの先頭にある要素を確認するメソッドです。Dequeueと違い、要素は削除されません。

C#
Queue<string> queue = new Queue<string>();

queue.Enqueue("A");
queue.Enqueue("B");

string value = queue.Peek();

Console.WriteLine(value); // A
Console.WriteLine(queue.Count); // 2

Peekを使うと、次に取り出される要素を事前に確認できます。

要素を削除せずに中身を見たい場合は、DequeueではなくPeekを使いましょう。

3-4. CountでQueue内の要素数を確認する

Countプロパティを使うと、Queueに入っている要素数を確認できます。

C#
Queue<int> numbers = new Queue<int>();

numbers.Enqueue(10);
numbers.Enqueue(20);

Console.WriteLine(numbers.Count); // 2

Countは、Queueが空かどうかを確認するときにもよく使います。

C#
if (numbers.Count > 0)
{
int value = numbers.Dequeue();
Console.WriteLine(value);
}

DequeuePeekは、Queueが空の状態で実行するとエラーになります。そのため、処理前にCountを確認するのが基本です。

3-5. ClearでQueueの中身をすべて削除する

Clearは、Queue内のすべての要素を削除するメソッドです。

C#
Queue<string> queue = new Queue<string>();

queue.Enqueue("A");
queue.Enqueue("B");
queue.Enqueue("C");

queue.Clear();

Console.WriteLine(queue.Count); // 0

一度にすべてのデータを消したい場合に使います。

たとえば、処理待ちのタスクをリセットしたい場合などに便利です。

3-6. Containsで特定の要素が含まれているか確認する

Containsを使うと、Queueの中に特定の要素が含まれているか確認できます。

C#
Queue<string> queue = new Queue<string>();

queue.Enqueue("A");
queue.Enqueue("B");
queue.Enqueue("C");

Console.WriteLine(queue.Contains("B")); // True
Console.WriteLine(queue.Contains("D")); // False

Containsは、指定した値がQueue内に存在すればtrue、存在しなければfalseを返します。

ただし、Queueは先頭から順番に処理するためのコレクションです。頻繁に検索したい場合は、ListDictionaryなど別のコレクションのほうが適していることもあります。

4. C# Queueの実装例

ここでは、C# Queueの使い方を具体的なコードで確認していきます。

基本操作を組み合わせることで、順番に処理するプログラムを簡単に作れます。

4-1. 数値を順番に処理する基本サンプル

まずは、数値をQueueに追加し、追加した順番に取り出す例です。

C#
using System;
using System.Collections.Generic;

class Program
{
static void Main()
{
Queue<int> numbers = new Queue<int>();

numbers.Enqueue(100);
numbers.Enqueue(200);
numbers.Enqueue(300);

Console.WriteLine(numbers.Dequeue()); // 100
Console.WriteLine(numbers.Dequeue()); // 200
Console.WriteLine(numbers.Dequeue()); // 300
}
}

100200300の順番で追加しているため、取り出す順番も同じです。

このように、Queueは「登録順に処理したいデータ」に向いています。

4-2. 文字列データを順番に取り出すサンプル

次に、文字列をQueueで管理する例です。

C#
using System;
using System.Collections.Generic;

class Program
{
static void Main()
{
Queue<string> customers = new Queue<string>();

customers.Enqueue("田中さん");
customers.Enqueue("佐藤さん");
customers.Enqueue("鈴木さん");

Console.WriteLine(customers.Dequeue() + "を案内しました");
Console.WriteLine(customers.Dequeue() + "を案内しました");
Console.WriteLine(customers.Dequeue() + "を案内しました");
}
}

実行結果は次のようになります。

田中さんを案内しました
佐藤さんを案内しました
鈴木さんを案内しました

受付や予約、順番待ちのような処理は、Queueの考え方と相性がよいです。

4-3. while文とCountを使ってQueueを空になるまで処理する

Queueでは、while文とCountを組み合わせて、空になるまで処理する書き方がよく使われます。

C#
using System;
using System.Collections.Generic;

class Program
{
static void Main()
{
Queue<string> tasks = new Queue<string>();

tasks.Enqueue("データ読み込み");
tasks.Enqueue("データ加工");
tasks.Enqueue("データ保存");

while (tasks.Count > 0)
{
string task = tasks.Dequeue();
Console.WriteLine(task + "を実行しました");
}

Console.WriteLine("すべてのタスクが完了しました");
}
}

実行結果です。

データ読み込みを実行しました
データ加工を実行しました
データ保存を実行しました
すべてのタスクが完了しました

Count > 0の間だけDequeueすることで、空のQueueに対してDequeueしてしまうエラーを防げます。

4-4. foreachでQueueの中身を確認する

foreachを使うと、Queueの中身を順番に確認できます。

C#
Queue<string> queue = new Queue<string>();

queue.Enqueue("A");
queue.Enqueue("B");
queue.Enqueue("C");

foreach (string item in queue)
{
Console.WriteLine(item);
}

実行結果です。

A
B
C

foreachでは、要素は削除されません。中身を一覧表示したいだけなら、foreachが便利です。

C#
Console.WriteLine(queue.Count); // 3

foreachで表示したあとも、Queueの要素数は変わりません。

4-5. ToArrayでQueueを配列に変換する

ToArrayを使うと、Queueの中身を配列に変換できます。

C#
Queue<string> queue = new Queue<string>();

queue.Enqueue("A");
queue.Enqueue("B");
queue.Enqueue("C");

string[] array = queue.ToArray();

foreach (string item in array)
{
Console.WriteLine(item);
}

配列に変換しても、Queue自体の中身は削除されません。

C#
Console.WriteLine(queue.Count); // 3

Queueの内容を別の形式で扱いたい場合や、配列として他のメソッドに渡したい場合にToArrayを使います。

5. Queueを使うときの注意点とエラー対策

Queueは便利ですが、使い方を間違えると例外が発生することがあります。

特に注意したいのが、空のQueueに対してDequeuePeekを実行するケースです。

5-1. 空のQueueでDequeueすると発生するエラー

空のQueueに対してDequeueを実行すると、例外が発生します。

C#
Queue<int> numbers = new Queue<int>();

int value = numbers.Dequeue(); // エラー

Queueの中に要素がないため、取り出すものがありません。この場合、InvalidOperationExceptionが発生します。

そのため、Dequeueを使う前には、Queueに要素が入っているか確認する必要があります。

5-2. 空のQueueでPeekすると発生するエラー

Peekも同じく、空のQueueに対して実行すると例外が発生します。

C#
Queue<string> queue = new Queue<string>();

string value = queue.Peek(); // エラー

Peekは要素を削除しませんが、先頭の要素を確認するメソッドです。空のQueueには先頭の要素が存在しないため、エラーになります。

5-3. Countで空かどうか確認してから処理する方法

もっとも基本的なエラー対策は、Countで要素数を確認してから処理することです。

C#
Queue<string> queue = new Queue<string>();

if (queue.Count > 0)
{
string value = queue.Dequeue();
Console.WriteLine(value);
}
else
{
Console.WriteLine("Queueは空です");
}

while文でQueueを空になるまで処理する場合も、Countを使います。

C#
while (queue.Count > 0)
{
string value = queue.Dequeue();
Console.WriteLine(value);
}

この書き方なら、空のQueueに対してDequeueすることを防げます。

5-4. TryDequeue・TryPeekを使った安全な取り出し方

TryDequeueを使うと、Queueが空でも例外を発生させずに安全に取り出しを試せます。

C#
Queue<int> numbers = new Queue<int>();

if (numbers.TryDequeue(out int value))
{
Console.WriteLine(value);
}
else
{
Console.WriteLine("取り出せる要素がありません");
}

TryDequeueは、取り出しに成功した場合はtrueを返し、取り出した値をout引数に入れます。Queueが空の場合はfalseを返します。

TryPeekを使うと、削除せずに先頭要素の取得を試せます。

C#
Queue<string> queue = new Queue<string>();

if (queue.TryPeek(out string? value))
{
Console.WriteLine("先頭の要素: " + value);
}
else
{
Console.WriteLine("Queueは空です");
}

例外を避けながら安全に処理したい場合は、TryDequeueTryPeekを使うと便利です。

ただし、古い.NET環境では使えない場合があります。その場合は、Count > 0で確認してからDequeuePeekを実行しましょう。

5-5. Queueはインデックス指定で要素を取り出せない

Queue<T>は、List<T>や配列のようにインデックスを指定して要素を取り出すことはできません。

C#
Queue<string> queue = new Queue<string>();

queue.Enqueue("A");
queue.Enqueue("B");

// queue[0] のような書き方はできない

Queueは、先頭から順番に取り出すためのコレクションです。任意の位置の要素にアクセスしたい場合は、List<T>や配列を使うほうが適しています。

どうしてもQueueの中身を配列として確認したい場合は、ToArrayを使います。

C#
string[] array = queue.ToArray();

Console.WriteLine(array[0]);

ただし、これはQueueを配列に変換してからアクセスしているだけです。Queue本来の使い方としては、DequeuePeekで先頭から扱うのが基本です。

6. QueueとStackの違い

Queueとよく比較されるコレクションにStackがあります。

どちらもデータを追加して取り出すコレクションですが、取り出す順番が大きく異なります。

6-1. QueueはFIFO、StackはLIFO

QueueはFIFOです。最初に入れたデータを最初に取り出します。

Queue: A → B → C の順に入れると、A → B → C の順に取り出す

一方、StackはLIFOです。LIFOは「Last In, First Out」の略で、日本語では「後入れ先出し」と呼ばれます。

Stack: A → B → C の順に入れると、C → B → A の順に取り出す

Stackは、積み重ねた本や皿をイメージするとわかりやすいです。最後に置いたものを最初に取ります。

6-2. Enqueue・DequeueとPush・Popの違い

Queueでは、要素の追加にEnqueue、取り出しにDequeueを使います。

C#
Queue<string> queue = new Queue<string>();

queue.Enqueue("A");
queue.Enqueue("B");

Console.WriteLine(queue.Dequeue()); // A

Stackでは、要素の追加にPush、取り出しにPopを使います。

C#
Stack<string> stack = new Stack<string>();

stack.Push("A");
stack.Push("B");

Console.WriteLine(stack.Pop()); // B

同じようにデータを追加して取り出しているように見えますが、取り出される順番が違います。

6-3. QueueとStackの使い分け

Queueは、先に追加したものから順番に処理したい場合に使います。

一方、Stackは、最後に追加したものから処理したい場合に使います。

たとえば、受付の順番待ちにはQueueが向いています。先に来た人を先に案内する必要があるからです。

一方、ブラウザの戻る履歴や、Undo機能のように「直前の操作から戻したい」場合はStackが向いています。

6-4. Queueが向いている処理の例

Queueが向いているのは、順番どおりに処理する必要がある場面です。

C#
Queue<string> printJobs = new Queue<string>();

printJobs.Enqueue("資料A");
printJobs.Enqueue("資料B");
printJobs.Enqueue("資料C");

while (printJobs.Count > 0)
{
Console.WriteLine(printJobs.Dequeue() + "を印刷しました");
}

この例では、追加された順番どおりに印刷されます。

Queueは、次のような処理に向いています。

受付の順番待ち、タスクキュー、印刷ジョブ、メッセージ処理、イベント処理、幅優先探索などです。

6-5. Stackが向いている処理の例

Stackが向いているのは、最後に追加したものから処理したい場面です。

C#
Stack<string> history = new Stack<string>();

history.Push("ページA");
history.Push("ページB");
history.Push("ページC");

Console.WriteLine(history.Pop()); // ページC
Console.WriteLine(history.Pop()); // ページB

この例では、最後に追加したページCが最初に取り出されます。

Stackは、ブラウザ履歴、Undo処理、再帰処理の管理、深さ優先探索などに使われます。

7. QueueとList・配列の違い

C#には、Queue以外にもListや配列があります。どれも複数のデータを扱えますが、目的や使い方が異なります。

7-1. QueueとListの主な違い

Queue<T>は、先頭から順番に処理するためのコレクションです。

一方、List<T>は、要素の追加、削除、検索、並び替え、インデックスアクセスなど、柔軟に扱えるコレクションです。

C#
List<string> list = new List<string>();

list.Add("A");
list.Add("B");
list.Add("C");

Console.WriteLine(list[1]); // B

List<T>では、list[1]のようにインデックスで要素にアクセスできます。

Queueではこのようなインデックスアクセスはできません。

C#
Queue<string> queue = new Queue<string>();

queue.Enqueue("A");
queue.Enqueue("B");
queue.Enqueue("C");

// queue[1] は使えない

順番に処理するならQueue、自由にアクセスしたいならListと考えるとわかりやすいです。

7-2. Queueと配列の主な違い

配列は、固定長のデータ構造です。

C#
int[] numbers = new int[3];

numbers[0] = 10;
numbers[1] = 20;
numbers[2] = 30;

配列はインデックスアクセスが得意ですが、サイズを後から自由に変えることはできません。

一方、Queueは要素をEnqueueで追加し、Dequeueで取り出せます。要素数は自動的に管理されるため、順番待ちのデータを扱いやすいです。

C#
Queue<int> queue = new Queue<int>();

queue.Enqueue(10);
queue.Enqueue(20);

Console.WriteLine(queue.Dequeue()); // 10

配列は「決まった数のデータを管理する」のに向いており、Queueは「順番に追加・処理されるデータを管理する」のに向いています。

7-3. 順番に処理するならQueueが適している理由

順番に処理するデータでは、「次に処理する要素」を簡単に取り出せることが重要です。

Queueでは、Dequeueを呼ぶだけで、次に処理すべき要素を取り出せます。

C#
string nextTask = tasks.Dequeue();

Listでも先頭の要素を取り出すことはできますが、先頭要素を削除する処理を書く必要があります。

C#
string nextTask = list[0];
list.RemoveAt(0);

単純に「先に追加したものから順番に処理する」目的なら、Queueを使ったほうがコードの意図がわかりやすくなります。

7-4. 任意の位置にアクセスしたい場合はListや配列を使う

Queueは、任意の位置の要素を直接取り出す用途には向いていません。

たとえば、3番目の要素を直接取得したい場合は、Listや配列を使うほうが自然です。

C#
List<string> names = new List<string>();

names.Add("田中");
names.Add("佐藤");
names.Add("鈴木");

Console.WriteLine(names[2]); // 鈴木

配列でも同じようにインデックスでアクセスできます。

C#
string[] names = { "田中", "佐藤", "鈴木" };

Console.WriteLine(names[2]); // 鈴木

Queueは「先頭から順番に処理する」ためのものです。途中の要素を頻繁に参照したり、並び替えたりする場合は、Listや配列を選びましょう。

8. Queueの実践的な活用例

ここからは、C# Queueを実際の開発でどのように使えるかを見ていきます。

Queueは、単なるサンプルコードだけでなく、実務でもよく使われる考え方です。

8-1. タスク処理の待ち行列を作る

複数のタスクを順番に処理したい場合、Queueを使うと簡単です。

C#
Queue<Action> taskQueue = new Queue<Action>();

taskQueue.Enqueue(() => Console.WriteLine("タスク1を実行"));
taskQueue.Enqueue(() => Console.WriteLine("タスク2を実行"));
taskQueue.Enqueue(() => Console.WriteLine("タスク3を実行"));

while (taskQueue.Count > 0)
{
Action task = taskQueue.Dequeue();
task();
}

実行結果です。

タスク1を実行
タスク2を実行
タスク3を実行

このように、処理そのものをQueueに入れて順番に実行することもできます。

8-2. プリンターの印刷待ちをQueueで表現する

プリンターの印刷待ちは、Queueの代表的な例です。

C#
Queue<string> printQueue = new Queue<string>();

printQueue.Enqueue("見積書.pdf");
printQueue.Enqueue("請求書.pdf");
printQueue.Enqueue("報告書.pdf");

while (printQueue.Count > 0)
{
string document = printQueue.Dequeue();
Console.WriteLine(document + "を印刷中...");
}

実行結果です。

見積書.pdfを印刷中...
請求書.pdfを印刷中...
報告書.pdfを印刷中...

先に印刷依頼されたファイルから順番に処理されるため、QueueのFIFOの仕組みがぴったり合います。

8-3. ゲームやアプリのイベント処理に使う

ゲームやアプリでは、発生したイベントを順番に処理したいことがあります。

C#
Queue<string> eventQueue = new Queue<string>();

eventQueue.Enqueue("プレイヤーが移動");
eventQueue.Enqueue("敵が出現");
eventQueue.Enqueue("アイテムを取得");

while (eventQueue.Count > 0)
{
string gameEvent = eventQueue.Dequeue();
Console.WriteLine("イベント処理: " + gameEvent);
}

実行結果です。

イベント処理: プレイヤーが移動
イベント処理: 敵が出現
イベント処理: アイテムを取得

イベントをQueueに入れておけば、発生した順番を保ったまま処理できます。

8-4. BFSなどのアルゴリズムでQueueを使う

Queueは、幅優先探索、つまりBFSでもよく使われます。

BFSでは、ある地点から近い順に探索していきます。この「先に見つけたものから順に調べる」処理にQueueが適しています。

簡単な例を見てみましょう。

C#
using System;
using System.Collections.Generic;

class Program
{
static void Main()
{
Dictionary<string, List<string>> graph = new Dictionary<string, List<string>>
{
{ "A", new List<string> { "B", "C" } },
{ "B", new List<string> { "D", "E" } },
{ "C", new List<string> { "F" } },
{ "D", new List<string>() },
{ "E", new List<string>() },
{ "F", new List<string>() }
};

Queue<string> queue = new Queue<string>();
HashSet<string> visited = new HashSet<string>();

queue.Enqueue("A");
visited.Add("A");

while (queue.Count > 0)
{
string current = queue.Dequeue();
Console.WriteLine(current);

foreach (string next in graph[current])
{
if (!visited.Contains(next))
{
queue.Enqueue(next);
visited.Add(next);
}
}
}
}
}

実行結果です。

A
B
C
D
E
F

Queueを使うことで、近いノードから順番に探索できます。

8-5. 非同期処理やマルチスレッドではConcurrentQueueを検討する

通常のQueue<T>は、複数のスレッドから同時に操作することを前提にしていません。

複数のスレッドから安全にキューを操作したい場合は、ConcurrentQueue<T>を検討します。

ConcurrentQueue<T>は、System.Collections.Concurrent名前空間にあります。

C#
using System;
using System.Collections.Concurrent;

class Program
{
static void Main()
{
ConcurrentQueue<string> queue = new ConcurrentQueue<string>();

queue.Enqueue("タスク1");
queue.Enqueue("タスク2");

if (queue.TryDequeue(out string? task))
{
Console.WriteLine(task);
}
}
}

ConcurrentQueue<T>では、TryDequeueTryPeekを使って安全に要素を取得します。

マルチスレッドや並列処理でQueueを使いたい場合、通常のQueue<T>ではなくConcurrentQueue<T>を使うことで、安全性を高められます。

9. C# Queueに関するよくある質問

ここでは、C# Queueを使うときによくある疑問をまとめます。

9-1. Queueの中身を確認するだけならどのメソッドを使う?

先頭の要素だけを確認したい場合は、Peekを使います。

C#
Queue<string> queue = new Queue<string>();

queue.Enqueue("A");
queue.Enqueue("B");

Console.WriteLine(queue.Peek()); // A

Peekは要素を削除しません。

Queue全体を一覧表示したい場合は、foreachを使います。

C#
foreach (string item in queue)
{
Console.WriteLine(item);
}

9-2. Queueの要素を削除せずに一覧表示できる?

できます。foreachを使えば、Queueの要素を削除せずに一覧表示できます。

C#
Queue<int> numbers = new Queue<int>();

numbers.Enqueue(10);
numbers.Enqueue(20);
numbers.Enqueue(30);

foreach (int number in numbers)
{
Console.WriteLine(number);
}

Console.WriteLine(numbers.Count); // 3

foreachで確認しても、Queueの中身はそのまま残ります。

また、ToArrayを使って配列に変換して確認することもできます。

C#
int[] array = numbers.ToArray();

9-3. Queueはnullを格納できる?

参照型のQueueであれば、nullを格納できます。

C#
Queue<string?> queue = new Queue<string?>();

queue.Enqueue("A");
queue.Enqueue(null);
queue.Enqueue("C");

ただし、nullを取り出したときにエラーが起きないよう、扱いには注意が必要です。

C#
string? value = queue.Dequeue();

if (value != null)
{
Console.WriteLine(value.Length);
}
else
{
Console.WriteLine("nullです");
}

Nullable参照型を有効にしている場合は、Queue<string?>のように?を付けると、nullを扱う意図が明確になります。

9-4. Queueの順番を途中で並び替えられる?

Queue<T>には、直接並び替えるためのメソッドはありません。

Queueは、先に入れたものを先に取り出すためのコレクションです。そのため、途中で自由に並び替える用途には向いていません。

並び替えたい場合は、一度Listに変換する方法があります。

C#
Queue<int> queue = new Queue<int>();

queue.Enqueue(30);
queue.Enqueue(10);
queue.Enqueue(20);

List<int> list = queue.ToList();
list.Sort();

Queue<int> sortedQueue = new Queue<int>(list);

このようにすれば、並び替えた結果を新しいQueueとして作れます。

ただし、頻繁に並び替える必要があるなら、最初からList<T>などを使うほうが適しています。

9-5. QueueとConcurrentQueueはどう違う?

Queue<T>は、通常の単一スレッド処理で使う基本的なキューです。

一方、ConcurrentQueue<T>は、複数のスレッドから同時に操作されることを想定したスレッドセーフなキューです。

通常の処理ではQueue<T>で十分です。

C#
Queue<string> queue = new Queue<string>();

複数スレッドや並列処理で使う場合は、ConcurrentQueue<T>を検討します。

C#
ConcurrentQueue<string> queue = new ConcurrentQueue<string>();

ConcurrentQueue<T>では、取り出しにTryDequeueを使います。

C#
if (queue.TryDequeue(out string? value))
{
Console.WriteLine(value);
}

アプリケーションの構成によって使い分けることが大切です。

まとめ

C#のQueueは、先に追加したデータを先に取り出すFIFO方式のコレクションです。順番待ちやタスク処理、印刷ジョブ、イベント処理、BFSなど、順序を守って処理したい場面で活躍します。

基本的には、型安全に扱えるQueue<T>を使います。要素を追加するにはEnqueue、先頭の要素を取り出して削除するにはDequeue、削除せずに確認するにはPeekを使います。

C#
Queue<string> queue = new Queue<string>();

queue.Enqueue("A");
queue.Enqueue("B");

Console.WriteLine(queue.Peek()); // A
Console.WriteLine(queue.Dequeue()); // A
Console.WriteLine(queue.Dequeue()); // B

空のQueueでDequeuePeekを実行すると例外が発生するため、Countで確認するか、TryDequeueTryPeekを使うと安全です。

QueueはFIFO、StackはLIFOという違いがあります。また、任意の位置にアクセスしたい場合はListや配列、複数スレッドで安全に扱いたい場合はConcurrentQueue<T>を使うとよいでしょう。

C# Queueを理解すると、順番に処理するプログラムをシンプルに書けるようになります。まずはEnqueueDequeuePeekCountの基本操作から使い方を身につけていきましょう。