コレクションを返すメソッドで逐次で値を返したい (yield returnの利用) - C#

C#でコレクションを返すメソッドで逐次で値を返すようにするコードを紹介します。

概要

C#でリストなどの複数の値を持つコレクション型を返すメソッドで、逐次的に値を返すロジックにしたい場合があります。 C#ではyield returnキーワードを利用するとコレクションの返却を逐次処理で記述できます。

プログラム例

初めにListを返すメソッドを実装して動作を確認します。

UI

下図のフォームを作成します。ButtonとMultilineプロパティをtrueに設定した複数行のテキストボックスを配置しています。

コード

下記のコードを記述します。button1のクリックイベントを実装しています。
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace YieldReturnDemo
{
  public partial class FormSimpleYieldReturn : Form
  {
    public FormSimpleYieldReturn()
    {
      InitializeComponent();
    }

    private void button1_Click(object sender, EventArgs e)
    {
      List<int> valueList = new List<int>() { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
      List<int> resultList = Func(valueList);
      foreach (int r in resultList)
      {
        textBox1.Text += r.ToString() + ",";
      }
    }

    private List<int> Func(List<int> input){
      List<int> result = new List<int>();

      foreach (int v in input)
      {
        if (v % 2 == 0)
        {
          result.Add(v);
        }
      }
      return result;
    }
  }
}

解説

button1では、入力元のリストを作成し、Funcメソッドに与えています。Funcメソッドの戻り値はint型のリストになります。 Funcの戻り値のリストをfor eachループでテキストボックスに表示します。
  List<int> valueList = new List<int>() { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
  List<int> resultList = Func(valueList);
  foreach (int r in resultList)
  {
    textBox1.Text += r.ToString() + ",";
  }

Funcメソッドはint型のリストを受け取ります。
戻り値のint型のリストを作成し、foreachループで入力したリストの要素をひとつづつ確認し、偶数の場合は、戻り値のリストに値を挿入しています。
入力したリストの中から偶数の値を返す動作になります。
  private List<int> Func(List<int> input){
    List<int> result = new List<int>();

    foreach (int v in input)
    {
      if (v % 2 == 0)
      {
        result.Add(v);
      }
    }
    return result;
  }

実行結果

プロジェクトを実行します。下図のウィンドウが表示されます。


[button1]をクリックします。Funcメソッドで処理された偶数の値のみがテキストボックスに表示されます。

メソッドの返却ロジックを逐次的にしたい

上記のプログラムですが、Funcメソッドで戻り値の List<int> オブジェクトを作成し作成したリストに結果をAddしたのち、 リストをreturn文で返す動作になっています。
従来のプログラムスタイルでは違和感のないコードですが、結果を返すためだけの List<int> オブジェクトの作成と値のAddは省略したいです。 yield return キーワードを利用すると、戻り値のためのリストの作成や挿入処理を使わずに同じ処理が実現できます。
Listを返すFuncメソッド
  private List<int> Func(List<int> input){
    List<int> result = new List<int>();

    foreach (int v in input)
    {
      if (v % 2 == 0)
      {
        result.Add(v);
      }
    }
    return result;
  }

yield return のプログラム例

yield return を利用したコードを紹介します。

UI

下図のフォームを作成します。先ほどのプログラムのフォームに button2 を追加します。

コード

以下のコードを記述します。(button1のコードは省略しています)
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace YieldReturnDemo
{
  public partial class FormSimpleYieldReturn : Form
  {
    public FormSimpleYieldReturn()
    {
      InitializeComponent();
    }

    private void button2_Click(object sender, EventArgs e)
    {
      List<int> valueList = new List<int>() { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };

      IEnumerable<int> resultList = FuncYield(valueList);

      foreach (int r in resultList)
      {
        textBox1.Text += r.ToString() + ",";
      }
    }

    private IEnumerable<int> FuncYield(List<int> input)
    {
      List<int> result = new List<int>();

      foreach (int v in input)
      {
        if (v % 2 == 0)
        {
          yield return v;
        }
      }
    }
  }
}

解説

button2では、入力元のリストを作成し、FuncYieldメソッドに与えています。FuncYieldメソッドの戻り値の型はIEnumerable<int>になります。 Funcの戻り値のIEnumerableオブジェクトをfor eachループでテキストボックスに表示します。
  List<int> valueList = new List<int>() { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
  IEnumerable<int> resultList = FuncYield(valueList);

  foreach (int r in resultList)
  {
    textBox1.Text += r.ToString() + ",";
  }

FuncYieldメソッドはint型のリストを受け取ります。
yield return文を利用するため、戻り値を格納するIEnumerableオブジェクトは作成しません。 foreachループで入力したリストの要素をひとつづつ確認し、偶数の場合は、yield return 文を利用し値を返します。
通常のreturn 文であればメソッドを抜けてしまいますが、yield return 文の場合はメソッドは抜けずに次の処理を実行します。
複数回 yield return 文が実行され、複数の戻り値が、IEnumerableオブジェクトに設定された状態でメソッドの戻り値になります。
  private IEnumerable<int> FuncYield(List<int> input)
  {
    List<int> result = new List<int>();
    foreach (int v in input)
    {
      if (v % 2 == 0)
      {
        yield return v;
      }
    }
  }

実行結果

上記のプロジェクトを実行します。下図のウィンドウが表示されます。


[button2]をクリックします。FuncYieldメソッドにより、元のリストの値から偶数のみを抜き出した値がテキストボックスに表示されます。


yield reutrn 文を利用したコードを紹介しました。

処理が実行される順番

先のプログラム例でコレクションを逐次処理で返せる動作が確認できましたが、処理が実行される順番を確認するとコレクションをまとめて返却する動作とは微妙に違うことがわかります。
次のプログラムを作成して、処理が実行される順番を確認します。

UI

下図のフォームを作成します。

コード

下記コードを記述します。
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace YieldReturnDemo
{
  public partial class FormYieldReturnOrder : Form
  {
    public FormYieldReturnOrder()
    {
      InitializeComponent();
    }

    private void button1_Click(object sender, EventArgs e)
    {
      textBox1.Text += "Start: ButtonClick\r\n";
      List<int> valueList = new List<int>() { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };

      IEnumerable<int> resultList = FuncYield(valueList);

      foreach (int r in resultList)
      {
        textBox1.Text += r.ToString() + "\r\n";
      }
      textBox1.Text += "End: ButtonClick\r\n";
    }

    private IEnumerable<int> FuncYield(List<int> input)
    {
      textBox1.Text += "Start:FuncYield\r\n";
      List<int> result = new List<int>();

      foreach (int v in input)
      {
        if (v % 2 == 0)
        {
          textBox1.Text += String.Format("yield return:{0:d}\r\n",v);
          yield return v;
        }
      }
      textBox1.Text += "End:FuncYield\r\n";
    }
  }
}

解説

先ほどのプログラムと同様のコードですが、各所にテキストボックスへのメッセージ出力のコードが追加されています。

実行結果

プロジェクトを実行します。下図のウィンドウが表示されます。


[button1]をクリックすると結果とメッセージが表示されます。


出力メッセージを確認すると、次の順番で処理が実行されることが確認できます。
  • Button Click メソッドの開始
  • FuncYield メソッドの呼び出し
  • FuncYield メソッドが2を返す
  • Button Click メソッドに戻りテキストボックスに2を表示
  • FuncYield メソッドが4を返す
  • Button Click メソッドに戻りテキストボックスに4を表示
  • FuncYield メソッドが6を返す
  • Button Click メソッドに戻りテキストボックスに6を表示
  • FuncYield メソッドが8を返す
  • Button Click メソッドに戻りテキストボックスに8を表示
  • FuncYield メソッドが10を返す
  • Button Click メソッドに戻りテキストボックスに10を表示
  • FuncYield メソッドが終了
  • Button Click メソッドが終了

一般的なプログラムの順序と違う順番で処理が実行される点に注意が必要です。
結果
Start: ButtonClick
Start:FuncYield
yield return:2
2
yield return:4
4
yield return:6
6
yield return:8
8
yield return:10
10
End:FuncYield
End: ButtonClick
著者
iPentecのメインプログラマー
C#, ASP.NET の開発がメイン、少し前まではDelphiを愛用
最終更新日: 2022-02-05
作成日: 2022-01-13
iPentec all rights reserverd.