C# Queue.Peekの使い方|先頭要素を削除せず取得する方法とDequeueとの違い

はじめに

C#で待ち行列のようなデータ構造を扱うときによく使われるのがQueueです。Queueは、先に追加した要素から順番に取り出す「FIFO(First In, First Out)」の仕組みを持っています。

その中でもQueue.Peekは、先頭にある要素を確認したいときに使うメソッドです。Dequeueと似ていますが、Peekは要素を削除しない点が大きな違いです。

この記事では、C#のQueue.Peekの基本的な使い方、Dequeueとの違い、空のQueueで発生する例外、安全に取得するためのTryPeek、実務での活用例までわかりやすく解説します。

1. C#のQueue.Peekとは

Queue.Peekとは、Queueの先頭にある要素を削除せずに取得するためのメソッドです。

Queueは「待ち行列」とも呼ばれ、最初に追加されたデータが最初に取り出されます。たとえば、受付番号、タスク処理、メッセージ処理など、順番に処理したいデータを管理する場面でよく使われます。

1-1. Queue.Peekでできること

Peekを使うと、Queueの先頭要素を確認できます。

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

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

string first = queue.Peek();

Console.WriteLine(first); // A

このコードでは、Queueの先頭にある"A"を取得しています。ただし、Peekを実行しても"A"はQueueから削除されません。

1-2. 先頭要素を削除せずに取得できる仕組み

Queueでは、要素は追加された順番に並びます。

C#
A  B  C

この状態でPeekを実行すると、先頭のAを参照します。

C#
queue.Peek(); // Aを取得

しかし、Queueの中身は変わりません。

C#
A  B  C

つまり、Peekは「次に処理される要素を確認するだけ」のメソッドです。実際に取り出して削除したい場合はDequeueを使います。

1-3. Queue<T>.Peekと非ジェネリックQueue.Peekの違い

C#には、主に2種類のQueueがあります。

C#
Queue<T>

C#
Queue

です。

Queue<T>はジェネリックコレクションで、型を指定して使います。

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

int value = numbers.Peek();

一方、非ジェネリックのQueueSystem.Collections名前空間にあり、要素をobjectとして扱います。

C#
Queue queue = new Queue();
queue.Enqueue(10);

object value = queue.Peek();

現在のC#では、基本的にQueue<T>を使うのがおすすめです。型が明確になり、キャストの手間や実行時エラーを減らせるためです。

1-4. Peekが使われる代表的な場面

Peekは、次に処理する要素を確認したいが、まだQueueから削除したくない場面で使います。

たとえば、次のようなケースです。

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

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

Console.WriteLine($"次の処理: {tasks.Peek()}");

このように、処理前の確認、条件判定、ログ出力、順番待ちの確認などで便利です。

2. Queue.Peekの基本的な使い方

ここからは、Queue.Peekの基本的な使い方をコード付きで解説します。

2-1. Queue<T>を使うための準備

Queue<T>を使うには、System.Collections.Generic名前空間を使用します。

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

基本的なQueueの作成方法は次のとおりです。

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

この例では、文字列を格納するQueueを作成しています。

2-2. Queueに要素を追加するEnqueueの基本

Queueに要素を追加するにはEnqueueを使います。

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

queue.Enqueue("Apple");
queue.Enqueue("Banana");
queue.Enqueue("Cherry");

この場合、Queueの中身は次の順番になります。

C#
Apple  Banana  Cherry

最初に追加されたAppleが先頭要素です。

2-3. Peekで先頭要素を取得するサンプルコード

Peekを使うと、先頭要素を取得できます。

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

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

queue.Enqueue("Apple");
queue.Enqueue("Banana");
queue.Enqueue("Cherry");

string first = queue.Peek();

Console.WriteLine(first);
}
}

実行結果は次のとおりです。

C#
Apple

Peekは先頭要素を返すだけなので、Queueの中身はそのまま残ります。

2-4. Peek実行後もQueue内の要素が残ることを確認する

Peekを実行しても要素が削除されないことを、Countで確認してみましょう。

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

queue.Enqueue("Apple");
queue.Enqueue("Banana");
queue.Enqueue("Cherry");

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

string first = queue.Peek();

Console.WriteLine(first); // Apple
Console.WriteLine(queue.Count); // 3

Peekの前後でCountは変わりません。つまり、PeekはQueueの状態を変更しないメソッドです。

2-5. string型・int型・独自クラスでの使用例

Queue<T>.Peekは、さまざまな型で使えます。

string型の例です。

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

names.Enqueue("Tanaka");
names.Enqueue("Suzuki");

Console.WriteLine(names.Peek()); // Tanaka

int型の例です。

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

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

Console.WriteLine(numbers.Peek()); // 100

独自クラスでも使えます。

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

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

Queue<TaskItem> tasks = new Queue<TaskItem>();

tasks.Enqueue(new TaskItem("データ読み込み"));
tasks.Enqueue(new TaskItem("データ保存"));

TaskItem nextTask = tasks.Peek();

Console.WriteLine(nextTask.Name); // データ読み込み

このように、Queue<T>では型を指定できるため、独自クラスのオブジェクトも安全に扱えます。

3. Queue.PeekとDequeueの違い

Queue.Peekとよく比較されるのがDequeueです。どちらもQueueの先頭要素を扱いますが、動作は異なります。

3-1. Peekは先頭要素を参照する

Peekは、先頭要素を取得しますが削除しません。

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後もQueueの要素数は変わりません。

3-2. Dequeueは先頭要素を取得して削除する

Dequeueは、先頭要素を取得したうえで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にはBだけが残ります。

3-3. PeekとDequeueの動作比較表

メソッド先頭要素を取得要素を削除Queueの要素数主な用途
Peekするしない変わらない次の要素を確認する
Dequeueするする減る要素を取り出して処理する

Peekは確認用、Dequeueは取り出し用と考えるとわかりやすいです。

3-4. 同じQueueに対してPeekとDequeueを実行した結果の違い

同じQueueに対して、PeekDequeueを実行してみます。

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

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

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

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

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

最初のPeekではAを取得しますが、Queueからは削除されません。

その後、DequeueAを取得すると、今度はQueueから削除されます。次にPeekすると、先頭要素はBになります。

3-5. PeekとDequeueを使い分ける判断基準

PeekDequeueは、目的に応じて使い分けます。

次に処理する要素を確認したいだけならPeekを使います。

C#
string next = queue.Peek();

先頭要素を実際に処理して、Queueから削除したいならDequeueを使います。

C#
string item = queue.Dequeue();

条件を確認してから削除したい場合は、Peekで確認して、必要に応じてDequeueする流れが便利です。

C#
if (queue.Peek() == "A")
{
string item = queue.Dequeue();
}

4. Queue.Peekを使うときの注意点

Queue.Peekを使うときは、空のQueueに注意が必要です。

4-1. Queueが空の状態でPeekすると例外が発生する

Queueが空の状態でPeekを呼び出すと、例外が発生します。

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

string value = queue.Peek();

このコードは、Queueに要素がないため正常に実行できません。

4-2. InvalidOperationExceptionが発生する原因

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

C#
System.InvalidOperationException

これは、先頭要素が存在しないのに取得しようとしているためです。

Peekは便利なメソッドですが、Queueが空でないことを前提に動作します。そのため、事前に要素数を確認することが重要です。

4-3. Countプロパティで空チェックしてからPeekする方法

もっとも基本的な対処法は、Countで要素数を確認してからPeekする方法です。

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

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

このように書けば、空のQueueに対してPeekを呼び出すことを防げます。

4-4. TryPeekを使って安全に先頭要素を取得する方法

TryPeekを使うと、Queueが空かどうかを安全に判定しながら先頭要素を取得できます。

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

queue.Enqueue("Apple");

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

TryPeekは、先頭要素を取得できた場合にtrueを返します。Queueが空の場合はfalseを返し、例外は発生しません。

空の可能性があるQueueを扱う場合は、PeekよりもTryPeekのほうが安全です。

4-5. nullが格納されている場合の注意点

参照型のQueueでは、nullを格納できます。

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

queue.Enqueue(null);

string? value = queue.Peek();

Console.WriteLine(value == null); // True

この場合、Peekの結果がnullでも、Queueが空とは限りません。

そのため、nullを格納する可能性がある場合は、Peekの戻り値だけで空かどうかを判断しないようにしましょう。

C#
if (queue.Count > 0)
{
string? value = queue.Peek();
}

または、TryPeekを使って取得できたかどうかを判定するのがおすすめです。

C#
if (queue.TryPeek(out string? value))
{
Console.WriteLine(value ?? "値はnullです");
}

5. Queue.Peekの実践的な活用例

Queue.Peekは、単に先頭要素を表示するだけでなく、実務的な処理でも役立ちます。

5-1. 次に処理するデータを事前確認する

タスクを順番に処理する前に、次のタスク名をログに出したい場合があります。

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

tasks.Enqueue("ファイル読み込み");
tasks.Enqueue("データ変換");
tasks.Enqueue("データ保存");

if (tasks.Count > 0)
{
Console.WriteLine($"次に処理するタスク: {tasks.Peek()}");
}

この時点では、タスクはQueueから削除されません。実際に処理するときにDequeueを使います。

5-2. 条件に合う場合だけDequeueする

先頭要素を確認し、条件に合う場合だけ取り出す処理です。

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

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

if (numbers.Count > 0 && numbers.Peek() >= 10)
{
int value = numbers.Dequeue();
Console.WriteLine($"処理しました: {value}");
}

このように、Peekで条件を確認してからDequeueすると、意図しない削除を防げます。

5-3. タスク処理や待ち行列での利用例

Queueは、順番待ちの処理に適しています。

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

requestQueue.Enqueue("Request-001");
requestQueue.Enqueue("Request-002");
requestQueue.Enqueue("Request-003");

while (requestQueue.Count > 0)
{
Console.WriteLine($"処理予定: {requestQueue.Peek()}");

string request = requestQueue.Dequeue();
Console.WriteLine($"処理中: {request}");
}

Peekで次の処理対象を確認し、Dequeueで実際に取り出して処理しています。

5-4. ログ・メッセージ・イベント処理での利用例

メッセージ処理でもPeekは便利です。たとえば、先頭メッセージの種類を確認してから処理を分岐できます。

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

messages.Enqueue("INFO: 起動しました");
messages.Enqueue("ERROR: 例外が発生しました");

if (messages.Count > 0)
{
string nextMessage = messages.Peek();

if (nextMessage.StartsWith("ERROR"))
{
Console.WriteLine("エラーメッセージを優先確認します。");
}

string message = messages.Dequeue();
Console.WriteLine(message);
}

Peekを使うことで、先頭メッセージの内容を確認してから処理方針を決められます。

5-5. Queue.Peekとループ処理を組み合わせる例

Peekはループ処理と組み合わせることもできます。

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

scores.Enqueue(80);
scores.Enqueue(90);
scores.Enqueue(40);
scores.Enqueue(100);

while (scores.Count > 0)
{
int nextScore = scores.Peek();

if (nextScore < 50)
{
Console.WriteLine($"低いスコアのため処理を停止: {nextScore}");
break;
}

int score = scores.Dequeue();
Console.WriteLine($"処理済みスコア: {score}");
}

この例では、先頭の値が50未満なら処理を停止します。Peekで確認してからDequeueしているため、条件に合わない要素をQueueに残したままにできます。

6. Queue.Peek・TryPeek・Dequeueの使い分け

Queueの先頭要素を扱うメソッドには、PeekTryPeekDequeueがあります。それぞれの役割を理解して使い分けることが大切です。

6-1. 先頭要素を確認するだけならPeek

Queueが空でないことがわかっている場合、先頭要素を確認するだけならPeekを使います。

C#
if (queue.Count > 0)
{
string value = queue.Peek();
}

PeekはQueueの中身を変更しないため、確認用途に向いています。

6-2. 空のQueueでも安全に処理したいならTryPeek

Queueが空かもしれない場合は、TryPeekを使うと安全です。

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

TryPeekは、要素を取得できたかどうかをboolで返します。例外処理を書かずに安全なコードにしやすい点がメリットです。

6-3. 先頭要素を取り出して削除するならDequeue

先頭要素を処理済みにしてQueueから削除したい場合はDequeueを使います。

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

Dequeueは要素を削除するため、処理済みデータをQueueに残したくない場合に適しています。

6-4. 例外処理を避けたい場合の実装パターン

例外を避けたい場合は、CountまたはTryPeekを使います。

Countを使うパターンです。

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

TryPeekを使うパターンです。

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

空のQueueが通常の処理として起こり得る場合は、TryPeekを使うほうが自然です。

6-5. 実務でおすすめの使い分け方

実務では、次のように使い分けるとわかりやすくなります。

目的おすすめのメソッド
先頭要素を確認したいPeek
空の可能性があるQueueから安全に確認したいTryPeek
先頭要素を取り出して削除したいDequeue
条件に合う場合だけ削除したいPeekまたはTryPeekで確認後、Dequeue
例外を避けたいTryPeek

特に、外部入力や非同期処理などでQueueが空になる可能性がある場合は、TryPeekを使うと安全です。

7. Queue.Peekでよくあるエラーと対処法

ここでは、Queue.Peekを使うときによくあるエラーや誤解を紹介します。

7-1. 空のQueueにPeekして例外が出る

もっとも多いエラーは、空のQueueに対してPeekを呼び出すことです。

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

Console.WriteLine(queue.Peek());

このコードはInvalidOperationExceptionになります。

対処法は、Countで確認することです。

C#
if (queue.Count > 0)
{
Console.WriteLine(queue.Peek());
}

または、TryPeekを使います。

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

7-2. Peekしたのに要素が減らない

PeekしたのにQueueの要素数が減らないのは正常な動作です。

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

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

queue.Peek();

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

Peekは参照するだけで削除しません。要素を減らしたい場合はDequeueを使います。

C#
queue.Dequeue();

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

7-3. Dequeueとの違いを誤解して処理結果がずれる

PeekDequeueを混同すると、同じ要素を何度も処理してしまうことがあります。

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

このコードは、Queueの要素数が減らないため無限ループになります。

処理済みの要素を削除したい場合はDequeueを使います。

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

確認だけしたい場合はPeek、処理して削除したい場合はDequeueを使いましょう。

7-4. foreach中にQueueを変更してエラーになる

Queueをforeachで列挙している最中に、EnqueueDequeueで変更するとエラーになることがあります。

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

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

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

このような書き方は避けましょう。

Queueから順番に取り出して処理したい場合は、whileを使うのが適切です。

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

Peekで確認しながら処理したい場合も、whileと組み合わせると安全です。

C#
while (queue.Count > 0)
{
Console.WriteLine($"次の要素: {queue.Peek()}");

string item = queue.Dequeue();
Console.WriteLine($"処理: {item}");
}

7-5. マルチスレッド環境でQueueを扱うときの注意点

通常のQueue<T>は、複数のスレッドから同時に操作する用途には向いていません。

たとえば、あるスレッドでCount > 0を確認した直後に、別のスレッドがDequeueしてQueueを空にする可能性があります。その状態でPeekを呼ぶと、例外が発生することがあります。

C#
if (queue.Count > 0)
{
string value = queue.Peek();
}

単一スレッドでは問題ないことが多いですが、マルチスレッド環境では注意が必要です。

複数スレッドで安全にQueueを扱いたい場合は、ConcurrentQueue<T>の利用を検討します。

C#
using System.Collections.Concurrent;

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

queue.Enqueue("A");

if (queue.TryPeek(out string value))
{
Console.WriteLine(value);
}

ConcurrentQueue<T>では、TryPeekTryDequeueを使って安全に処理できます。

8. Queue.Peekに関するよくある質問

最後に、Queue.Peekに関するよくある質問をまとめます。

8-1. Queue.Peekは要素を削除しますか

いいえ、Queue.Peekは要素を削除しません。

Peekは先頭要素を取得するだけです。Queueから要素を削除したい場合はDequeueを使います。

C#
queue.Peek();    // 削除しない
queue.Dequeue(); // 削除する

8-2. Queue.PeekとQueue.Firstの違いは何ですか

Queue.Peekは、Queueの先頭要素を取得するためのメソッドです。

一方、FirstはLINQの拡張メソッドです。

C#
using System.Linq;

string value = queue.First();

どちらも先頭要素を取得できますが、Queueの先頭を扱う目的であればPeekのほうが意図が明確です。

また、PeekはQueue専用の操作であるため、「次に取り出される要素を確認している」という意味がコードから伝わりやすくなります。

8-3. Queueが空かどうかを確認する方法はありますか

Queueが空かどうかは、Countプロパティで確認できます。

C#
if (queue.Count == 0)
{
Console.WriteLine("Queueは空です。");
}

Peekする前には、次のように確認すると安全です。

C#
if (queue.Count > 0)
{
Console.WriteLine(queue.Peek());
}

また、TryPeekを使えば、空チェックと取得を同時に行えます。

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

8-4. TryPeekはどのバージョンのC#で使えますか

TryPeekはC#の言語機能というより、.NETのライブラリで提供されるメソッドです。そのため、使用できるかどうかはC#のバージョンではなく、利用している.NETのバージョンや対象フレームワークに依存します。

現在の.NET環境でQueue<T>を使う場合は、TryPeekを利用できるケースが一般的です。

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

古い環境でTryPeekが使えない場合は、Countで確認してからPeekする方法を使います。

C#
if (queue.Count > 0)
{
string value = queue.Peek();
}

8-5. Stack.Peekとの違いは何ですか

Queue.PeekStack.Peekは、どちらも要素を削除せずに取得するメソッドです。

違いは、取得する要素の位置です。

QueueはFIFOなので、最初に追加された要素を取得します。

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

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

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

StackはLIFOなので、最後に追加された要素を取得します。

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

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

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

つまり、Queue.Peekは「次に取り出される先頭要素」、Stack.Peekは「最後に追加された一番上の要素」を確認します。

まとめ

C#のQueue.Peekは、Queueの先頭要素を削除せずに取得するメソッドです。

QueueはFIFOのデータ構造であり、最初に追加された要素が最初に取り出されます。Peekを使うと、次に処理される要素を事前に確認できます。

PeekDequeueの違いは、要素を削除するかどうかです。Peekは削除せずに確認するだけ、Dequeueは取得して削除します。

C#
queue.Peek();    // 先頭要素を確認するだけ
queue.Dequeue(); // 先頭要素を取得して削除する

ただし、空のQueueに対してPeekを実行するとInvalidOperationExceptionが発生します。そのため、Countで空チェックをするか、TryPeekを使って安全に取得することが重要です。

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

実務では、次のように使い分けるとよいでしょう。

目的使用するメソッド
先頭要素を確認したいPeek
空でも安全に確認したいTryPeek
先頭要素を取り出して削除したいDequeue

Queue.Peekを正しく使うことで、次に処理するデータを確認しながら、安全でわかりやすいQueue処理を実装できます。