C#のyieldとは?使い方・戻り値・foreachとの関係・注意点を初心者向けに徹底解説
はじめに
C#のyieldは、複数の値を順番に返す処理をシンプルに書ける便利な構文です。
たとえば、配列やList<T>を作ってからまとめて返すのではなく、値が必要になったタイミングで1つずつ返すことができます。
C#IEnumerable<int> GetNumbers()
{
yield return 1;
yield return 2;
yield return 3;
}
このメソッドは、1、2、3を順番に取り出せる列挙可能なデータを返します。
C#のyieldは、foreach、IEnumerable<T>、IEnumerator<T>、LINQ、遅延実行と深く関係しています。最初は少し難しく見えますが、基本的な考え方は「値を1つずつ返すための書き方」です。
この記事では、csharp yieldを初めて学ぶ人に向けて、基本構文、戻り値、foreachとの関係、遅延実行、メリット、注意点、実践例までわかりやすく解説します。
1. C#のyieldとは?初心者にもわかる基本
1-1. yieldは「列挙処理を簡単に書くための構文」
C#のyieldは、複数の値を順番に返す処理を簡単に書くための構文です。
通常、複数の値を返したい場合は、配列やList<T>を作って返すことが多いです。
C#List<int> GetNumbers()
{
var numbers = new List<int>();
numbers.Add(1);
numbers.Add(2);
numbers.Add(3);
return numbers;
}
一方、yield returnを使うと、次のように書けます。
C#IEnumerable<int> GetNumbers()
{
yield return 1;
yield return 2;
yield return 3;
}
このコードでは、List<int>を明示的に作っていません。それでも、呼び出し側ではforeachを使って値を順番に取り出せます。
C#foreach (int number in GetNumbers())
{
Console.WriteLine(number);
}
実行結果は次のとおりです。
1
2
3
yieldは、データを「まとめて作る」のではなく、「必要になったら順番に返す」ための仕組みです。
1-2. yield returnとyield breakの違い
C#のyieldには、主に次の2つがあります。
C#yield return 値;
yield break;
yield returnは、値を1つ返します。
C#IEnumerable<int> GetNumbers()
{
yield return 10;
yield return 20;
yield return 30;
}
この場合、10、20、30が順番に返されます。
一方、yield breakは、列挙をそこで終了します。
C#IEnumerable<int> GetNumbers()
{
yield return 1;
yield return 2;
yield break;
yield return 3;
}
このコードでは、yield breakより後ろのyield return 3は実行されません。
実行結果は次のようになります。
1
2
つまり、yield returnは「値を返して続ける」、yield breakは「列挙を終了する」という違いがあります。
1-3. 通常のreturnとの違い
通常のreturnは、メソッドの処理を終了して、値を1つ返します。
C#int GetNumber()
{
return 10;
}
このメソッドは10を返して終了します。
一方、yield returnは、値を1つ返しますが、メソッド全体を完全に終了するわけではありません。次に値が要求されたとき、前回の続きから処理が再開されます。
C#IEnumerable<int> GetNumbers()
{
yield return 1;
yield return 2;
yield return 3;
}
このメソッドは、1回の呼び出しで1、2、3をまとめて返しているように見えますが、実際にはforeachなどで値が必要になるたびに、1つずつ処理が進みます。
returnは「結果を返して終了する」、yield returnは「値を1つ返して一時停止する」と考えると理解しやすいです。
1-4. yieldを使うと何が便利になるのか
yieldを使うと、次のようなメリットがあります。
まず、コードがシンプルになります。List<T>を作成して、値を追加して、最後に返すという手順を省けます。
C#IEnumerable<string> GetNames()
{
yield return "Alice";
yield return "Bob";
yield return "Charlie";
}
また、すべてのデータを先に作る必要がないため、大量データを扱う場合にも便利です。
たとえば、1万件のデータがあるとしても、最初から1万件すべてをList<T>に入れる必要はありません。必要な分だけ処理できます。
さらに、条件に合う値だけを順番に返す処理も書きやすくなります。
C#IEnumerable<int> GetEvenNumbers(IEnumerable<int> numbers)
{
foreach (int number in numbers)
{
if (number % 2 == 0)
{
yield return number;
}
}
}
このように、yieldは「順番に取り出せるデータ」を自作したいときにとても便利です。
2. C#のyieldの基本的な使い方
2-1. yield returnの基本構文
yield returnの基本構文は次のとおりです。
C#yield return 値;
ただし、yield returnはどこでも使えるわけではありません。基本的には、戻り値の型がIEnumerable<T>またはIEnumerator<T>のメソッドで使います。
よく使われる形は次のとおりです。
C#IEnumerable<int> メソッド名()
{
yield return 1;
yield return 2;
yield return 3;
}
具体例を見てみましょう。
C#IEnumerable<string> GetFruits()
{
yield return "Apple";
yield return "Banana";
yield return "Orange";
}
呼び出し側では、foreachで値を取り出せます。
C#foreach (string fruit in GetFruits())
{
Console.WriteLine(fruit);
}
実行結果は次のようになります。
Apple
Banana
Orange
yield returnで返す値の型は、戻り値のIEnumerable<T>のTと一致している必要があります。
C#IEnumerable<int> GetNumbers()
{
yield return 1; // OK
yield return 2; // OK
// yield return "3"; // エラー
}
IEnumerable<int>ならintを返し、IEnumerable<string>ならstringを返します。
2-2. IEnumerable<T>を返すメソッドで使う例
yieldで最もよく使う戻り値の型はIEnumerable<T>です。
C#IEnumerable<int> GetNumbers()
{
yield return 1;
yield return 2;
yield return 3;
}
このメソッドは、int型の値を順番に取り出せるオブジェクトを返します。
使う側は次のように書けます。
C#var numbers = GetNumbers();
foreach (int number in numbers)
{
Console.WriteLine(number);
}
ここで重要なのは、GetNumbers()の戻り値はList<int>ではないという点です。戻り値はIEnumerable<int>です。
IEnumerable<T>は、「順番に値を取り出せるもの」を表すインターフェイスです。配列やList<T>もIEnumerable<T>として扱えます。
C#IEnumerable<int> numbers1 = new List<int> { 1, 2, 3 };
IEnumerable<int> numbers2 = new int[] { 1, 2, 3 };
IEnumerable<int> numbers3 = GetNumbers();
yieldを使ったメソッドも、同じようにforeachで処理できます。
2-3. IEnumerator<T>を返すメソッドで使う例
yieldは、IEnumerator<T>を返すメソッドでも使えます。
C#IEnumerator<int> GetNumberEnumerator()
{
yield return 1;
yield return 2;
yield return 3;
}
IEnumerator<T>は、実際に値を1つずつ取り出すための仕組みです。
foreachで直接使うなら、一般的にはIEnumerable<T>を返す方が扱いやすいです。IEnumerator<T>を直接使う場合は、次のようにMoveNext()とCurrentを使います。
C#IEnumerator<int> enumerator = GetNumberEnumerator();
while (enumerator.MoveNext())
{
Console.WriteLine(enumerator.Current);
}
実行結果は次のとおりです。
1
2
3
ただし、初心者のうちは、基本的にIEnumerable<T>を返す形を覚えておけば十分です。
C#IEnumerable<int> GetNumbers()
{
yield return 1;
yield return 2;
yield return 3;
}
IEnumerator<T>は、foreachの裏側で使われている仕組みだと考えると理解しやすいです。
2-4. yield breakで列挙を途中終了する例
yield breakを使うと、列挙処理を途中で終了できます。
たとえば、指定した数まで値を返すメソッドを考えてみます。
C#IEnumerable<int> GetNumbersUntil(int max)
{
for (int i = 1; i <= 10; i++)
{
if (i > max)
{
yield break;
}
yield return i;
}
}
このメソッドを次のように呼び出します。
C#foreach (int number in GetNumbersUntil(5))
{
Console.WriteLine(number);
}
実行結果は次のとおりです。
1
2
3
4
5
iが6になった時点でi > maxがtrueになり、yield breakによって列挙が終了します。
なお、この例では次のように書いても同じ結果になります。
C#IEnumerable<int> GetNumbersUntil(int max)
{
for (int i = 1; i <= max; i++)
{
yield return i;
}
}
ただし、複雑な条件で途中終了したい場合には、yield breakが便利です。
2-5. 実行結果から処理の流れを確認する
yieldの処理の流れを理解するには、Console.WriteLineを入れて確認するとわかりやすいです。
C#IEnumerable<int> GetNumbers()
{
Console.WriteLine("Start");
yield return 1;
Console.WriteLine("After 1");
yield return 2;
Console.WriteLine("After 2");
yield return 3;
Console.WriteLine("End");
}
このメソッドをforeachで実行します。
C#foreach (int number in GetNumbers())
{
Console.WriteLine($"Number: {number}");
}
実行結果は次のようになります。
Start
Number: 1
After 1
Number: 2
After 2
Number: 3
End
ここからわかるように、yield returnで値を返すたびに処理が一時停止し、foreach側で値が使われます。
そして、次の値が必要になったときに、前回のyield returnの次の行から処理が再開されます。
これがyieldの大きな特徴です。
3. yieldの戻り値は何になる?IEnumerable・IEnumeratorとの関係
3-1. yieldを使うメソッドの戻り値
yieldを使うメソッドの戻り値としてよく使われるのは、次の型です。
C#IEnumerable<T>
IEnumerator<T>
IEnumerable
IEnumerator
初心者がまず覚えるべきなのは、IEnumerable<T>です。
C#IEnumerable<int> GetNumbers()
{
yield return 1;
yield return 2;
yield return 3;
}
yield returnで返している値はintですが、メソッド全体の戻り値はintではありません。
C#int GetNumbers() // NG
{
yield return 1;
}
このような書き方はできません。
yieldを使うメソッドは、「値を1つ返すメソッド」ではなく、「値を順番に取り出せるものを返すメソッド」です。
そのため、戻り値はIEnumerable<int>のような列挙用の型になります。
3-2. IEnumerable<T>とは何か
IEnumerable<T>は、「順番に値を取り出せること」を表すインターフェイスです。
たとえば、次のようなものはIEnumerable<T>として扱えます。
C#List<int> list = new List<int> { 1, 2, 3 };
int[] array = { 1, 2, 3 };
これらはどちらもforeachで処理できます。
C#foreach (int number in list)
{
Console.WriteLine(number);
}
foreach (int number in array)
{
Console.WriteLine(number);
}
yieldを使ったメソッドも、IEnumerable<T>を返せばforeachで処理できます。
C#IEnumerable<int> GetNumbers()
{
yield return 1;
yield return 2;
yield return 3;
}
IEnumerable<T>は、「このデータは順番に取り出せます」という約束を表していると考えるとよいです。
3-3. IEnumerator<T>とは何か
IEnumerator<T>は、実際に値を1つずつ取り出すためのインターフェイスです。
主に次のようなメンバーがあります。
C#bool MoveNext();
T Current { get; }
MoveNext()は、次の値があるかどうかを調べ、次の位置へ進めます。
Currentは、現在の値を取得します。
イメージとしては、次のような処理です。
C#IEnumerator<int> enumerator = GetNumbers().GetEnumerator();
while (enumerator.MoveNext())
{
int current = enumerator.Current;
Console.WriteLine(current);
}
これは、foreachが内部的に行っている処理に近いものです。
普段はIEnumerator<T>を直接書く機会は多くありません。しかし、foreachやyieldを深く理解するうえでは重要な存在です。
3-4. IEnumerable<T>とIEnumerator<T>の違い
IEnumerable<T>とIEnumerator<T>は名前が似ていますが、役割が違います。
IEnumerable<T>は、「列挙できるもの」です。
C#IEnumerable<int> numbers = GetNumbers();
一方、IEnumerator<T>は、「列挙するための操作を行うもの」です。
C#IEnumerator<int> enumerator = numbers.GetEnumerator();
簡単に言うと、次のような関係です。
IEnumerable<T> = foreachできる対象
IEnumerator<T> = 実際に1つずつ取り出す係
foreachは、IEnumerable<T>からIEnumerator<T>を取得し、MoveNext()とCurrentを使って値を取り出します。
C#foreach (int number in numbers)
{
Console.WriteLine(number);
}
この短いコードの裏側では、列挙子を取得して、次の値へ進み、現在の値を読み取る処理が行われています。
3-5. 配列やList<T>を返す場合との違い
List<T>を返す場合、基本的にはすべての値を先に作ってから返します。
C#List<int> GetNumbersAsList()
{
var list = new List<int>();
list.Add(1);
list.Add(2);
list.Add(3);
return list;
}
一方、yieldを使う場合は、必要になったタイミングで値を返します。
C#IEnumerable<int> GetNumbers()
{
yield return 1;
yield return 2;
yield return 3;
}
少ないデータであれば大きな違いは感じにくいかもしれません。
しかし、大量データや重い処理を扱う場合は差が出ます。List<T>は全件を作成してから返すため、メモリを多く使う場合があります。yieldは1つずつ返せるため、必要な分だけ処理しやすくなります。
ただし、List<T>にはCountで件数をすぐ取得できる、インデックスでアクセスできる、何度も繰り返し使いやすいといった利点もあります。
C#List<int> list = GetNumbersAsList();
Console.WriteLine(list.Count);
Console.WriteLine(list[0]);
yieldとList<T>はどちらが常に優れているというものではなく、用途によって使い分けることが大切です。
4. yieldとforeachの関係
4-1. foreachはIEnumerableを順番に取り出す構文
foreachは、IEnumerableまたはIEnumerable<T>として扱えるデータを順番に取り出す構文です。
C#foreach (var item in collection)
{
// itemを使った処理
}
たとえば、List<int>をforeachで処理できます。
C#var numbers = new List<int> { 1, 2, 3 };
foreach (int number in numbers)
{
Console.WriteLine(number);
}
配列も同じように処理できます。
C#int[] numbers = { 1, 2, 3 };
foreach (int number in numbers)
{
Console.WriteLine(number);
}
yieldを使ったメソッドも、IEnumerable<T>を返していればforeachで処理できます。
C#foreach (int number in GetNumbers())
{
Console.WriteLine(number);
}
つまり、yieldで作ったデータの流れを、foreachが1つずつ取り出しているという関係です。
4-2. yield returnした値がforeachで1つずつ取り出される仕組み
次のコードを見てみましょう。
C#IEnumerable<int> GetNumbers()
{
yield return 1;
yield return 2;
yield return 3;
}
foreach (int number in GetNumbers())
{
Console.WriteLine(number);
}
foreachは、GetNumbers()から返されたIEnumerable<int>を順番に列挙します。
最初の値が必要になると、GetNumbers()の処理が進み、yield return 1で1が返されます。
次の値が必要になると、前回止まった場所の続きから処理が再開され、yield return 2で2が返されます。
さらに次の値が必要になると、yield return 3で3が返されます。
最後まで処理が終わると、foreachも終了します。
このように、yield returnした値が、foreachで1つずつ取り出されます。
4-3. foreach実行時にyieldの処理が進む理由
yieldを使ったメソッドは、呼び出した瞬間にすべての処理が実行されるわけではありません。
C#var numbers = GetNumbers();
この時点では、まだyield returnの処理は進んでいません。
実際に処理が進むのは、foreachなどで値を取り出そうとしたときです。
C#foreach (int number in numbers)
{
Console.WriteLine(number);
}
このタイミングで、GetNumbers()の中身が順番に実行されます。
これは、yieldが遅延実行の仕組みを持っているためです。
遅延実行とは、「必要になるまで処理を実行しない」という考え方です。
4-4. foreachを使った実践コード例
条件に合う値だけをyield returnする例を見てみましょう。
C#IEnumerable<int> GetEvenNumbers(IEnumerable<int> numbers)
{
foreach (int number in numbers)
{
if (number % 2 == 0)
{
yield return number;
}
}
}
このメソッドは、受け取った数値の中から偶数だけを返します。
C#var source = new List<int> { 1, 2, 3, 4, 5, 6 };
foreach (int number in GetEvenNumbers(source))
{
Console.WriteLine(number);
}
実行結果は次のとおりです。
2
4
6
yieldを使うことで、「条件に合う値を見つけたら、その場で返す」という自然な流れで書けます。
List<int>を作る場合は次のようになります。
C#List<int> GetEvenNumbersAsList(IEnumerable<int> numbers)
{
var result = new List<int>();
foreach (int number in numbers)
{
if (number % 2 == 0)
{
result.Add(number);
}
}
return result;
}
この書き方も正しいですが、yieldを使うと一時的なList<int>を作らずに済みます。
4-5. foreachなしでIEnumeratorを使う場合の流れ
foreachを使わずに、IEnumerator<T>を直接使うこともできます。
C#IEnumerable<int> GetNumbers()
{
yield return 1;
yield return 2;
yield return 3;
}
このメソッドを、IEnumerator<int>で処理してみます。
C#IEnumerator<int> enumerator = GetNumbers().GetEnumerator();
while (enumerator.MoveNext())
{
int number = enumerator.Current;
Console.WriteLine(number);
}
実行結果は次のとおりです。
1
2
3
MoveNext()を呼ぶたびに、次の値へ進みます。値がある場合はtrue、もう値がない場合はfalseを返します。
Currentには、現在の値が入っています。
foreachは、このような処理をわかりやすく書くための構文です。通常はforeachを使えば十分ですが、裏側ではIEnumerator<T>が使われていることを知っておくと、yieldの理解が深まります。
5. yieldの大きな特徴:遅延実行
5-1. yieldは呼び出した時点では処理されない
yieldの重要な特徴は、遅延実行です。
次のコードを見てください。
C#IEnumerable<int> GetNumbers()
{
Console.WriteLine("GetNumbers started");
yield return 1;
yield return 2;
yield return 3;
}
このメソッドを呼び出します。
C#var numbers = GetNumbers();
Console.WriteLine("Before foreach");
この時点では、GetNumbers startedは表示されません。
実行結果は次のようになります。
Before foreach
つまり、GetNumbers()を呼び出しただけでは、メソッドの中身はまだ実行されていません。
yieldを使ったメソッドは、列挙が開始されたときに初めて処理が進みます。
5-2. foreachで値が必要になったタイミングで実行される
次のようにforeachを実行すると、初めてyieldの中身が動きます。
C#var numbers = GetNumbers();
Console.WriteLine("Before foreach");
foreach (int number in numbers)
{
Console.WriteLine(number);
}
実行結果は次のとおりです。
Before foreach
GetNumbers started
1
2
3
foreachが最初の値を取り出そうとしたタイミングで、GetNumbers()の処理が始まります。
この動きは、初心者が混乱しやすいポイントです。
メソッドを呼び出した時点ではなく、値を取り出した時点で処理が始まる。これがyieldの遅延実行です。
5-3. 遅延実行のメリット
遅延実行には、いくつかのメリットがあります。
まず、必要な分だけ処理できます。
C#IEnumerable<int> GetNumbers()
{
for (int i = 1; i <= 1000000; i++)
{
yield return i;
}
}
このメソッドは、1から100万までの数値を返せます。
しかし、呼び出し側で最初の5件しか使わない場合、すべてを処理する必要はありません。
C#foreach (int number in GetNumbers().Take(5))
{
Console.WriteLine(number);
}
この場合、必要な5件分だけ処理されます。
また、一時的なList<T>を作らないため、メモリ使用量を抑えやすくなります。
大量データ、ログ処理、ファイル読み込み、ストリーム処理などでは、遅延実行が役立ちます。
5-4. 遅延実行で初心者が混乱しやすいポイント
遅延実行でよくある混乱は、「メソッドを呼んだのに処理されていない」というものです。
C#var numbers = GetNumbers();
このコードだけでは、GetNumbers()の中のyield returnは実行されません。
次のように列挙して初めて処理されます。
C#foreach (int number in numbers)
{
Console.WriteLine(number);
}
また、同じIEnumerable<T>を何度もforeachすると、そのたびに処理が再実行される場合があります。
C#var numbers = GetNumbers();
foreach (int number in numbers)
{
Console.WriteLine(number);
}
foreach (int number in numbers)
{
Console.WriteLine(number);
}
この場合、GetNumbers()の中身は2回列挙されます。
データベースアクセスやファイル読み込みのような重い処理をyieldの中で行っている場合、何度も列挙するとその分コストがかかることがあります。
5-5. デバッグ時に確認すべきこと
yieldを使ったコードをデバッグするときは、次の点を確認するとよいです。
まず、メソッドを呼び出しただけでは処理が進まないことを意識します。
C#var result = GetNumbers();
この時点では中身が実行されていない可能性があります。
次に、どこで列挙されているかを確認します。
C#foreach (var item in result)
{
Console.WriteLine(item);
}
また、LINQのToList()やToArray()を呼ぶと、その時点で列挙が実行されます。
C#var list = GetNumbers().ToList();
デバッグ時に処理の流れを確認したい場合は、yield returnの前後にログを入れるとわかりやすいです。
C#IEnumerable<int> GetNumbers()
{
Console.WriteLine("before 1");
yield return 1;
Console.WriteLine("before 2");
yield return 2;
}
yieldでは、「いつ実行されるか」を意識することがとても重要です。
6. yieldを使うメリット
6-1. コードをシンプルに書ける
yieldを使う大きなメリットは、コードをシンプルに書けることです。
たとえば、条件に合う値を返す処理をList<T>で書くと、次のようになります。
C#List<int> GetPositiveNumbers(IEnumerable<int> numbers)
{
var result = new List<int>();
foreach (int number in numbers)
{
if (number > 0)
{
result.Add(number);
}
}
return result;
}
yieldを使うと、次のように書けます。
C#IEnumerable<int> GetPositiveNumbers(IEnumerable<int> numbers)
{
foreach (int number in numbers)
{
if (number > 0)
{
yield return number;
}
}
}
結果を入れるためのList<int>を作る必要がありません。
「条件に合ったら返す」という処理を、そのままコードに表現できます。
6-2. 一時的なListを作らずに済む
yieldを使うと、一時的なList<T>を作らずに値を返せます。
C#IEnumerable<string> GetLongNames(IEnumerable<string> names)
{
foreach (string name in names)
{
if (name.Length >= 5)
{
yield return name;
}
}
}
このコードでは、条件に一致する名前を見つけるたびにyield returnで返しています。
一時的なコレクションを作らないため、特に大量データを扱うときにメモリ効率がよくなる場合があります。
ただし、後で何度も同じ結果を使いたい場合は、ToList()でリスト化した方がよいこともあります。
C#var longNames = GetLongNames(names).ToList();
このように、必要に応じて遅延実行のまま使うか、リストに変換するかを選べます。
6-3. 大量データを扱いやすい
yieldは、大量データを扱うときに便利です。
たとえば、100万件の数値を生成する処理を考えます。
C#IEnumerable<int> GenerateNumbers()
{
for (int i = 1; i <= 1000000; i++)
{
yield return i;
}
}
このメソッドは、100万件分のList<int>を作って返すわけではありません。
値が必要になるたびに、1つずつ返します。
C#foreach (int number in GenerateNumbers().Take(10))
{
Console.WriteLine(number);
}
この場合、最初の10件だけを処理できます。
すべてのデータをメモリに載せる必要がないため、巨大なデータ列を扱う場合に向いています。
6-4. 必要な分だけ処理できる
yieldは遅延実行されるため、必要な分だけ処理できます。
次のコードでは、条件に一致する最初の値だけを取り出しています。
C#int first = GenerateNumbers()
.First(n => n > 100);
GenerateNumbers()が大量の値を生成できるとしても、Firstは条件に合う最初の値を見つけた時点で列挙を止めます。
これは、すべての値を先にリスト化してから処理する方法とは違います。
C#var list = GenerateNumbers().ToList();
int first = list.First(n => n > 100);
この場合は、先にすべての値をリスト化してしまいます。
必要な分だけ処理したい場合、yieldとLINQの組み合わせは便利です。
6-5. 自作イテレータを簡単に作れる
yieldを使うと、自作イテレータを簡単に作れます。
イテレータとは、値を順番に取り出す仕組みのことです。
たとえば、指定した範囲の数値を返すイテレータは次のように書けます。
C#IEnumerable<int> Range(int start, int count)
{
for (int i = 0; i < count; i++)
{
yield return start + i;
}
}
使う側は、通常のコレクションと同じようにforeachできます。
C#foreach (int number in Range(10, 5))
{
Console.WriteLine(number);
}
実行結果は次のとおりです。
10
11
12
13
14
yieldを使わずに同じような仕組みを作ろうとすると、IEnumerator<T>を自分で実装する必要があり、コードがかなり複雑になります。
yieldは、その複雑な処理をC#コンパイラに任せられる便利な構文です。
7. yieldの注意点・デメリット
7-1. 処理の実行タイミングが直感とずれる
yieldの注意点は、処理の実行タイミングが直感とずれることです。
通常のメソッドは、呼び出した時点で処理が実行されます。
C#var result = GetList();
しかし、yieldを使ったメソッドは、呼び出しただけでは中身が実行されません。
C#var result = GetNumbers();
実際に処理が進むのは、foreachやToList()などで列挙されたときです。
C#foreach (var item in result)
{
Console.WriteLine(item);
}
この違いを理解していないと、「なぜログが出ないのか」「なぜ例外がこのタイミングで発生するのか」と混乱することがあります。
yieldを使うときは、呼び出し時ではなく列挙時に実行されることを意識しましょう。
7-2. 何度も列挙すると処理も何度も実行される
yieldを使ったIEnumerable<T>は、列挙するたびに処理が実行されることがあります。
C#IEnumerable<int> GetNumbers()
{
Console.WriteLine("Start");
yield return 1;
yield return 2;
}
次のように2回foreachするとどうなるでしょうか。
C#var numbers = GetNumbers();
foreach (int number in numbers)
{
Console.WriteLine(number);
}
foreach (int number in numbers)
{
Console.WriteLine(number);
}
実行結果は次のようになります。
Start
1
2
Start
1
2
Startが2回表示されています。
これは、2回列挙したため、GetNumbers()の処理も2回実行されたからです。
結果を何度も使う場合は、必要に応じてToList()でリスト化しておくとよいです。
C#var numbers = GetNumbers().ToList();
これにより、列挙結果を一度確定させてから再利用できます。
7-3. 例外が発生するタイミングに注意する
yieldを使うと、例外が発生するタイミングも遅れることがあります。
C#IEnumerable<int> GetNumbers()
{
throw new InvalidOperationException("エラーが発生しました");
yield return 1;
}
このメソッドを呼び出しただけでは、例外が発生しない場合があります。
C#var numbers = GetNumbers();
実際に列挙したときに例外が発生します。
C#foreach (int number in numbers)
{
Console.WriteLine(number);
}
そのため、例外処理を書く場所には注意が必要です。
C#try
{
foreach (int number in GetNumbers())
{
Console.WriteLine(number);
}
}
catch (InvalidOperationException ex)
{
Console.WriteLine(ex.Message);
}
yieldを使ったメソッドでは、「メソッド呼び出し時」ではなく「列挙時」に例外が起きる可能性があると覚えておきましょう。
7-4. 戻り値の型に制限がある
yield returnは、どんな戻り値のメソッドでも使えるわけではありません。
次のような戻り値では使えません。
C#int GetNumber()
{
yield return 1; // エラー
}
C#void PrintNumbers()
{
yield return 1; // エラー
}
基本的には、次のような型を戻り値にする必要があります。
C#IEnumerable<int> GetNumbers()
{
yield return 1;
}
または、IEnumerator<T>を返す形です。
C#IEnumerator<int> GetNumberEnumerator()
{
yield return 1;
}
yield returnで返す値の型も、戻り値の型と一致している必要があります。
C#IEnumerable<string> GetNames()
{
yield return "Alice"; // OK
// yield return 123; // エラー
}
7-5. yieldを使えない場所がある
yieldは便利ですが、使えない場所もあります。
たとえば、通常のvoidメソッドでは使えません。
C#void Test()
{
yield return 1; // エラー
}
また、通常のreturnと同じ感覚で、値を1つ返すメソッドに使うこともできません。
C#int GetValue()
{
yield return 1; // エラー
}
さらに、ラムダ式や匿名メソッドの中では基本的にyield returnを使えません。
C#Func<IEnumerable<int>> func = () =>
{
yield return 1; // エラー
};
yieldを使いたい場合は、通常のメソッドとして定義するのが基本です。
C#IEnumerable<int> GetValues()
{
yield return 1;
}
そのほか、ref、out、inパラメータを持つメソッドでは使えないなどの制限もあります。
初心者のうちは、「yieldはIEnumerable<T>を返す通常のメソッドで使う」と覚えておくとよいでしょう。
7-6. リソース解放やusingとの関係に注意する
ファイルやデータベース接続など、リソースを扱う処理でyieldを使う場合は注意が必要です。
たとえば、ファイルを1行ずつ読み込む処理を考えます。
C#IEnumerable<string> ReadLines(string path)
{
using var reader = new StreamReader(path);
string? line;
while ((line = reader.ReadLine()) != null)
{
yield return line;
}
}
このコードでは、列挙が続いている間、StreamReaderが使われます。
foreachが最後まで実行されるか、途中で終了した場合でも、通常はusingによってリソースは解放されます。
C#foreach (string line in ReadLines("sample.txt"))
{
Console.WriteLine(line);
}
ただし、yieldの処理は遅延実行されるため、ファイルを開くタイミングも列挙時になります。
また、列挙結果を返した後で、外側のリソースがすでに破棄されているような設計にすると、エラーの原因になります。
リソースを扱うときは、「いつ開かれるか」「いつ閉じられるか」を意識することが大切です。
8. yield returnとreturnの違い
8-1. returnはメソッドを終了する
通常のreturnは、メソッドの処理を終了します。
C#int GetNumber()
{
return 10;
// ここより後ろは実行されない
}
returnが実行されると、その時点で呼び出し元に戻ります。
複数の値を返したい場合は、配列やList<T>などにまとめる必要があります。
C#List<int> GetNumbers()
{
return new List<int> { 1, 2, 3 };
}
この場合、List<int>という1つのオブジェクトを返しています。
8-2. yield returnは値を1つ返して処理を一時停止する
yield returnは、値を1つ返して、処理を一時停止します。
C#IEnumerable<int> GetNumbers()
{
yield return 1;
yield return 2;
yield return 3;
}
最初に値が要求されると、1を返して一時停止します。
次に値が要求されると、続きから処理が再開され、2を返します。
さらに次に、3を返します。
つまり、yield returnは「値を返して終わり」ではなく、「値を返して、次回そこから再開する」ための構文です。
8-3. 複数の値を返したい場合の違い
複数の値を返したい場合、returnだけではそのまま複数回返すことはできません。
C#int GetNumbers()
{
return 1;
return 2; // 実行されない
return 3; // 実行されない
}
通常のreturnは最初の1回でメソッドが終了します。
複数の値を返したいなら、次のようにコレクションにまとめます。
C#List<int> GetNumbers()
{
return new List<int> { 1, 2, 3 };
}
yield returnなら、複数回書けます。
C#IEnumerable<int> GetNumbers()
{
yield return 1;
yield return 2;
yield return 3;
}
値を順番に返したい処理では、yield returnが自然に書けます。
8-4. Listを作ってreturnする場合との比較
List<T>を作ってreturnする場合と、yield returnを使う場合を比較してみましょう。
List<T>を使う場合です。
C#List<int> GetEvenNumbers(List<int> numbers)
{
var result = new List<int>();
foreach (int number in numbers)
{
if (number % 2 == 0)
{
result.Add(number);
}
}
return result;
}
yield returnを使う場合です。
C#IEnumerable<int> GetEvenNumbers(IEnumerable<int> numbers)
{
foreach (int number in numbers)
{
if (number % 2 == 0)
{
yield return number;
}
}
}
List<T>版は、結果をすべて作ってから返します。
yield版は、条件に合った値を見つけたタイミングで1つずつ返します。
すべての結果を確定させたい場合はList<T>が向いています。必要な分だけ順番に処理したい場合はyieldが向いています。
8-5. 使い分けの判断基準
returnとyield returnは、目的によって使い分けます。
値を1つだけ返すなら、通常のreturnを使います。
C#int GetAge()
{
return 20;
}
複数の値をまとめて返し、件数やインデックスアクセスを使いたいなら、List<T>や配列を返す方法が向いています。
C#List<int> GetNumbers()
{
return new List<int> { 1, 2, 3 };
}
複数の値を順番に返したい、必要な分だけ処理したい、一時的なリストを作りたくない場合は、yield returnが向いています。
C#IEnumerable<int> GetNumbers()
{
yield return 1;
yield return 2;
yield return 3;
}
初心者向けにまとめると、次のように考えるとよいです。
1つの値を返す → return
複数の値をまとめて返す → List<T>や配列
複数の値を順番に返す → yield return
9. yieldの実践的な使用例
9-1. 条件に一致する値だけを返す
yieldは、条件に一致する値だけを返す処理と相性がよいです。
C#IEnumerable<int> GetNumbersGreaterThan(IEnumerable<int> numbers, int threshold)
{
foreach (int number in numbers)
{
if (number > threshold)
{
yield return number;
}
}
}
使い方は次のとおりです。
C#var numbers = new List<int> { 5, 10, 15, 20, 25 };
foreach (int number in GetNumbersGreaterThan(numbers, 12))
{
Console.WriteLine(number);
}
実行結果は次のようになります。
15
20
25
このように、条件に合うものだけを順番に返せます。
LINQのWhereに近い処理を、自分で実装しているイメージです。
9-2. 範囲内の数値を順番に返す
指定した範囲の数値を順番に返す処理も、yieldで簡単に書けます。
C#IEnumerable<int> Range(int start, int end)
{
for (int i = start; i <= end; i++)
{
yield return i;
}
}
使い方は次のとおりです。
C#foreach (int number in Range(3, 7))
{
Console.WriteLine(number);
}
実行結果は次のようになります。
3
4
5
6
7
このような処理をList<T>で書くと、リストを作って値を追加する必要があります。
C#List<int> RangeAsList(int start, int end)
{
var result = new List<int>();
for (int i = start; i <= end; i++)
{
result.Add(i);
}
return result;
}
yieldを使うと、値を順番に返す処理を簡潔に書けます。
9-3. ファイルやログを1行ずつ処理する
ファイルやログを1行ずつ処理する場面でも、yieldは役立ちます。
C#IEnumerable<string> ReadLogLines(string path)
{
using var reader = new StreamReader(path);
string? line;
while ((line = reader.ReadLine()) != null)
{
yield return line;
}
}
使う側は、次のように書けます。
C#foreach (string line in ReadLogLines("app.log"))
{
if (line.Contains("ERROR"))
{
Console.WriteLine(line);
}
}
この方法では、ファイル全体を一度にメモリへ読み込む必要がありません。
大きなログファイルを扱う場合でも、1行ずつ処理できます。
ただし、実際の開発ではFile.ReadLinesという便利なメソッドもあります。
C#foreach (string line in File.ReadLines("app.log"))
{
Console.WriteLine(line);
}
File.ReadLinesも遅延実行で行を読み込めるため、考え方としてはyieldに近いです。
9-4. 大量データを分割して処理する
大量データを一定件数ごとに分割する処理にも、yieldを使えます。
C#IEnumerable<List<T>> Chunk<T>(IEnumerable<T> source, int size)
{
var chunk = new List<T>(size);
foreach (T item in source)
{
chunk.Add(item);
if (chunk.Count == size)
{
yield return chunk;
chunk = new List<T>(size);
}
}
if (chunk.Count > 0)
{
yield return chunk;
}
}
使い方は次のとおりです。
C#var numbers = Enumerable.Range(1, 10);
foreach (var group in Chunk(numbers, 3))
{
Console.WriteLine(string.Join(", ", group));
}
実行結果は次のようになります。
1, 2, 3
4, 5, 6
7, 8, 9
10
この例では、3件ずつ分割して返しています。
大量データを少しずつ処理したい場合に便利です。
なお、現在のC#/.NETではLINQにChunkが用意されている環境もありますが、自分で仕組みを理解するためにはよい練習になります。
9-5. LINQと組み合わせて使う
yieldで返したIEnumerable<T>は、LINQと組み合わせて使えます。
C#IEnumerable<int> GetNumbers()
{
for (int i = 1; i <= 10; i++)
{
yield return i;
}
}
このメソッドに対して、WhereやSelectを使えます。
C#var result = GetNumbers()
.Where(n => n % 2 == 0)
.Select(n => n * 10);
foreach (int number in result)
{
Console.WriteLine(number);
}
実行結果は次のとおりです。
20
40
60
80
100
yieldで作った値の流れに対して、LINQで絞り込みや変換を行えます。
yieldとLINQはどちらも遅延実行と関係が深いため、組み合わせると効率よくデータ処理を書けます。
10. yieldを使うべきケース・使わない方がよいケース
10-1. yieldを使うべきケース
yieldを使うべきケースは、値を順番に返したい場合です。
たとえば、次のような処理です。
C#IEnumerable<int> GetEvenNumbers(IEnumerable<int> numbers)
{
foreach (int number in numbers)
{
if (number % 2 == 0)
{
yield return number;
}
}
}
条件に合う値を順番に返したい場合、yieldはとても自然です。
また、大量データを扱う場合にも向いています。
C#IEnumerable<int> GenerateLargeData()
{
for (int i = 0; i < 1000000; i++)
{
yield return i;
}
}
さらに、すべての結果を先に作る必要がない場合、必要な分だけ処理したい場合にも便利です。
foreachやLINQで順番に処理する前提なら、yieldは有力な選択肢です。
10-2. List<T>で返した方がよいケース
一方で、List<T>で返した方がよいケースもあります。
たとえば、結果の件数をすぐに知りたい場合です。
C#List<int> numbers = GetNumbersAsList();
Console.WriteLine(numbers.Count);
IEnumerable<T>でもCount()は使えますが、場合によっては列挙が実行されます。
C#int count = GetNumbers().Count();
また、インデックスでアクセスしたい場合もList<T>が便利です。
C#Console.WriteLine(numbers[0]);
さらに、同じ結果を何度も使う場合も、List<T>にしておいた方がわかりやすいことがあります。
C#var list = GetNumbers().ToList();
何度も列挙するとコストが高い処理では、一度リスト化することで再実行を避けられます。
10-3. パフォーマンス面で考えるポイント
yieldはメモリ効率がよい場合がありますが、常に最速というわけではありません。
yieldを使うと、値を1つずつ返すための状態管理が内部で行われます。そのため、単純な小さなデータでは、List<T>や配列の方がわかりやすく、十分に高速な場合もあります。
一方、大量データを扱う場合や、途中で処理を打ち切る可能性がある場合は、yieldが有利になることがあります。
C#var first = GetLargeNumbers().First(n => n > 1000);
このような処理では、条件に合う値が見つかった時点で列挙を止められます。
パフォーマンスを考えるときは、次の点を意識するとよいです。
すべての結果が必要か
途中で処理を止める可能性があるか
データ量は多いか
同じ結果を何度も使うか
メモリ使用量を抑えたいか
yieldは便利ですが、処理内容に応じて使い分けることが大切です。
10-4. 可読性を優先すべきケース
yieldを使うとコードがシンプルになることが多いですが、複雑に使いすぎると逆に読みにくくなることもあります。
たとえば、条件分岐が多く、途中で何度もyield returnやyield breakが出てくるコードは、処理の流れが追いにくくなる場合があります。
C#IEnumerable<int> GetValues(IEnumerable<int> source)
{
foreach (var item in source)
{
if (item < 0)
{
yield break;
}
if (item % 2 == 0)
{
yield return item;
}
if (item > 100)
{
yield return item * 2;
}
}
}
このようなコードが悪いわけではありませんが、読み手が処理の流れを理解しやすいかを考える必要があります。
初心者が多いチームや、保守性を重視する場面では、あえてList<T>に一度入れてから返す方がわかりやすい場合もあります。
C#List<int> GetValues(IEnumerable<int> source)
{
var result = new List<int>();
foreach (var item in source)
{
// 条件ごとにresultへ追加
}
return result;
}
可読性を優先することも大切です。
10-5. 初心者向けの判断基準
初心者のうちは、次の基準で考えるとよいです。
値を1つだけ返すなら、通常のreturnを使います。
C#int GetNumber()
{
return 1;
}
複数の値をすべて作って返したいなら、List<T>や配列を使います。
C#List<int> GetNumbers()
{
return new List<int> { 1, 2, 3 };
}
複数の値を順番に返したいなら、yield returnを使います。
C#IEnumerable<int> GetNumbers()
{
yield return 1;
yield return 2;
yield return 3;
}
大量データやフィルタ処理では、yieldが役立つ場面が多いです。
ただし、処理の実行タイミングがわかりにくいと感じる場合は、まずはList<T>で書いても問題ありません。
慣れてきたら、List<T>を作って返している処理のうち、「順番に返せばよいもの」をyieldに置き換えてみるとよいでしょう。
11. async・LINQとyieldの関係
11-1. LINQの遅延実行とyieldの関係
LINQの多くのメソッドも、yieldと同じように遅延実行されます。
たとえば、Whereを使った次のコードを見てください。
C#var numbers = new List<int> { 1, 2, 3, 4, 5 };
var evens = numbers.Where(n => n % 2 == 0);
この時点では、まだ絞り込み処理は実行されていません。
実際に実行されるのは、foreachなどで列挙したときです。
C#foreach (int number in evens)
{
Console.WriteLine(number);
}
この動きは、yieldとよく似ています。
yieldは、自分で遅延実行される処理を作るための構文と考えることもできます。
11-2. WhereやSelectとyieldの共通点
LINQのWhereやSelectは、IEnumerable<T>を受け取り、別のIEnumerable<T>を返します。
C#var result = numbers
.Where(n => n % 2 == 0)
.Select(n => n * 10);
これらは、必要になったタイミングで値を1つずつ処理します。
自分で似たような処理を書くと、yieldを使って表現できます。
C#IEnumerable<int> WhereEven(IEnumerable<int> numbers)
{
foreach (int number in numbers)
{
if (number % 2 == 0)
{
yield return number;
}
}
}
Selectに近い処理も書けます。
C#IEnumerable<int> MultiplyByTen(IEnumerable<int> numbers)
{
foreach (int number in numbers)
{
yield return number * 10;
}
}
LINQの仕組みを理解するうえでも、yieldを学ぶことは役立ちます。
11-3. asyncメソッドとyieldの違い
asyncとyieldはどちらも処理を途中で止めるように見えるため、混同しやすいです。
しかし、目的が違います。
asyncは、非同期処理を書くための仕組みです。
C#async Task<string> GetDataAsync()
{
await Task.Delay(1000);
return "data";
}
awaitは、非同期処理の完了を待つために使います。
一方、yieldは、値を順番に返すための仕組みです。
C#IEnumerable<int> GetNumbers()
{
yield return 1;
yield return 2;
}
通常のasync Task<T>メソッドの中では、yield returnを使えません。
C#async Task<IEnumerable<int>> GetNumbersAsync()
{
yield return 1; // エラー
}
非同期で値を順番に返したい場合は、IAsyncEnumerable<T>を使う方法があります。
11-4. IAsyncEnumerable<T>とyield returnの概要
C#では、非同期に値を順番に返すためにIAsyncEnumerable<T>を使えます。
C#async IAsyncEnumerable<int> GetNumbersAsync()
{
for (int i = 1; i <= 3; i++)
{
await Task.Delay(1000);
yield return i;
}
}
このメソッドは、1秒ごとに値を返すような非同期ストリームを表現できます。
呼び出し側では、await foreachを使います。
C#await foreach (int number in GetNumbersAsync())
{
Console.WriteLine(number);
}
通常のIEnumerable<T>とforeachの非同期版と考えると理解しやすいです。
ただし、初心者が最初に学ぶ段階では、まず通常のIEnumerable<T>とyield returnを理解することが大切です。
11-5. まず初心者が理解すべき範囲
初心者が最初に理解すべきなのは、次の範囲です。
yield returnは値を1つずつ返す
yield breakは列挙を終了する
戻り値は主にIEnumerable<T>
foreachで順番に取り出せる
処理は遅延実行される
asyncやIAsyncEnumerable<T>は、通常のyieldを理解した後で学べば十分です。
また、LINQの遅延実行も、yieldを理解してから学ぶとわかりやすくなります。
最初からすべてを完璧に理解する必要はありません。
まずは、次の形を書けるようになることを目指しましょう。
C#IEnumerable<int> GetNumbers()
{
yield return 1;
yield return 2;
yield return 3;
}
そして、foreachで取り出せることを確認しましょう。
C#foreach (int number in GetNumbers())
{
Console.WriteLine(number);
}
12. C#のyieldでよくあるエラーと対処法
12-1. 戻り値の型が間違っている
yieldでよくあるエラーの1つは、戻り値の型が間違っていることです。
次のコードはエラーになります。
C#int GetNumbers()
{
yield return 1;
}
yield returnを使う場合、戻り値はintではなく、IEnumerable<int>などにする必要があります。
C#IEnumerable<int> GetNumbers()
{
yield return 1;
}
複数の値を順番に返すため、戻り値の型も列挙可能な型にする必要があります。
12-2. voidメソッドでyieldを使っている
voidメソッドではyield returnを使えません。
C#void PrintNumbers()
{
yield return 1; // エラー
}
yieldを使う場合は、戻り値をIEnumerable<T>にします。
C#IEnumerable<int> GetNumbers()
{
yield return 1;
yield return 2;
}
表示したい場合は、呼び出し側でforeachします。
C#foreach (int number in GetNumbers())
{
Console.WriteLine(number);
}
yieldは、直接表示するための構文ではなく、値を順番に返すための構文です。
12-3. yield returnで返す型が一致していない
yield returnで返す値の型が、戻り値の型と合っていない場合もエラーになります。
C#IEnumerable<int> GetNumbers()
{
yield return 1;
yield return "2"; // エラー
}
IEnumerable<int>なら、yield returnで返す値はintである必要があります。
正しくは次のようにします。
C#IEnumerable<int> GetNumbers()
{
yield return 1;
yield return 2;
}
文字列を返したい場合は、戻り値をIEnumerable<string>にします。
C#IEnumerable<string> GetNames()
{
yield return "Alice";
yield return "Bob";
}
型が一致しているかを確認しましょう。
12-4. yieldを使えない場所で使っている
yieldは、ラムダ式や匿名メソッドなど、使えない場所があります。
C#Func<IEnumerable<int>> func = () =>
{
yield return 1; // エラー
};
このような場合は、通常のメソッドとして定義します。
C#IEnumerable<int> GetValues()
{
yield return 1;
}
また、ref、out、inパラメータを持つメソッドでも使えません。
C#IEnumerable<int> GetValues(ref int value)
{
yield return value; // エラー
}
yieldを使うメソッドは、シンプルなIEnumerable<T>を返す通常メソッドとして書くのが基本です。
12-5. foreachで想定と違う結果になる
yieldを使った処理で、foreachの結果が想定と違う場合は、遅延実行を疑いましょう。
たとえば、次のようなコードです。
C#var numbers = new List<int> { 1, 2, 3 };
var result = GetNumbers(numbers);
numbers.Add(4);
foreach (int number in result)
{
Console.WriteLine(number);
}
GetNumbersが次のようなyieldメソッドだった場合を考えます。
C#IEnumerable<int> GetNumbers(IEnumerable<int> numbers)
{
foreach (int number in numbers)
{
yield return number;
}
}
この場合、resultを作った時点ではまだ列挙されていません。
その後にnumbers.Add(4)してからforeachしているため、4も出力される可能性があります。
実行結果は次のようになります。
1
2
3
4
最初の状態で結果を固定したい場合は、ToList()を使います。
C#var result = GetNumbers(numbers).ToList();
numbers.Add(4);
foreach (int number in result)
{
Console.WriteLine(number);
}
yieldでは、列挙されるタイミングによって結果が変わることがあるため注意しましょう。
13. C#のyieldに関するよくある質問
13-1. yieldは必ず使う必要がある?
yieldは必ず使う必要がある構文ではありません。
複数の値を返す場合でも、List<T>や配列を使えば実装できます。
C#List<int> GetNumbers()
{
return new List<int> { 1, 2, 3 };
}
ただし、値を順番に返したい場合や、大量データを必要な分だけ処理したい場合には、yieldが便利です。
初心者のうちは、無理に使う必要はありません。
まずはList<T>で書けるようになり、その後で「この処理はyieldで書けるかもしれない」と考えるとよいです。
13-2. yield returnは複数回書ける?
yield returnは複数回書けます。
C#IEnumerable<string> GetNames()
{
yield return "Alice";
yield return "Bob";
yield return "Charlie";
}
この場合、Alice、Bob、Charlieが順番に返されます。
returnは通常1回実行されるとメソッドが終了しますが、yield returnは値を返して一時停止するだけです。
そのため、複数回書くことで、複数の値を順番に返せます。
13-3. yield breakは省略できる?
多くの場合、yield breakは省略できます。
次のコードでは、最後まで処理が進むと自然に列挙が終了します。
C#IEnumerable<int> GetNumbers()
{
yield return 1;
yield return 2;
yield return 3;
}
この場合、最後にyield breakを書く必要はありません。
C#IEnumerable<int> GetNumbers()
{
yield return 1;
yield return 2;
yield return 3;
yield break; // 書いてもよいが通常は不要
}
yield breakが役立つのは、条件によって途中終了したい場合です。
C#IEnumerable<int> GetNumbers(int max)
{
for (int i = 1; i <= 10; i++)
{
if (i > max)
{
yield break;
}
yield return i;
}
}
13-4. yieldは配列やListの代わりになる?
yieldは、配列やList<T>の代わりになる場合もありますが、完全に同じものではありません。
yieldは、値を順番に取り出せるIEnumerable<T>を返します。
C#IEnumerable<int> GetNumbers()
{
yield return 1;
yield return 2;
}
一方、List<T>は、件数を取得したり、インデックスでアクセスしたり、要素を追加・削除したりできます。
C#var list = new List<int> { 1, 2, 3 };
Console.WriteLine(list.Count);
Console.WriteLine(list[0]);
list.Add(4);
yieldで返したIEnumerable<T>は、基本的に順番に取り出す用途に向いています。
インデックスアクセスや追加・削除が必要なら、List<T>を使う方が適しています。
13-5. yieldはパフォーマンスが良い?
yieldは、メモリ効率がよくなる場合があります。
特に、大量データを扱う場合や、途中で処理を止める場合には有効です。
C#var first = GetNumbers().First(n => n > 100);
このような処理では、条件に合う値が見つかった時点で列挙を終了できます。
一方で、yieldは内部的に状態を管理するため、必ずしもすべての場面で最速とは限りません。
少量の固定データなら、配列やList<T>で十分なことも多いです。
パフォーマンスを考えるときは、「メモリ使用量」「列挙回数」「データ量」「途中終了の有無」を総合的に見ることが大切です。
13-6. 初心者はどこまで理解すればよい?
初心者は、まず次のポイントを理解すれば十分です。
yield returnは値を1つずつ返す
yield breakは途中で終了する
戻り値は主にIEnumerable<T>
foreachで取り出せる
呼び出しただけでは実行されず、列挙時に実行される
特に大事なのは、yield returnとforeachの関係です。
C#IEnumerable<int> GetNumbers()
{
yield return 1;
yield return 2;
yield return 3;
}
foreach (int number in GetNumbers())
{
Console.WriteLine(number);
}
このコードの流れを理解できれば、yieldの基本はかなり身についています。
その後で、IEnumerable<T>、IEnumerator<T>、LINQの遅延実行、IAsyncEnumerable<T>などを少しずつ学んでいくとよいでしょう。
まとめ
C#のyieldは、複数の値を順番に返すための便利な構文です。
yield returnを使うと、値を1つ返して処理を一時停止し、次に値が必要になったときに続きから再開できます。
C#IEnumerable<int> GetNumbers()
{
yield return 1;
yield return 2;
yield return 3;
}
呼び出し側では、foreachを使って値を順番に取り出せます。
C#foreach (int number in GetNumbers())
{
Console.WriteLine(number);
}
yield breakを使うと、列挙を途中で終了できます。
C#IEnumerable<int> GetNumbers(int max)
{
for (int i = 1; i <= 10; i++)
{
if (i > max)
{
yield break;
}
yield return i;
}
}
yieldの戻り値は、主にIEnumerable<T>やIEnumerator<T>です。通常のintやvoidを返すメソッドでは使えません。
また、yieldには遅延実行という特徴があります。メソッドを呼び出しただけでは処理されず、foreachやToList()などで列挙されたタイミングで実行されます。
yieldのメリットは、コードをシンプルに書けること、一時的なList<T>を作らずに済むこと、大量データを必要な分だけ処理しやすいことです。
一方で、実行タイミングがわかりにくい、何度も列挙すると処理も何度も実行される、例外の発生タイミングが遅れるといった注意点もあります。
初心者は、まず次のように覚えるとよいです。
return = 値を返してメソッドを終了する
yield return = 値を1つ返して処理を一時停止する
yield break = 列挙を終了する
C#でforeachやLINQをよく使うなら、yieldの理解はとても役立ちます。最初は難しく感じるかもしれませんが、「値を1つずつ返す仕組み」と考えれば、少しずつ使いどころが見えてきます。

