C# Queueの使い方を基礎から解説|Queueの追加・取り出し・確認をサンプルコードで理解

はじめに

C#で「登録された順番どおりに処理したい」「先に来たデータから順番に取り出したい」という場面では、Queue<T>が便利です。Queueは日本語で「待ち行列」と訳されることが多く、レジ待ちやチケット待ちのように、先に並んだ人から順番に処理するイメージで理解できます。

C#のQueue<T>は、要素を末尾に追加し、先頭から取り出すコレクションです。代表的な操作には、要素を追加するEnqueue、要素を取り出すDequeue、先頭要素を確認するPeekがあります。Microsoftの公式ドキュメントでも、Queue<T>はオブジェクトの先入れ先出し、つまりFIFOコレクションとして説明されています。Microsoft Learn

この記事では、C#のQueueの基本から、Queue<T>の追加・取り出し・確認・削除・安全な扱い方まで、サンプルコードを使ってわかりやすく解説します。

1. C#のQueueとは?基本概念と使いどころ

C#のQueueは、データを順番に管理するためのコレクションです。特に、処理待ちのタスク、メッセージ、ユーザーの待ち行列、ログの一時保存など、「入ってきた順番を保ったまま処理したい」場合に向いています。

C#では主にSystem.Collections.Generic.Queue<T>を使います。Tにはintstring、独自クラスなど、Queueに格納したい型を指定します。

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

このように宣言すると、文字列だけを格納できるQueueを作成できます。

1-1. Queueは「先に入れたものを先に取り出す」FIFO構造

Queueの最大の特徴は、FIFOという仕組みです。FIFOは「First In, First Out」の略で、「最初に入れたものを最初に取り出す」という意味です。

たとえば、次の順番でQueueにデータを追加したとします。

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

この場合、取り出される順番は次のようになります。

A → B → C

最初に追加したAが最初に取り出され、その次にB、最後にCが取り出されます。

これは、レジに並んだお客さんを順番に対応するようなイメージです。後から来た人が先に処理されるのではなく、先に並んだ人から順番に処理されます。

1-2. Stack・List・配列との違い

C#にはQueue以外にも、Stack<T>List<T>、配列など、複数のデータ構造があります。それぞれ特徴が異なるため、目的に合わせて使い分けることが大切です。

Queue<T>は、先に入れた要素を先に取り出すFIFO構造です。一方、Stack<T>は、後に入れた要素を先に取り出すLIFO構造です。Microsoftのドキュメントでも、同じ順序で情報にアクセスしたい場合はQueue<T>、逆順にアクセスしたい場合はStack<T>を使うと説明されています。Microsoft Learn

C#
// Queue: 先に入れたものから取り出す
Queue<string> queue = new Queue<string>();
queue.Enqueue("A");
queue.Enqueue("B");
Console.WriteLine(queue.Dequeue()); // A

// Stack: 後に入れたものから取り出す
Stack<string> stack = new Stack<string>();
stack.Push("A");
stack.Push("B");
Console.WriteLine(stack.Pop()); // B

List<T>や配列は、インデックスを指定して要素にアクセスできます。

C#
List<string> list = new List<string>();
list.Add("A");
list.Add("B");

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

一方、Queue<T>では次のようなインデックスアクセスはできません。

C#
// Queue<T>ではこのようなアクセスはできない
// Console.WriteLine(queue[0]);

つまり、Queueは「何番目の要素を取り出すか」を自由に選ぶためのコレクションではなく、「先頭から順番に処理する」ためのコレクションです。

1-3. C#でQueueを使う主な場面

C#でQueueを使う場面としては、次のような例があります。

処理待ちのタスクを順番に実行する場合、Queueが便利です。たとえば、印刷ジョブ、メール送信処理、ファイル変換処理など、登録された順番に処理したいタスクをQueueで管理できます。

また、ゲーム開発でもQueueはよく使われます。キャラクターの行動予定、イベントメッセージ、入力履歴などを順番に処理する場合に役立ちます。

Webアプリケーションや業務システムでは、問い合わせ、予約、注文、通知などを順番に処理する場面でQueueの考え方が使えます。

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

tasks.Enqueue("メール送信");
tasks.Enqueue("PDF作成");
tasks.Enqueue("通知送信");

while (tasks.Count > 0)
{
string task = tasks.Dequeue();
Console.WriteLine($"{task}を処理します");
}

このコードでは、追加された順番にタスクが処理されます。

1-4. Queue<T>を使うメリット

Queue<T>を使うメリットは、データを追加した順番どおりに安全に処理しやすいことです。

Enqueueで追加し、Dequeueで取り出すという操作が明確なので、コードの意図が読みやすくなります。

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

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

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

このコードを見るだけで、「待っているユーザーを順番に処理している」と理解しやすくなります。

また、Queue<T>はジェネリックコレクションなので、格納する型を指定できます。Queue<string>なら文字列、Queue<int>なら整数、Queue<Order>なら注文オブジェクトのように、型を明確にできます。

2. C#でQueue<T>を使うための基本準備

C#でQueue<T>を使うには、まず必要な名前空間を読み込み、Queueを宣言して初期化します。基本的な準備はとても簡単です。

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

Queue<T>を使うには、次の名前空間を指定します。

C#
using System.Collections.Generic;

Queue<T>System.Collections.Generic名前空間に含まれるジェネリックコレクションです。Microsoftの公式ドキュメントでも、Queue<T>の名前空間はSystem.Collections.Genericとされています。Microsoft Learn

プログラム全体の例は次のようになります。

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

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

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

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

2-2. Queue<T>の宣言と初期化

Queue<T>の基本的な宣言は次の形式です。

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

文字列を格納するQueueであれば、次のように書きます。

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

整数を格納するQueueであれば、次のように書きます。

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

C#のバージョンによっては、右辺の型を省略して次のように書くこともできます。

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

ただし、初心者のうちは型が明確に見えるように、次の書き方から覚えるとよいでしょう。

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

2-3. int・string・クラスなど型別のQueue作成例

Queue<T>Tには、さまざまな型を指定できます。

まず、int型のQueueです。

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

scores.Enqueue(80);
scores.Enqueue(90);
scores.Enqueue(75);

Console.WriteLine(scores.Dequeue()); // 80

次に、string型のQueueです。

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

messages.Enqueue("おはよう");
messages.Enqueue("こんにちは");
messages.Enqueue("こんばんは");

Console.WriteLine(messages.Dequeue()); // おはよう

独自クラスをQueueに入れることもできます。

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

class Customer
{
public string Name { get; set; }
public int Number { get; set; }

public Customer(string name, int number)
{
Name = name;
Number = number;
}
}

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

customers.Enqueue(new Customer("田中", 1));
customers.Enqueue(new Customer("佐藤", 2));

Customer customer = customers.Dequeue();

Console.WriteLine($"{customer.Number}: {customer.Name}");
}
}

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

1: 田中

このように、Queueは単純な値だけでなく、クラスオブジェクトの管理にも使えます。

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

C#には、Queue<T>とは別に、非ジェネリックのQueueもあります。

非ジェネリックのQueueは、System.Collections名前空間に含まれます。

C#
using System.Collections;

Queue queue = new Queue();

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

非ジェネリックのQueueは、さまざまな型の値を同じQueueに入れられます。しかし、取り出すときにはobjectとして扱われるため、型変換が必要になることがあります。

C#
Queue queue = new Queue();

queue.Enqueue("Hello");

string text = (string)queue.Dequeue();
Console.WriteLine(text);

一方、Queue<T>では最初から型を指定するため、意図しない型の値を追加しにくくなります。

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

queue.Enqueue("Hello");
// queue.Enqueue(100); // コンパイルエラー

通常のC#開発では、型安全で扱いやすいQueue<T>を使うのがおすすめです。

3. Queue<T>に要素を追加する方法|Enqueue

Queue<T>に要素を追加するときは、Enqueueメソッドを使います。Queueを使ううえで最も基本的な操作です。

3-1. Enqueueメソッドの基本構文

Enqueueメソッドの基本構文は次のとおりです。

C#
queue.Enqueue(追加する値);

たとえば、文字列を追加する場合は次のように書きます。

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

queue.Enqueue("A");

Enqueueは、Queueの末尾に要素を追加します。公式ドキュメントでも、EnqueueQueue<T>の末尾にオブジェクトを追加するメソッドとして説明されています。Microsoft Learn

3-2. 文字列をQueueに追加するサンプルコード

次のコードでは、文字列をQueueに追加しています。

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

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

queue.Enqueue("りんご");
queue.Enqueue("みかん");
queue.Enqueue("バナナ");

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

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

りんご
みかん
バナナ

Enqueueした順番どおりにQueueの中身が並んでいることがわかります。

3-3. 数値をQueueに追加するサンプルコード

数値をQueueに追加する場合も、書き方は同じです。

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

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

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

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

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

10
20
30

Queue<int>には整数だけを追加できます。文字列など別の型を追加しようとすると、コンパイルエラーになります。

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

numbers.Enqueue(10);
// numbers.Enqueue("20"); // コンパイルエラー

このように、Queue<T>は型を指定できるため、誤ったデータを入れてしまうミスを防ぎやすくなります。

3-4. 複数の要素を順番に追加した場合の並び順

Queueに複数の要素を追加すると、追加した順番がそのまま取り出し順になります。

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

queue.Enqueue("1番目");
queue.Enqueue("2番目");
queue.Enqueue("3番目");

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

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

1番目
2番目
3番目

最初に追加した1番目が最初に取り出され、次に2番目、最後に3番目が取り出されます。

Queueを使うときは、「追加は末尾」「取り出しは先頭」という動きを意識すると理解しやすくなります。

4. Queue<T>から要素を取り出す方法|Dequeue

Queue<T>から要素を取り出すには、Dequeueメソッドを使います。Dequeueは先頭の要素を取り出し、その要素をQueueから削除します。

4-1. Dequeueメソッドの基本構文

Dequeueメソッドの基本構文は次のとおりです。

C#
T value = queue.Dequeue();

Tには、Queueに指定した型が入ります。

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

queue.Enqueue("A");

string value = queue.Dequeue();

公式ドキュメントでも、DequeueQueue<T>の先頭にあるオブジェクトを削除して返すメソッドとして説明されています。Microsoft Learn

4-2. 先頭の要素を取り出すサンプルコード

次のコードでは、Queueから先頭の要素を取り出しています。

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

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

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

string first = queue.Dequeue();

Console.WriteLine(first);
}
}

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

A

ABCの順番で追加したため、最初に取り出されるのはAです。

4-3. DequeueするとQueue内の要素が削除される仕組み

Dequeueは、先頭の要素を返すだけではありません。取り出した要素はQueueから削除されます。

次のコードで確認してみましょう。

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

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

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

Console.WriteLine($"Count: {queue.Count}");

string item = queue.Dequeue();

Console.WriteLine($"取り出した値: {item}");
Console.WriteLine($"Count: {queue.Count}");

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

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

Count: 3
取り出した値: A
Count: 2
B
C

DequeueAを取り出したあと、Queueの要素数が3から2に減っています。Queueの中にはBCだけが残ります。

4-4. 空のQueueでDequeueしたときの例外に注意

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

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

string item = queue.Dequeue(); // 例外が発生する

このようなコードを実行すると、InvalidOperationExceptionが発生します。Dequeueを使う前には、Queueに要素が入っているか確認することが重要です。

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

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

Queueから安全に取り出したい場合は、Countを確認する方法のほかに、TryDequeueを使う方法もあります。TryDequeueについては後の章で詳しく解説します。

5. Queue<T>の先頭要素を確認する方法|Peek

Queueの先頭要素を確認したいけれど、削除はしたくない場合はPeekメソッドを使います。

5-1. Peekメソッドの基本構文

Peekメソッドの基本構文は次のとおりです。

C#
T value = queue.Peek();

Peekは、Queueの先頭にある要素を返します。ただし、Dequeueとは異なり、要素をQueueから削除しません。公式ドキュメントでも、Peekは先頭にあるオブジェクトを削除せずに返すメソッドとして説明されています。Microsoft Learn

5-2. Peekで先頭要素を確認するサンプルコード

次のコードでは、PeekでQueueの先頭要素を確認しています。

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

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

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

string first = queue.Peek();

Console.WriteLine($"先頭: {first}");
Console.WriteLine($"Count: {queue.Count}");
}
}

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

先頭: A
Count: 3

PeekAを確認しても、Queueの要素数は3のままです。つまり、Peekは中身を確認するだけで、削除は行いません。

5-3. PeekとDequeueの違い

PeekDequeueは、どちらもQueueの先頭要素を扱います。しかし、要素を削除するかどうかが違います。

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

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

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

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

Peekは先頭の値を確認するだけなので、Countは変わりません。一方、Dequeueは先頭の値を取り出して削除するため、Countが減ります。

使い分けの目安は次のとおりです。

先頭を確認したいだけ → Peek
先頭を取り出して処理済みにしたい → Dequeue

5-4. 空のQueueでPeekしたときの注意点

空のQueueに対してPeekを実行した場合も、Dequeueと同じように例外が発生します。

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

string item = queue.Peek(); // 例外が発生する

空の可能性があるQueueでPeekを使う場合は、事前にCountを確認しましょう。

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

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

また、例外を避けて安全に確認したい場合は、TryPeekを使う方法もあります。

6. Queue<T>の要素数・中身を確認する方法

Queueを使っていると、現在の要素数や中身を確認したい場面があります。Queue<T>では、CountforeachContainsToArrayなどを使って状態を確認できます。

6-1. Countプロパティで要素数を取得する

Queueの要素数を確認するには、Countプロパティを使います。

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

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

Console.WriteLine(queue.Count);

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

2

Countは、Queueに現在いくつの要素が入っているかを返します。公式ドキュメントでも、CountQueue<T>に含まれる要素の数を取得するプロパティとして説明されています。Microsoft Learn

Dequeueと組み合わせると、空になるまで処理できます。

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

6-2. foreachでQueueの中身を順番に表示する

Queueの中身を確認したい場合は、foreachを使えます。

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でQueueを列挙しても、Queueの中身は削除されません。

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

中身を確認するだけならforeach、取り出して削除するならDequeueと覚えておくとよいでしょう。

6-3. Containsで特定の要素が存在するか確認する

Queueに特定の要素が含まれているか確認するには、Containsメソッドを使います。

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

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

bool exists = queue.Contains("B");

Console.WriteLine(exists);

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

True

Containsは、指定した値がQueue内に存在する場合にtrueを返します。公式ドキュメントでも、Containsは要素がQueue<T>内にあるかどうかを判断するメソッドとして説明されています。Microsoft Learn

C#
if (queue.Contains("B"))
{
Console.WriteLine("Bが見つかりました");
}
else
{
Console.WriteLine("Bは見つかりませんでした");
}

ただし、Queueは先頭から順番に処理するためのコレクションです。頻繁に検索したり、特定の位置の要素を操作したりしたい場合は、List<T>Dictionary<TKey, TValue>など別のコレクションも検討しましょう。

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

Queueの中身を配列として取得したい場合は、ToArrayメソッドを使います。

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);
}

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

A
B
C

ToArrayは、Queueの要素を新しい配列にコピーします。公式ドキュメントでも、ToArrayQueue<T>要素を新しい配列にコピーするメソッドとして説明されています。Microsoft Learn

配列に変換すると、インデックスを使って要素を参照できます。

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

Console.WriteLine(array[0]); // A
Console.WriteLine(array[1]); // B

ただし、これはQueueそのものにインデックスアクセスしているわけではありません。Queueのコピーを配列として取得している点に注意しましょう。

7. Queue<T>の要素を安全に取り出す方法

Queueから要素を取り出すときは、空のQueueに対してDequeuePeekを実行しないように注意が必要です。安全に扱う方法として、Countを確認する方法、TryDequeueを使う方法、TryPeekを使う方法があります。

7-1. Countを確認してからDequeueする方法

最も基本的な方法は、Countで要素数を確認してからDequeueする方法です。

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

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

このように書けば、空のQueueに対してDequeueを実行することを防げます。

複数の要素をすべて取り出す場合は、while文と組み合わせます。

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

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

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

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

A
B
C

while (queue.Count > 0)という条件により、Queueが空になるまで処理し、空になったらループを終了します。

7-2. TryDequeueで例外を避ける方法

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

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

queue.Enqueue("A");

if (queue.TryDequeue(out string item))
{
Console.WriteLine($"取り出した値: {item}");
}
else
{
Console.WriteLine("Queueは空です");
}

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

取り出した値: A

TryDequeueは、取り出しに成功した場合はtrueを返し、取り出した値をout引数に設定します。Queueが空の場合はfalseを返します。Microsoftの公式ドキュメントでも、TryDequeueは先頭にあるオブジェクトを削除してresultパラメーターにコピーするメソッドとして説明されています。Microsoft Learn

TryDequeueを使うと、次のように簡潔に安全な処理を書けます。

C#
while (queue.TryDequeue(out string item))
{
Console.WriteLine(item);
}

ただし、使用している.NETのバージョンによってはQueue<T>TryDequeueが利用できない場合があります。その場合は、Countを確認してからDequeueする方法を使いましょう。

7-3. TryPeekで先頭要素を安全に確認する方法

先頭要素を削除せずに安全に確認したい場合は、TryPeekを使います。

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

queue.Enqueue("A");

if (queue.TryPeek(out string item))
{
Console.WriteLine($"先頭の値: {item}");
}
else
{
Console.WriteLine("Queueは空です");
}

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

先頭の値: A

TryPeekは、先頭要素が存在する場合にtrueを返し、値をout引数に設定します。要素は削除されません。公式ドキュメントでも、TryPeekは先頭にオブジェクトがあるかを示す値を返し、存在する場合はresultにコピーし、Queueからは削除しないメソッドとして説明されています。Microsoft Learn

Peekでは空のQueueで例外が発生しますが、TryPeekならfalseで判定できます。

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

if (!queue.TryPeek(out string item))
{
Console.WriteLine("確認できる要素がありません");
}

7-4. 空のQueueを扱うときの実装例

実際の開発では、Queueが空かどうかわからない状態で処理することがよくあります。その場合は、次のように安全なメソッドを用意しておくと便利です。

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

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

ProcessNextTask(tasks);

tasks.Enqueue("メール送信");
tasks.Enqueue("ログ出力");

ProcessNextTask(tasks);
ProcessNextTask(tasks);
ProcessNextTask(tasks);
}

static void ProcessNextTask(Queue<string> tasks)
{
if (tasks.TryDequeue(out string task))
{
Console.WriteLine($"{task}を処理しました");
}
else
{
Console.WriteLine("処理するタスクはありません");
}
}
}

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

処理するタスクはありません
メール送信を処理しました
ログ出力を処理しました
処理するタスクはありません

このように、Queueが空の場合の処理をあらかじめ書いておくことで、例外によるプログラム停止を防ぎやすくなります。

8. Queue<T>の要素を削除・初期化する方法

Queueの要素を削除する方法には、Dequeueで1つずつ削除する方法、Clearでまとめて削除する方法、Queueを再作成する方法があります。

8-1. Dequeueで1つずつ削除する

Queueから1つずつ要素を削除するには、Dequeueを使います。

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

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

queue.Dequeue();

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

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

B
C

Dequeueは先頭の要素を削除するため、Aが削除され、BCが残ります。

すべての要素を1つずつ削除しながら処理する場合は、次のように書けます。

C#
while (queue.Count > 0)
{
string item = queue.Dequeue();
Console.WriteLine($"{item}を削除しました");
}

8-2. ClearでQueueの要素をすべて削除する

Queueの要素をすべて削除したい場合は、Clearメソッドを使います。

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

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

queue.Clear();

Console.WriteLine(queue.Count);

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

0

Clearは、Queueからすべてのオブジェクトを削除します。公式ドキュメントでも、ClearQueue<T>からすべてのオブジェクトを削除するメソッドとして説明されています。Microsoft Learn

「中身を完全に空にしたい」場合は、Dequeueを繰り返すよりもClearを使うほうが簡潔です。

8-3. Queueを再作成して初期化する方法

Queueを新しく作り直すことで、初期化する方法もあります。

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

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

queue = new Queue<string>();

Console.WriteLine(queue.Count);

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

0

この方法では、変数queueに新しいQueueを代入しています。結果として、以前のQueueではなく、空のQueueを参照するようになります。

ただし、他の場所でも同じQueueインスタンスを参照している場合は注意が必要です。

C#
Queue<string> queue1 = new Queue<string>();
queue1.Enqueue("A");

Queue<string> queue2 = queue1;

queue1 = new Queue<string>();

Console.WriteLine(queue1.Count); // 0
Console.WriteLine(queue2.Count); // 1

queue1を再作成しても、queue2は元のQueueを参照したままです。共有されているQueueの中身を確実に空にしたい場合は、Clearを使うほうがわかりやすいです。

8-4. 削除後のCount確認サンプル

削除後の状態を確認するには、Countを使います。

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

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

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

Console.WriteLine($"削除前: {queue.Count}");

queue.Dequeue();

Console.WriteLine($"Dequeue後: {queue.Count}");

queue.Clear();

Console.WriteLine($"Clear後: {queue.Count}");
}
}

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

削除前: 3
Dequeue後: 2
Clear後: 0

Dequeueでは1つ減り、Clearではすべて削除されることがわかります。

9. Queue<T>を使った実践的なサンプルコード

ここからは、C#のQueueを実際の処理に近い形で使うサンプルを紹介します。Queueは、順番に処理する必要があるデータと相性がよいコレクションです。

9-1. タスクを順番に処理するQueueの例

まずは、タスクを登録順に処理する例です。

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("すべてのタスクが完了しました");
}
}

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

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

タスクを追加した順番どおりに処理できています。このように、Queueは「処理待ちリスト」を作るときに便利です。

9-2. 待ち行列をQueueで表現する例

次に、窓口の待ち行列をQueueで表現してみます。

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

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

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

Console.WriteLine("受付を開始します");

while (waitingCustomers.Count > 0)
{
string customer = waitingCustomers.Dequeue();
Console.WriteLine($"{customer}さんを案内しました");
}

Console.WriteLine("待っているお客様はいません");
}
}

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

受付を開始します
田中さんを案内しました
佐藤さんを案内しました
鈴木さんを案内しました
待っているお客様はいません

先にQueueへ追加された人から順番に案内されています。QueueのFIFO構造が、待ち行列の表現に向いていることがわかります。

9-3. クラスオブジェクトをQueueで管理する例

Queueには、独自クラスのオブジェクトも格納できます。次の例では、注文情報をQueueで管理します。

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

class Order
{
public int Id { get; }
public string ProductName { get; }

public Order(int id, string productName)
{
Id = id;
ProductName = productName;
}
}

class Program
{
static void Main()
{
Queue<Order> orders = new Queue<Order>();

orders.Enqueue(new Order(1, "ノートPC"));
orders.Enqueue(new Order(2, "マウス"));
orders.Enqueue(new Order(3, "キーボード"));

while (orders.Count > 0)
{
Order order = orders.Dequeue();
Console.WriteLine($"注文ID:{order.Id} 商品:{order.ProductName} を処理しました");
}
}
}

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

注文ID:1 商品:ノートPC を処理しました
注文ID:2 商品:マウス を処理しました
注文ID:3 商品:キーボード を処理しました

このように、単なる文字列や数値だけでなく、業務データを表すオブジェクトもQueueで順番に処理できます。

9-4. while文とQueueを組み合わせた処理例

Queueは、while文と組み合わせることで、「空になるまで処理する」という書き方がしやすくなります。

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

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

logs.Enqueue("ログ1: アプリ起動");
logs.Enqueue("ログ2: ユーザー認証");
logs.Enqueue("ログ3: データ保存");

while (logs.TryDequeue(out string log))
{
Console.WriteLine(log);
}

Console.WriteLine("ログをすべて出力しました");
}
}

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

ログ1: アプリ起動
ログ2: ユーザー認証
ログ3: データ保存
ログをすべて出力しました

TryDequeueを使うと、Queueが空になった時点でfalseが返り、自然にループを終了できます。例外を避けながら順番に処理できるため、実践的なコードで使いやすい書き方です。

10. Queue<T>を使うときの注意点

Queue<T>は便利なコレクションですが、使うときにはいくつか注意点があります。特に、インデックスアクセスができないこと、空のQueueで例外が発生すること、スレッドセーフではないことを理解しておきましょう。

10-1. インデックス指定で要素にアクセスできない

List<T>や配列では、list[0]array[1]のようにインデックスを指定して要素にアクセスできます。

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

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

Console.WriteLine(list[0]); // A

しかし、Queue<T>ではインデックス指定ができません。

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

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

// Console.WriteLine(queue[0]); // コンパイルエラー

Queueは、任意の位置にある要素を取り出すためのコレクションではなく、先頭から順番に処理するためのコレクションです。

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

C#
Console.WriteLine(queue.Peek());

すべての中身を確認したい場合はforeachを使います。

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

特定の位置の要素を頻繁に参照したい場合は、List<T>や配列を使うほうが適しています。

10-2. Dequeue・Peek時のInvalidOperationExceptionに注意

空のQueueに対してDequeuePeekを実行すると、InvalidOperationExceptionが発生します。

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

// string item1 = queue.Dequeue(); // 例外
// string item2 = queue.Peek(); // 例外

そのため、空の可能性があるQueueを扱うときは、次のようにCountを確認しましょう。

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

または、TryDequeueTryPeekを使うと、より安全に処理できます。

C#
if (queue.TryDequeue(out string item))
{
Console.WriteLine(item);
}
else
{
Console.WriteLine("Queueは空です");
}

例外を前提にするよりも、空の状態を通常の分岐として扱うほうが、読みやすく安定したコードになります。

10-3. Queue<T>はスレッドセーフではない

Queue<T>は、複数のスレッドから同時に追加・削除されることを前提にしたコレクションではありません。

たとえば、あるスレッドがEnqueueしている最中に、別のスレッドがDequeueするような処理では、予期しない動作や例外が発生する可能性があります。

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

// 複数スレッドから同時に操作する用途には注意が必要
queue.Enqueue("A");
string item = queue.Dequeue();

単一スレッドで順番に処理する場合はQueue<T>で問題ありません。しかし、複数スレッドから同時にアクセスする場合は、ロックを使って排他制御するか、スレッドセーフなコレクションを検討する必要があります。

Microsoftのドキュメントでも、複数のスレッドから同時にコレクションへアクセスする場合は、System.Collections.Concurrent名前空間のコレクションがスレッドセーフでスケーラブルな操作を提供すると説明されています。Microsoft Learn

10-4. 並列処理ではConcurrentQueue<T>を検討する

並列処理やマルチスレッド環境でQueueを使いたい場合は、ConcurrentQueue<T>を検討します。

ConcurrentQueue<T>は、スレッドセーフなFIFOコレクションです。Microsoftの公式ドキュメントでも、ConcurrentQueue<T>はスレッドセーフな先入れ先出しコレクションとして説明されています。Microsoft Learn

ConcurrentQueue<T>を使うには、次の名前空間を指定します。

C#
using System.Collections.Concurrent;

基本的な使い方は次のとおりです。

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

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

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

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

ConcurrentQueue<T>では、取り出しにTryDequeueを使うのが一般的です。複数スレッドから安全に追加・取り出しを行いたい場合は、通常のQueue<T>ではなくConcurrentQueue<T>を選びましょう。

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

ここでは、C#のQueueやQueue<T>を使うときによくある疑問を解説します。

11-1. Queue<T>とList<T>はどちらを使うべき?

追加した順番どおりに先頭から処理したい場合は、Queue<T>が向いています。

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

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

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

一方、インデックスを指定して要素にアクセスしたい場合や、途中の要素を追加・削除したい場合は、List<T>が向いています。

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

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

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

選び方の目安は次のとおりです。

先に入れたものから順番に処理したい → Queue<T>
何番目の要素かを指定して扱いたい → List<T>

Queueは「順番待ちの処理」に強く、Listは「一覧データの管理」に強いと考えるとわかりやすいです。

11-2. Queue<T>の中身を削除せずに確認できる?

はい、確認できます。

先頭要素だけを削除せずに確認したい場合は、Peekを使います。

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

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

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

すべての中身を削除せずに確認したい場合は、foreachを使います。

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

Peekforeachでは、Queueの中身は削除されません。

11-3. Queue<T>でnullは扱える?

参照型のQueueでは、nullを扱えます。Microsoftの公式ドキュメントでも、Queue<T>は参照型の有効な値としてnullを受け取り、重複する要素を許可すると説明されています。Microsoft Learn

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

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

foreach (string? item in queue)
{
Console.WriteLine(item ?? "nullです");
}

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

A
nullです
C

ただし、nullを許可すると、取り出したあとにNullReferenceExceptionが発生する可能性があります。必要に応じてnullチェックを行いましょう。

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

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

11-4. Queue<T>を逆順に取り出すことはできる?

Queue<T>はFIFO構造なので、標準の取り出し方法では逆順に取り出せません。Dequeueを使うと、常に先に追加した要素から取り出されます。

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

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

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

逆順に取り出したい場合は、Stack<T>の使用を検討します。

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

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

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

また、Queueの中身を一度配列に変換して逆順に処理する方法もあります。

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

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

string[] array = queue.ToArray();
Array.Reverse(array);

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

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

C
B
A

ただし、この方法はQueue本来の使い方とは少し異なります。逆順処理が主目的なら、最初からStack<T>を使うほうが自然です。

11-5. Queue<T>とConcurrentQueue<T>の違いは?

Queue<T>ConcurrentQueue<T>の大きな違いは、複数スレッドからの同時アクセスを想定しているかどうかです。

Queue<T>は、通常の単一スレッド処理で使いやすいFIFOコレクションです。シンプルで扱いやすく、基本的な待ち行列の実装に適しています。

一方、ConcurrentQueue<T>は、複数スレッドから同時に追加・取り出しされる場面を想定したスレッドセーフなFIFOコレクションです。公式ドキュメントでも、ConcurrentQueue<T>はスレッドセーフなFIFOコレクションとして説明されています。Microsoft Learn

単一スレッドで使うなら、基本的にはQueue<T>で十分です。

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

queue.Enqueue("A");
Console.WriteLine(queue.Dequeue());

複数スレッドで安全に使いたいなら、ConcurrentQueue<T>を検討します。

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

queue.Enqueue("A");

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

選び方の目安は次のとおりです。

通常の順番処理 → Queue<T>
複数スレッドから同時に操作 → ConcurrentQueue<T>

まとめ

C#のQueueは、先に追加した要素を先に取り出すFIFO構造のコレクションです。Queue<T>を使うと、処理待ちのタスク、待ち行列、メッセージ、注文データなどを、追加された順番どおりに管理できます。

基本操作は、要素を追加するEnqueue、先頭の要素を取り出して削除するDequeue、先頭の要素を削除せずに確認するPeekです。さらに、Countで要素数を確認し、foreachで中身を表示し、Containsで特定の要素の有無を確認し、ToArrayで配列に変換できます。

空のQueueに対してDequeuePeekを実行すると例外が発生するため、Countを確認するか、TryDequeueTryPeekを使って安全に処理することが大切です。

また、Queue<T>は通常の単一スレッド処理には便利ですが、複数スレッドから同時に操作する場合はConcurrentQueue<T>を検討しましょう。

C#で「先に入ったものから順番に処理する」コードを書きたいときは、Queue<T>を使うことで、意図がわかりやすく保守しやすい実装にできます。