C#のキューとは?Queueの使い方・FIFOの仕組み・実践例を初心者向けに解説
はじめに
C#でプログラムを書いていると、「データを追加した順番どおりに処理したい」という場面がよくあります。
たとえば、印刷待ちの文書を順番に処理したい場合や、ユーザーから届いたお問い合わせを受付順に処理したい場合です。このようなときに便利なのが、キューというデータ構造です。
C#では、キューを扱うためにQueue<T>というコレクションが用意されています。Queue<T>を使うと、先に追加したデータから順番に取り出して処理できます。
この記事では、C#のキューとは何か、Queue<T>の基本的な使い方、FIFOの仕組み、実践的なサンプルコードまで、初心者向けにわかりやすく解説します。
1. C#のキューとは?Queue<T>の基本をわかりやすく解説
C#のキューとは、データを順番に並べて管理し、先に入れたデータを先に取り出すための仕組みです。
C#では、Queue<T>というクラスを使うことで、キューを簡単に扱えます。
1-1. キューは「先に入れたデータを先に取り出す」データ構造
キューは、データ構造の一種です。
キューの大きな特徴は、最初に追加したデータが、最初に取り出されることです。
たとえば、次のような順番でデータを追加したとします。
C#1
2
3
この場合、取り出される順番も次のようになります。
C#1
2
3
つまり、追加した順番を保ったまま処理できます。
この仕組みは、順番待ちの処理にとても向いています。
1-2. C#でキューを扱うときはQueue<T>を使う
C#でキューを使う場合は、Queue<T>を使います。
Tには、格納したいデータの型を指定します。
たとえば、整数を格納するキューなら次のように書きます。
C#Queue<int> numbers = new Queue<int>();
文字列を格納するキューなら、次のように書きます。
C#Queue<string> names = new Queue<string>();
Queue<T>はジェネリックコレクションなので、int、string、独自クラスなど、さまざまな型のデータを扱えます。
1-3. Queue<T>がよく使われる場面
Queue<T>は、次のような場面でよく使われます。
C#// 例:順番に処理したいデータ
Queue<string> tasks = new Queue<string>();
tasks.Enqueue("メール送信");
tasks.Enqueue("ログ保存");
tasks.Enqueue("通知送信");
Queue<T>が向いているのは、次のような処理です。
タスクを順番に処理する
メッセージを受け取った順に処理する
印刷待ちの文書を管理する
ゲーム内の行動順を管理する
幅優先探索のようなアルゴリズムで使う
「先に来たものから順番に処理する」場面では、Queue<T>がとても便利です。
2. キューの仕組み「FIFO」とは
キューを理解するうえで重要なのが、FIFOという考え方です。
2-1. FIFOはFirst In, First Outの略
FIFOは、First In, First Outの略です。
日本語では、先入れ先出しと呼ばれます。
これは、先に入ったデータが先に出ていくという意味です。
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
この例では、A、B、Cの順番で追加しています。
取り出すときも、A、B、Cの順番になります。
2-2. レジ待ちの行列で理解するFIFOの考え方
FIFOは、スーパーやコンビニのレジ待ちをイメージするとわかりやすいです。
最初に並んだ人が、最初にレジで対応されます。
後から来た人が、先に対応されることは通常ありません。
キューもこれと同じです。
C#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<T>は順番待ちを表現するのに適しています。
2-3. FIFOとLIFOの違い
FIFOとよく比較される考え方に、LIFOがあります。
LIFOは、Last In, First Outの略です。
日本語では、後入れ先出しと呼ばれます。
FIFOはキューで使われる考え方です。
一方、LIFOはスタックで使われる考え方です。
C#Queue<string> queue = new Queue<string>();
queue.Enqueue("A");
queue.Enqueue("B");
queue.Enqueue("C");
Console.WriteLine(queue.Dequeue()); // A
キューでは、最初に入れたAが最初に取り出されます。
C#Stack<string> stack = new Stack<string>();
stack.Push("A");
stack.Push("B");
stack.Push("C");
Console.WriteLine(stack.Pop()); // C
スタックでは、最後に入れたCが最初に取り出されます。
順番に処理したいならQueue<T>、最後に追加したものから処理したいならStack<T>を使うと考えるとわかりやすいです。
3. C#でQueue<T>を使う準備
ここからは、C#でQueue<T>を使うための準備を見ていきましょう。
3-1. Queue<T>を使うために必要な名前空間
Queue<T>を使うには、次の名前空間を指定します。
C#using System.Collections.Generic;
C#ファイルの先頭にこのusingを書くことで、Queue<T>を使えるようになります。
C#using System;
using System.Collections.Generic;
class Program
{
static void Main()
{
Queue<string> queue = new Queue<string>();
}
}
3-2. Queue<T>の基本的な宣言方法
Queue<T>の基本的な宣言方法は次のとおりです。
C#Queue<型> 変数名 = new Queue<型>();
たとえば、整数を扱う場合は次のようになります。
C#Queue<int> numbers = new Queue<int>();
文字列を扱う場合は次のようになります。
C#Queue<string> names = new Queue<string>();
C# 9以降では、次のように簡潔に書くこともできます。
C#Queue<int> numbers = new();
初心者のうちは、まず次の書き方を覚えておくとよいでしょう。
C#Queue<int> numbers = new Queue<int>();
3-3. int・string・独自クラスを格納する例
Queue<T>には、さまざまな型のデータを格納できます。
整数を格納する例です。
C#Queue<int> numbers = new Queue<int>();
numbers.Enqueue(10);
numbers.Enqueue(20);
numbers.Enqueue(30);
文字列を格納する例です。
C#Queue<string> names = new Queue<string>();
names.Enqueue("田中");
names.Enqueue("佐藤");
names.Enqueue("鈴木");
独自クラスを格納することもできます。
C#class Customer
{
public string Name { get; set; }
public Customer(string name)
{
Name = name;
}
}
Queue<Customer> customers = new Queue<Customer>();
customers.Enqueue(new Customer("田中さん"));
customers.Enqueue(new Customer("佐藤さん"));
このように、Queue<T>は単純な値だけでなく、オブジェクトも順番に管理できます。
4. Queue<T>の基本操作
Queue<T>でよく使う基本操作を見ていきましょう。
主に使うメソッドやプロパティは次のとおりです。
Enqueue:データを追加するDequeue:データを取り出すPeek:先頭のデータを確認するCount:要素数を確認するClear:キューを空にするContains:特定のデータがあるか確認する
4-1. Enqueueでデータを追加する
Enqueueは、キューにデータを追加するメソッドです。
C#Queue<string> queue = new Queue<string>();
queue.Enqueue("A");
queue.Enqueue("B");
queue.Enqueue("C");
この場合、キューにはA、B、Cの順番でデータが入ります。
Enqueueで追加したデータは、キューの末尾に追加されます。
4-2. Dequeueでデータを取り出す
Dequeueは、キューの先頭からデータを取り出すメソッドです。
C#Queue<string> queue = new Queue<string>();
queue.Enqueue("A");
queue.Enqueue("B");
queue.Enqueue("C");
string value = queue.Dequeue();
Console.WriteLine(value); // A
Dequeueを実行すると、先頭のデータが取り出されます。
取り出されたデータは、キューから削除されます。
C#Console.WriteLine(queue.Count); // 2
上の例では、Aを取り出したあと、キューにはBとCが残ります。
4-3. Peekで先頭のデータを確認する
Peekは、キューの先頭にあるデータを確認するメソッドです。
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を使うと、次に取り出されるデータを確認できます。
ただし、キューの中身はそのまま残ります。
4-4. Countでキュー内の要素数を確認する
Countは、キューに入っている要素数を確認するプロパティです。
C#Queue<string> queue = new Queue<string>();
queue.Enqueue("A");
queue.Enqueue("B");
Console.WriteLine(queue.Count); // 2
Countは、キューが空かどうかを確認するときにもよく使います。
C#if (queue.Count > 0)
{
string value = queue.Dequeue();
Console.WriteLine(value);
}
DequeueやPeekを使う前には、Countで要素があるか確認すると安全です。
4-5. Clearでキューを空にする
Clearは、キューの中身をすべて削除するメソッドです。
C#Queue<string> queue = new Queue<string>();
queue.Enqueue("A");
queue.Enqueue("B");
queue.Enqueue("C");
queue.Clear();
Console.WriteLine(queue.Count); // 0
処理をリセットしたいときや、不要になったデータをまとめて消したいときに使います。
4-6. Containsで特定のデータがあるか確認する
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
指定したデータが存在する場合はtrue、存在しない場合はfalseを返します。
C#Console.WriteLine(queue.Contains("D")); // False
5. Queue<T>の使い方をサンプルコードで解説
ここからは、Queue<T>の使い方を具体的なサンプルコードで見ていきます。
5-1. 数値を順番に追加・取り出す基本例
まずは、数値をキューに追加して、順番に取り出す基本例です。
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);
Console.WriteLine(numbers.Dequeue()); // 10
Console.WriteLine(numbers.Dequeue()); // 20
Console.WriteLine(numbers.Dequeue()); // 30
}
}
10、20、30の順番で追加したので、取り出す順番も同じです。
これがキューの基本であるFIFOの動きです。
5-2. 文字列を順番待ちとして処理する例
次は、文字列を使って順番待ちを表現する例です。
C#using System;
using System.Collections.Generic;
class Program
{
static void Main()
{
Queue<string> waitingList = new Queue<string>();
waitingList.Enqueue("田中さん");
waitingList.Enqueue("佐藤さん");
waitingList.Enqueue("鈴木さん");
Console.WriteLine("次に対応する人:");
Console.WriteLine(waitingList.Dequeue());
Console.WriteLine("次に対応する人:");
Console.WriteLine(waitingList.Dequeue());
}
}
実行結果は次のようになります。
C#次に対応する人:
田中さん
次に対応する人:
佐藤さん
先に追加した人から順番に処理されています。
5-3. foreachでQueue<T>の中身を確認する例
Queue<T>は、foreachを使って中身を確認できます。
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");
foreach (string item in queue)
{
Console.WriteLine(item);
}
}
}
実行結果は次のようになります。
C#A
B
C
foreachで確認しても、キューの中身は削除されません。
C#Console.WriteLine(queue.Count); // 3
中身を表示したいだけなら、foreachを使うと便利です。
5-4. while文とCountを使って空になるまで処理する例
キューの中身をすべて処理したい場合は、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("すべてのタスクが完了しました");
}
}
実行結果は次のようになります。
C#メール送信を処理しました
ログ保存を処理しました
通知送信を処理しました
すべてのタスクが完了しました
キューが空になるまで、先頭から順番にデータを取り出して処理しています。
6. Queue<T>で初心者がつまずきやすい注意点
Queue<T>は使いやすいコレクションですが、初心者がつまずきやすいポイントもあります。
特に、空のキューに対してDequeueやPeekを使うと例外が発生する点に注意しましょう。
6-1. 空のQueue<T>にDequeueすると例外が発生する
空のQueue<T>に対してDequeueを実行すると、例外が発生します。
C#Queue<string> queue = new Queue<string>();
string value = queue.Dequeue(); // 例外が発生
キューにデータがないのに取り出そうとしているためです。
このようなエラーを防ぐには、Countで確認してからDequeueを使います。
C#if (queue.Count > 0)
{
string value = queue.Dequeue();
Console.WriteLine(value);
}
else
{
Console.WriteLine("キューは空です");
}
6-2. 空のQueue<T>にPeekすると例外が発生する
Peekも同じように、空のキューに対して実行すると例外が発生します。
C#Queue<string> queue = new Queue<string>();
string value = queue.Peek(); // 例外が発生
先頭のデータを確認しようとしても、そもそもデータが存在しないためです。
Peekを使う前にも、Countで確認すると安全です。
C#if (queue.Count > 0)
{
Console.WriteLine(queue.Peek());
}
else
{
Console.WriteLine("確認できるデータがありません");
}
6-3. TryDequeue・TryPeekを使って安全に処理する
TryDequeueやTryPeekを使うと、キューが空でも例外を発生させずに処理できます。
TryDequeueは、取り出しに成功した場合はtrueを返します。
C#Queue<string> queue = new Queue<string>();
queue.Enqueue("A");
if (queue.TryDequeue(out string value))
{
Console.WriteLine(value);
}
else
{
Console.WriteLine("キューは空です");
}
TryPeekは、先頭のデータを確認できた場合にtrueを返します。
C#Queue<string> queue = new Queue<string>();
if (queue.TryPeek(out string value))
{
Console.WriteLine("先頭のデータ: " + value);
}
else
{
Console.WriteLine("キューは空です");
}
TryDequeueやTryPeekを使うと、Countによる事前チェックを書かずに安全な処理ができます。
6-4. Queue<T>はインデックス指定で要素を取り出せない
Queue<T>は、List<T>や配列のようにインデックスを指定して要素を取り出すことはできません。
次のような書き方はできません。
C#Queue<string> queue = new Queue<string>();
queue.Enqueue("A");
queue.Enqueue("B");
// string value = queue[0]; // エラー
Queue<T>は、先頭から順番に取り出すためのコレクションです。
特定の位置にあるデータを頻繁に取り出したい場合は、List<T>など別のコレクションを検討しましょう。
6-5. Queue<T>は通常スレッドセーフではない
Queue<T>は、通常の使い方ではスレッドセーフではありません。
スレッドセーフとは、複数のスレッドから同時にアクセスしても安全に動作する性質のことです。
複数のスレッドから同時にキューへ追加したり、取り出したりする場合、Queue<T>では問題が起きる可能性があります。
そのような場合は、ConcurrentQueue<T>の利用を検討します。
C#using System.Collections.Concurrent;
ConcurrentQueue<string> queue = new ConcurrentQueue<string>();
queue.Enqueue("A");
if (queue.TryDequeue(out string value))
{
Console.WriteLine(value);
}
通常の単純なプログラムではQueue<T>で十分ですが、複数スレッドで共有する場合は注意が必要です。
7. Queue<T>と他のコレクションの違い
C#には、Queue<T>以外にもさまざまなコレクションがあります。
ここでは、Stack<T>、List<T>、配列との違いを見ていきます。
7-1. Queue<T>とStack<T>の違い
Queue<T>とStack<T>の大きな違いは、データを取り出す順番です。
Queue<T>はFIFOです。
先に追加したデータを先に取り出します。
C#Queue<string> queue = new Queue<string>();
queue.Enqueue("A");
queue.Enqueue("B");
queue.Enqueue("C");
Console.WriteLine(queue.Dequeue()); // A
一方、Stack<T>はLIFOです。
最後に追加したデータを先に取り出します。
C#Stack<string> stack = new Stack<string>();
stack.Push("A");
stack.Push("B");
stack.Push("C");
Console.WriteLine(stack.Pop()); // C
順番待ちのような処理にはQueue<T>、元に戻す処理や履歴管理のような処理にはStack<T>が向いています。
7-2. Queue<T>とList<T>の違い
List<T>は、インデックスを使って自由に要素へアクセスできるコレクションです。
C#List<string> list = new List<string>();
list.Add("A");
list.Add("B");
list.Add("C");
Console.WriteLine(list[0]); // A
一方、Queue<T>はインデックス指定ができません。
C#Queue<string> queue = new Queue<string>();
queue.Enqueue("A");
queue.Enqueue("B");
queue.Enqueue("C");
Console.WriteLine(queue.Dequeue()); // A
List<T>は、要素を自由に追加・削除・参照したい場合に向いています。
Queue<T>は、追加した順番どおりに処理したい場合に向いています。
7-3. Queue<T>と配列の違い
配列は、あらかじめ決まった数の要素を扱うのに向いています。
C#string[] names = new string[3];
names[0] = "田中";
names[1] = "佐藤";
names[2] = "鈴木";
配列はインデックス指定で要素にアクセスできます。
C#Console.WriteLine(names[0]); // 田中
一方、Queue<T>は、データを順番に追加し、先頭から取り出すためのコレクションです。
C#Queue<string> queue = new Queue<string>();
queue.Enqueue("田中");
queue.Enqueue("佐藤");
queue.Enqueue("鈴木");
Console.WriteLine(queue.Dequeue()); // 田中
固定されたデータを扱うなら配列、順番待ちのように追加と取り出しを繰り返すならQueue<T>が便利です。
7-4. 複数スレッドで使うならConcurrentQueue<T>を検討する
複数スレッドでキューを共有する場合は、ConcurrentQueue<T>を検討します。
ConcurrentQueue<T>は、複数のスレッドから安全に操作できるキューです。
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>では、DequeueではなくTryDequeueを使います。
また、先頭を確認するときはTryPeekを使います。
複数スレッドで安全にキューを扱いたい場合は、Queue<T>ではなくConcurrentQueue<T>を選ぶとよいでしょう。
8. Queue<T>の実践的な使い道
Queue<T>は、実際のプログラムでもさまざまな場面で使えます。
ここでは、代表的な使い道を紹介します。
8-1. タスクを順番に処理する
複数のタスクを順番に処理したい場合、Queue<T>が便利です。
C#Queue<string> tasks = new Queue<string>();
tasks.Enqueue("データ読み込み");
tasks.Enqueue("データ加工");
tasks.Enqueue("データ保存");
while (tasks.Count > 0)
{
string task = tasks.Dequeue();
Console.WriteLine(task + "を実行します");
}
このように、登録された順番どおりにタスクを処理できます。
8-2. ログやメッセージを順番に処理する
ログやメッセージを受け取った順番に処理したい場合にも、Queue<T>が使えます。
C#Queue<string> messages = new Queue<string>();
messages.Enqueue("ログ1: 起動しました");
messages.Enqueue("ログ2: 処理を開始しました");
messages.Enqueue("ログ3: 処理が完了しました");
while (messages.Count > 0)
{
string message = messages.Dequeue();
Console.WriteLine(message);
}
受信した順番を保ったまま処理できるため、処理の流れを追いやすくなります。
8-3. 幅優先探索でQueue<T>を使う
Queue<T>は、幅優先探索でもよく使われます。
幅優先探索は、近い場所から順番に探索していくアルゴリズムです。
簡単な例として、数値を順番に探索するイメージを見てみましょう。
C#Queue<int> queue = new Queue<int>();
queue.Enqueue(1);
while (queue.Count > 0)
{
int current = queue.Dequeue();
Console.WriteLine(current);
if (current < 5)
{
queue.Enqueue(current + 1);
}
}
この例では単純な数値の処理ですが、実際には木構造やグラフ構造の探索でQueue<T>がよく使われます。
8-4. ゲームやアプリで順番待ちを管理する
ゲームやアプリでも、順番待ちを管理する場面があります。
たとえば、プレイヤーの行動順を管理する場合です。
C#Queue<string> players = new Queue<string>();
players.Enqueue("プレイヤー1");
players.Enqueue("プレイヤー2");
players.Enqueue("プレイヤー3");
while (players.Count > 0)
{
string player = players.Dequeue();
Console.WriteLine(player + "のターンです");
}
このように、登録した順番どおりに処理したい場合にQueue<T>が役立ちます。
9. Queue<T>を使った実践サンプル
ここからは、Queue<T>を使った実践的なサンプルを紹介します。
9-1. 印刷待ちキューを作るサンプル
印刷待ちの文書を順番に処理するサンプルです。
C#using System;
using System.Collections.Generic;
class Program
{
static void Main()
{
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 + "を印刷しました");
}
Console.WriteLine("すべての印刷が完了しました");
}
}
実行結果は次のようになります。
C#見積書.pdfを印刷しました
請求書.pdfを印刷しました
会議資料.pdfを印刷しました
すべての印刷が完了しました
最初に追加した文書から順番に印刷されています。
9-2. お問い合わせ対応を順番に処理するサンプル
お問い合わせを受付順に処理するサンプルです。
C#using System;
using System.Collections.Generic;
class Program
{
static void Main()
{
Queue<string> inquiries = new Queue<string>();
inquiries.Enqueue("お問い合わせ1: ログインできない");
inquiries.Enqueue("お問い合わせ2: パスワードを変更したい");
inquiries.Enqueue("お問い合わせ3: 請求内容を確認したい");
while (inquiries.Count > 0)
{
string inquiry = inquiries.Dequeue();
Console.WriteLine("対応中: " + inquiry);
}
Console.WriteLine("すべてのお問い合わせに対応しました");
}
}
お問い合わせ対応のように、受付順が重要な処理ではQueue<T>が適しています。
9-3. タスク処理キューを作るサンプル
複数のタスクを順番に実行するサンプルです。
C#using System;
using System.Collections.Generic;
class Program
{
static void Main()
{
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();
}
}
}
この例では、Actionを使って処理そのものをキューに格納しています。
登録した順番どおりに処理を実行できます。
9-4. 独自クラスをQueue<T>に格納するサンプル
最後に、独自クラスをQueue<T>に格納するサンプルです。
C#using System;
using System.Collections.Generic;
class Job
{
public int Id { get; set; }
public string Name { get; set; }
public Job(int id, string name)
{
Id = id;
Name = name;
}
}
class Program
{
static void Main()
{
Queue<Job> jobs = new Queue<Job>();
jobs.Enqueue(new Job(1, "メール送信"));
jobs.Enqueue(new Job(2, "CSV出力"));
jobs.Enqueue(new Job(3, "バックアップ"));
while (jobs.Count > 0)
{
Job job = jobs.Dequeue();
Console.WriteLine($"Job ID: {job.Id}, Name: {job.Name}");
}
}
}
実行結果は次のようになります。
C#Job ID: 1, Name: メール送信
Job ID: 2, Name: CSV出力
Job ID: 3, Name: バックアップ
独自クラスを使うことで、複数の情報をまとめてキューで管理できます。
10. C#のキューに関するよくある質問
ここでは、C#のキューやQueue<T>について、初心者が疑問に思いやすいポイントを解説します。
10-1. Queue<T>とQueueの違いは?
Queue<T>は、型を指定して使うジェネリック版のキューです。
C#Queue<string> queue = new Queue<string>();
この場合、string型のデータだけを格納できます。
一方、Queueは非ジェネリック版のキューです。
C#System.Collections.Queue queue = new System.Collections.Queue();
queue.Enqueue("A");
queue.Enqueue(100);
非ジェネリック版のQueueは、さまざまな型のデータを入れられますが、取り出すときに型変換が必要になることがあります。
現在のC#では、基本的にはQueue<T>を使うのがおすすめです。
型が明確になり、コードも安全で読みやすくなります。
10-2. Queue<T>はnullを格納できる?
Queue<T>は、参照型であればnullを格納できます。
C#Queue<string?> queue = new Queue<string?>();
queue.Enqueue("A");
queue.Enqueue(null);
Console.WriteLine(queue.Dequeue()); // A
Console.WriteLine(queue.Dequeue()); // null
ただし、nullを格納すると、取り出した後の処理でNullReferenceExceptionが発生する可能性があります。
そのため、nullを扱う場合は事前に確認しましょう。
C#string? value = queue.Dequeue();
if (value != null)
{
Console.WriteLine(value.Length);
}
else
{
Console.WriteLine("値はnullです");
}
10-3. Queue<T>の順番を途中で変更できる?
Queue<T>は、基本的に順番を途中で変更するためのコレクションではありません。
キューは、先に追加したデータを先に取り出すための仕組みです。
そのため、途中の要素を入れ替えたり、特定の位置に挿入したりする操作には向いていません。
順番を自由に変更したい場合は、List<T>を使う方が適しています。
C#List<string> list = new List<string>();
list.Add("A");
list.Add("B");
list.Add("C");
list.Insert(1, "X");
foreach (string item in list)
{
Console.WriteLine(item);
}
順番を厳密に守って処理したいならQueue<T>、自由に並べ替えたいならList<T>を選びましょう。
10-4. Queue<T>の中身を配列やListに変換できる?
Queue<T>の中身は、配列やList<T>に変換できます。
配列に変換する場合は、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);
}
List<T>に変換する場合は、ToListを使います。
C#using System.Linq;
List<string> list = queue.ToList();
ToListを使うには、次の名前空間が必要です。
C#using System.Linq;
配列やリストに変換しても、元のQueue<T>の中身は削除されません。
10-5. Queue<T>はどんなときに使うべき?
Queue<T>は、データを追加した順番どおりに処理したいときに使うべきです。
たとえば、次のような場面に向いています。
受付順に処理する
タスクを登録順に実行する
メッセージを受信順に処理する
印刷待ちを管理する
幅優先探索を実装する
ゲームのターン順を管理する
反対に、途中の要素を頻繁に変更したい場合や、インデックスで自由にアクセスしたい場合は、List<T>や配列の方が向いています。
「先に入れたものを、先に処理したい」と思ったら、Queue<T>を使うとよいでしょう。
まとめ
C#のキューは、データを追加した順番どおりに取り出すためのデータ構造です。
C#では、Queue<T>を使うことで、FIFOの仕組みを簡単に実装できます。
Queue<T>の基本操作は、次のとおりです。
Enqueueでデータを追加するDequeueで先頭のデータを取り出すPeekで先頭のデータを確認するCountで要素数を確認するClearで中身を空にするContainsで特定のデータがあるか確認する
Queue<T>は、順番待ちの処理にとても向いています。
印刷待ち、タスク処理、お問い合わせ対応、メッセージ処理、幅優先探索など、実際のプログラムでも幅広く活用できます。
一方で、空のキューにDequeueやPeekを使うと例外が発生する点には注意が必要です。
安全に処理したい場合は、Countで確認するか、TryDequeueやTryPeekを使いましょう。
C#で「先に追加したデータから順番に処理したい」と思ったときは、Queue<T>を使うのが基本です。

