Enumerable.Join メソッド/ List<T>.Join メソッドを利用して IEnumerableインターフェイスを持つオブジェクトを結合する - C#

Enumerable.​Join メソッドを利用して、2つのEnumerable オブジェクトの要素の結合をするコードを紹介します。

概要

Listなど、一つのオブジェクトに複数の値が格納される Collection型の2つのオブジェクトの結合をしたいことがあります。オーソドックスな方法としては、forループやforeachループを利用して突合せ処理をする方法があります。通常はシンプルな処理を利用すれば問題ありませんが、適用させる処理のロジックを使いまわす場合や、よりコードをシンプルに記述したいこともあります。
Enumerable オブジェクトの Join メソッドを利用することでループ処理を無くしたシンプルなコードを記述できます。

プログラム例

今回は IEnumerable インターフェイスを実装しているListクラスを利用してJoin メソッドを利用するサンプルを紹介します。

UI

下図のUIを作成します。複数行のテキストボックスにボタンを5つ配置します。

コード

下記のコードを記述します。
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 JoinDemo
{
  public partial class FormListJoin : Form
  {
    public class Item
    {
      public string name;
      public string code;
    }

    public class PriceItem
    {
      public string code;
      public int price;
    }

    public class JoinItem
    {
      public string name;
      public string code;
      public int price;
    }

    public FormListJoin()
    {
      InitializeComponent();
    }

    private List<Item> initItemList()
    {
      List<Item> ItemList = new List<Item>();
      Item ii;
      ii = new Item();
      ii.name = "ぺんぎんクッキー";
      ii.code = "P-001";
      ItemList.Add(ii);

      ii = new Item();
      ii.name = "らくだキャラメル";
      ii.code = "P-002";
      ItemList.Add(ii);

      ii = new Item();
      ii.name = "しろくまアイス";
      ii.code = "P-003";
      ItemList.Add(ii);

      ii = new Item();
      ii.name = "あひるサブレ";
      ii.code = "P-004";
      ItemList.Add(ii);

      ii = new Item();
      ii.name = "くじらプリン";
      ii.code = "P-005";
      ItemList.Add(ii);

      return ItemList;
    }

    private List<PriceItem> initPriceList()
    {
      List<PriceItem> PriceList = new List<PriceItem>();
      PriceItem pi;
      pi = new PriceItem();
      pi.code = "P-004";
      pi.price = 540;
      PriceList.Add(pi);

      pi = new PriceItem();
      pi.code = "P-001";
      pi.price = 320;
      PriceList.Add(pi);

      pi = new PriceItem();
      pi.code = "P-005";
      pi.price = 160;
      PriceList.Add(pi);

      return PriceList;
    }

    private void button1_Click(object sender, EventArgs e)
    {
      List<Item> ItemList = initItemList();
      List<PriceItem> PriceList = initPriceList();

      IEnumerable <JoinItem> ResultList = ItemList.Join(PriceList, outerKeySelector_proc, innerKeySelectorProc, resultSelector_proc);

      List<JoinItem> ProcList = ResultList.ToList<JoinItem>();

      foreach (JoinItem t in ProcList) {
        textBox1.Text += "Name: " + t.name + "\r\n";
        textBox1.Text += "Code: " + t.code + "\r\n";
        textBox1.Text += "Price: " + t.price.ToString() + "\r\n";
        textBox1.Text += "----- \r\n";
      }

    }

    private string outerKeySelector_proc(Item value)
    {
      return value.code;
    }

    private string innerKeySelectorProc(PriceItem value)
    {
      return value.code;
    }

    private JoinItem resultSelector_proc(Item ItemValue, PriceItem PriceItemValue)
    {
      JoinItem ji = new JoinItem();
      ji.name = ItemValue.name;
      ji.code = ItemValue.code;
      ji.price = PriceItemValue.price;

      return ji;
    }

    private void button2_Click(object sender, EventArgs e)
    {
      List<Item> ItemList = initItemList();
      List<PriceItem> PriceList = initPriceList();

      IEnumerable<JoinItem> ResultList = ItemList.Join(
        PriceList,
        delegate (Item value) {
          return value.code;
        },
        delegate (PriceItem value) {
          return value.code;
        },
        delegate (Item ItemValue, PriceItem PriceItemValue) {
          JoinItem ji = new JoinItem();
          ji.name = ItemValue.name;
          ji.code = ItemValue.code;
          ji.price = PriceItemValue.price;
          return ji;
        });

      List<JoinItem> ProcList = ResultList.ToList<JoinItem>();

      foreach (JoinItem t in ProcList) {
        textBox1.Text += "Name: " + t.name + "\r\n";
        textBox1.Text += "Code: " + t.code + "\r\n";
        textBox1.Text += "Price: " + t.price.ToString() + "\r\n";
        textBox1.Text += "----- \r\n";
      }
    }

    private void button3_Click(object sender, EventArgs e)
    {
      List<Item> ItemList = initItemList();
      List<PriceItem> PriceList = initPriceList();

      IEnumerable<JoinItem> ResultList = ItemList.Join(
        PriceList,
        (Item value) => {
          return value.code;
        },
        (PriceItem value) => {
          return value.code;
        },
        (Item ItemValue, PriceItem PriceItemValue) => {
          JoinItem ji = new JoinItem();
          ji.name = ItemValue.name;
          ji.code = ItemValue.code;
          ji.price = PriceItemValue.price;
          return ji;
        });

      List<JoinItem> ProcList = ResultList.ToList<JoinItem>();

      foreach (JoinItem t in ProcList) {
        textBox1.Text += "Name: " + t.name + "\r\n";
        textBox1.Text += "Code: " + t.code + "\r\n";
        textBox1.Text += "Price: " + t.price.ToString() + "\r\n";
        textBox1.Text += "----- \r\n";
      }
    }

    private void button4_Click(object sender, EventArgs e)
    {
      List<Item> ItemList = initItemList();
      List<PriceItem> PriceList = initPriceList();

      IEnumerable<JoinItem> ResultList = ItemList.Join(
        PriceList,
        (Item value) => value.code,
        (PriceItem value) => value.code,
        (Item ItemValue, PriceItem PriceItemValue) =>  new JoinItem {
          name = ItemValue.name,
          code = ItemValue.code,
          price = PriceItemValue.price
        }
      );
        
      List<JoinItem> ProcList = ResultList.ToList<JoinItem>();

      foreach (JoinItem t in ProcList) {
        textBox1.Text += "Name: " + t.name + "\r\n";
        textBox1.Text += "Code: " + t.code + "\r\n";
        textBox1.Text += "Price: " + t.price.ToString() + "\r\n";
        textBox1.Text += "----- \r\n";
      }
    }

    private void button5_Click(object sender, EventArgs e)
    {
      List<Item> ItemList = initItemList();
      List<PriceItem> PriceList = initPriceList();

      IEnumerable<JoinItem> ResultList = from value_item in ItemList join value_price in PriceList on value_item.code equals value_price.code select new JoinItem { name = value_item.name, code = value_item.code, price = value_price.price };

      List<JoinItem> ProcList = ResultList.ToList<JoinItem>();

      foreach (JoinItem t in ProcList) {
        textBox1.Text += "Name: " + t.name + "\r\n";
        textBox1.Text += "Code: " + t.code + "\r\n";
        textBox1.Text += "Price: " + t.price.ToString() + "\r\n";
        textBox1.Text += "----- \r\n";
      }
    }
  }
}

実行結果と解説

initItemList(), initPriceList()の2つのメソッドは結合する元のリストを作成するメソッドになります。メソッドの戻り値で初期化され値が設定されたリストを戻す動作になります。

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

button1

[button1]をクリックすると下記のコードが実行されます。
先頭部分(initItemList(), initPriceList() メソッド)はListオブジェクトを作成しリストに値を追加するコードです。値の追加後に、Join() メソッドを呼び出します。
Joinメソッドの第一引数には結合先のEnumerable オブジェクトを与えます。第二引数には、結合先のオブジェクトのキーを選択するメソッドを与えます。第三引数は結合元のオブジェクトのキーを選択するメソッドを与えます。第四引数は結果を返すメソッドを与えます。
オブジェクトの結合結果はJoinメソッドの戻り値で帰ります。結果をforeach ループでテキストボックスに表示します。
    private void button1_Click(object sender, EventArgs e)
    {
      List<Item> ItemList = initItemList();
      List<PriceItem> PriceList = initPriceList();

      IEnumerable <JoinItem> ResultList = ItemList.Join(PriceList, outerKeySelector_proc, innerKeySelectorProc, resultSelector_proc);

      List<JoinItem> ProcList = ResultList.ToList<JoinItem>();

      foreach (JoinItem t in ProcList) {
        textBox1.Text += "Name: " + t.name + "\r\n";
        textBox1.Text += "Code: " + t.code + "\r\n";
        textBox1.Text += "Price: " + t.price.ToString() + "\r\n";
        textBox1.Text += "----- \r\n";
      }
    }

キーを選択するメソッドは下記になります。
今回は、PriceItemクラスのcode の値と、Itemクラスのcode の値が一致しているものが結合の対象となりますので、outerKeySelector_proc では結合元のItemクラスのcodeを戻り値として返します。innerKeySelectorProc では結合先のPriceItem クラスのcodeを戻り値とします。
    private string outerKeySelector_proc(Item value)
    {
      return value.code;
    }

    private string innerKeySelectorProc(PriceItem value)
    {
      return value.code;
    }

Joinで結合条件に一致したオブジェクトに対する結果の返却メソッドです。今回、結合の結果はJoinItem クラスの Enuberable オブジェクトで返されるため、resultSelector_proc() メソッドの戻り値は JoinItem クラスのオブジェクトを返します。new により、JoinItem クラスのオブジェクトを作成し、各メンバ変数に値を代入したものをメソッドの戻り値としています。
    private JoinItem resultSelector_proc(Item ItemValue, PriceItem PriceItemValue)
    {
      JoinItem ji = new JoinItem();
      ji.name = ItemValue.name;
      ji.code = ItemValue.code;
      ji.price = PriceItemValue.price;

      return ji;
    }

プロジェクトを実行し、[button1]をクリックします。テキストボックスに下図の結果が表示されます。ItemList と PriceList の両方に同じcodeがある項目について2つのListを結合した情報が表示されます。

button2

[button2]クリック時には下記のコードが実行されます。[button1]のコードとの違いは、button1ではJoinメソッドの引数にouterKeySelector_proc(), innerKeySelectorProc(), resultSelector_proc() のメソッドを与えましたが、button2ではメソッドを与えずに、Joinメソッドの引数に匿名メソッドを記述しています。匿名メソッドを利用すると、メソッドの実装を別のメソッドに分けた記述が不要になります。
    private void button2_Click(object sender, EventArgs e)
    {
      List<Item> ItemList = initItemList();
      List<PriceItem> PriceList = initPriceList();

      IEnumerable<JoinItem> ResultList = ItemList.Join(
        PriceList,
        delegate (Item value) {
          return value.code;
        },
        delegate (PriceItem value) {
          return value.code;
        },
        delegate (Item ItemValue, PriceItem PriceItemValue) {
          JoinItem ji = new JoinItem();
          ji.name = ItemValue.name;
          ji.code = ItemValue.code;
          ji.price = PriceItemValue.price;
          return ji;
        });

      List<JoinItem> ProcList = ResultList.ToList<JoinItem>();

      foreach (JoinItem t in ProcList) {
        textBox1.Text += "Name: " + t.name + "\r\n";
        textBox1.Text += "Code: " + t.code + "\r\n";
        textBox1.Text += "Price: " + t.price.ToString() + "\r\n";
        textBox1.Text += "----- \r\n";
      }
    }

プロジェクトを実行し、[button2]をクリックします。テキストボックスに下図の結果が表示されます。[button1]をクリックしたときと同様の結果となります。

button3

[button3]のコードは下記になります。button2のコードでは、Join() メソッドに匿名メソッドを与えていましたが、ラムダ式での記述に書き換えています。
    private void button3_Click(object sender, EventArgs e)
    {
      List<Item> ItemList = initItemList();
      List<PriceItem> PriceList = initPriceList();

      IEnumerable<JoinItem> ResultList = ItemList.Join(
        PriceList,
        (Item value) => {
          return value.code;
        },
        (PriceItem value) => {
          return value.code;
        },
        (Item ItemValue, PriceItem PriceItemValue) => {
          JoinItem ji = new JoinItem();
          ji.name = ItemValue.name;
          ji.code = ItemValue.code;
          ji.price = PriceItemValue.price;
          return ji;
        });

      List<JoinItem> ProcList = ResultList.ToList<JoinItem>();

      foreach (JoinItem t in ProcList) {
        textBox1.Text += "Name: " + t.name + "\r\n";
        textBox1.Text += "Code: " + t.code + "\r\n";
        textBox1.Text += "Price: " + t.price.ToString() + "\r\n";
        textBox1.Text += "----- \r\n";
      }
    }

プロジェクトを実行し、[button3]をクリックします。テキストボックスに下図の結果が表示されます。button1やbutton2をクリックしたときと同様の結果となります。

button4

[button4]がクリックされると下記のコードが実行されます。button3のコードのラムダ式はステートメント形式のラムダ式でしたが、これを式形式のラムダ式に書き換えています。
    private void button4_Click(object sender, EventArgs e)
    {
      List<Item> ItemList = initItemList();
      List<PriceItem> PriceList = initPriceList();

      IEnumerable<JoinItem> ResultList = ItemList.Join(
        PriceList,
        (Item value) => value.code,
        (PriceItem value) => value.code,
        (Item ItemValue, PriceItem PriceItemValue) =>  new JoinItem {
          name = ItemValue.name,
          code = ItemValue.code,
          price = PriceItemValue.price
        }
      );
        
      List<JoinItem> ProcList = ResultList.ToList<JoinItem>();

      foreach (JoinItem t in ProcList) {
        textBox1.Text += "Name: " + t.name + "\r\n";
        textBox1.Text += "Code: " + t.code + "\r\n";
        textBox1.Text += "Price: " + t.price.ToString() + "\r\n";
        textBox1.Text += "----- \r\n";
      }
    }

プロジェクトを実行し、[button4]をクリックします。テキストボックスに下図の結果が表示されます。button1やbutton2をクリックしたときと同様の結果となります。

補足
結果を保存するクラスを匿名オブジェクトにした場合のコードは下記になります。
    private void button4_Click(object sender, EventArgs e)
    {
      List<Item> ItemList = initItemList();
      List<PriceItem> PriceList = initPriceList();

      var ResultList = ItemList.Join(
        PriceList,
        (Item value) => value.code,
        (PriceItem value) => value.code,
        (Item ItemValue, PriceItem PriceItemValue) => new
        {
          name = ItemValue.name,
          code = ItemValue.code,
          price = PriceItemValue.price
        }
      );

      foreach (var t in ResultList) {
        textBox1.Text += "Name: " + t.name + "\r\n";
        textBox1.Text += "Code: " + t.code + "\r\n";
        textBox1.Text += "Price: " + t.price.ToString() + "\r\n";
        textBox1.Text += "----- \r\n";
      }
    }

button5

[button5]をクリックした際に実行されるコードは下記になります。このコードは、Joinメソッドの呼び出しをメソッドの呼び出し形式で記述する方式からLINQの記述に変更したコードになります。join 節の後ろに記述したラムダ式が結合条件の式となります。また、結果となる変数をselect節で指定しています。LINQについてはこちらの記事を参照してください。
    private void button5_Click(object sender, EventArgs e)
    {
      List<Item> ItemList = initItemList();
      List<PriceItem> PriceList = initPriceList();

      IEnumerable<JoinItem> ResultList = from value_item in ItemList join value_price in PriceList on value_item.code equals value_price.code select new JoinItem { name = value_item.name, code = value_item.code, price = value_price.price };

      List<JoinItem> ProcList = ResultList.ToList<JoinItem>();

      foreach (JoinItem t in ProcList) {
        textBox1.Text += "Name: " + t.name + "\r\n";
        textBox1.Text += "Code: " + t.code + "\r\n";
        textBox1.Text += "Price: " + t.price.ToString() + "\r\n";
        textBox1.Text += "----- \r\n";
      }
    }

プログラムを実行し[button5]をクリックします。[button1]~[button5]まで同じ処理であることがわかります。

補足
結果を保存するクラスを匿名オブジェクトにした場合のコードは下記になります。
    private void button5_Click(object sender, EventArgs e)
    {
      List<Item> ItemList = initItemList();
      List<PriceItem> PriceList = initPriceList();

      var ResultList = from value_item in ItemList join value_price in PriceList on value_item.code equals value_price.code select new { name = value_item.name, code = value_item.code, price = value_price.price };

      foreach (var t in ResultList) {
        textBox1.Text += "Name: " + t.name + "\r\n";
        textBox1.Text += "Code: " + t.code + "\r\n";
        textBox1.Text += "Price: " + t.price.ToString() + "\r\n";
        textBox1.Text += "----- \r\n";
      }
    }
著者
iPentecのメインプログラマー
C#, ASP.NET の開発がメイン、少し前まではDelphiを愛用
掲載日: 2018-10-09
iPentec all rights reserverd.