C#のLINQの関数であるJoin()
の使い方についてです。
別の情報リストとデータを結合することが出来ます。
この記事には.NET Framework 4.6.1を使用しています。
データを結合するぞ
LINQのJoin()
を使うことでデータを結合することができます。
……と言われても、結合ってなんのことさ!
ではJoin()
の結合を学ぶために、二つのクラスを作ってみました。
Program.cs
private class PersonData { public string Name { get; set; } public int ItemID { get; set; } public override string ToString() { return string.Format( "Name:{0}, ItemID:{1}", Name, ItemID ); } }
Program.cs
private class ItemData { public int ID { get; set; } public string Name { get; set; } public override string ToString() { return string.Format( "ID:{0}, Name:{1}", ID, Name ); } }
PersonData
は人の情報クラス。
名前とアイテムIDを持っています。
ItemData
はアイテムの情報クラス。
IDと名前を持っています。
PersonData
の持つアイテムIDと、ItemData
の持つIDは同じものを指しています。
例えば、ItemData
のIDが[3]の名前が玉子焼きだった場合、
アイテムID[3]を持つ人は、玉子焼きを持っていることになります。
PersonData
が配列でいっぱいあり、そしてItemData
も配列でいっぱいあるとき、
各PersonData
のアイテムIDとItemData
のIDをいい感じに紐づけたいとき。
そんなときにJoin()
を使えばいい感じにしてくれるのです。
public static IEnumerable<TResult> Join<TOuter, TInner, TKey, TResult>( this IEnumerable<TOuter> outer, IEnumerable<TInner> inner, Func<TOuter, TKey> outerKeySelector, Func<TInner, TKey> innerKeySelector, Func<TOuter, TInner, TResult> resultSelector );
https://msdn.microsoft.com/ja-jp/library/bb534675.aspx
Join()
は引数が多いので、ちと面倒くさいです。
第一引数に、紐づけたい情報の配列やリストを。
第二引数に、自分自身の紐づけるプロパティの取得処理を。
第三引数に、紐づけたい情報の配列やリストの紐づけるプロパティの取得処理を。
第四引数に、紐づけられた情報に対して行う処理を。
かけばOKです。
多分、うまく説明できていない気がするので、コードをみて確認してみてください。
Program.cs
using System.Linq; using System.Collections; using System.Collections.Generic; public static class Program { private class PersonData { public string Name { get; set; } public int ItemID { get; set; } public override string ToString() { return string.Format( "Name:{0}, ItemID:{1}", Name, ItemID ); } } private class ItemData { public int ID { get; set; } public string Name { get; set; } public override string ToString() { return string.Format( "ID:{0}, Name:{1}", ID, Name ); } } private class JoinData { public PersonData Person { get; set; } public ItemData Item { get; set; } public override string ToString() { return string.Format( "Person:{{ {0} }}, Item:{{ {1} }}", Person, Item ); } } static void Main( string[] args ) { // 人物データ PersonData[] persons = new PersonData[] { new PersonData() { Name = "正一郎", ItemID = 0 }, new PersonData() { Name = "清次郎", ItemID = 1 }, new PersonData() { Name = "誠三郎", ItemID = 2 }, new PersonData() { Name = "征史郎", ItemID = 0 }, }; // アイテムデータ ItemData[] items = new ItemData[] { new ItemData() { ID = 0, Name = "金" }, new ItemData() { ID = 1, Name = "権力" }, }; // 人物データのItemIDとアイテムデータのIDが同じものを結合する。 IEnumerable<JoinData> joinList = persons.Join( items, person => person.ItemID, item => item.ID, ( person, item ) => new JoinData() { Person = person, Item = item } ); // 結果発表 System.Console.WriteLine( "persons:{0}", persons.Text() ); System.Console.WriteLine( "items :{0}", items.Text() ); System.Console.WriteLine( "join :{0}", joinList.Text() ); // 入力待ち用 System.Console.ReadKey(); } /// <summary> /// 簡易的なコレクションのテキスト取得処理 /// </summary> public static string Text( this IEnumerable i_source ) { string text = string.Empty; foreach( var value in i_source ) { text += string.Format( "[{0}], ", value ); } return text; } } // class Program
persons:[Name:正一郎, ItemID:0], [Name:清次郎, ItemID:1], [Name:誠三郎, ItemID:2], [Name:征史郎, ItemID:0],
items :[ID:0, Name:金], [ID:1, Name:権力],
join :[Person:{ Name:正一郎, ItemID:0 }, Item:{ ID:0, Name:金 }], [Person:{ Name:清次郎, ItemID:1 }, Item:{ ID:1, Name:権力 }], [Person:{ Name:征史郎, ItemID:0 }, Item:{ ID:0, Name:金 }],
上記のコードでは、紐づけた情報をJoinData
クラスにまとめています。
ですが、これぐらいの情報量なら匿名型で返してしまったほうが楽かもしれません。
Program.cs
using System.Linq; using System.Collections; using System.Collections.Generic; public static class Program { private class PersonData { public string Name { get; set; } public int ItemID { get; set; } public override string ToString() { return string.Format( "Name:{0}, ItemID:{1}", Name, ItemID ); } } private class ItemData { public int ID { get; set; } public string Name { get; set; } public override string ToString() { return string.Format( "ID:{0}, Name:{1}", ID, Name ); } } static void Main( string[] args ) { // 人物データ PersonData[] persons = new PersonData[] { new PersonData() { Name = "正一郎", ItemID = 0 }, new PersonData() { Name = "清次郎", ItemID = 1 }, new PersonData() { Name = "誠三郎", ItemID = 2 }, new PersonData() { Name = "征史郎", ItemID = 0 }, }; // アイテムデータ ItemData[] items = new ItemData[] { new ItemData() { ID = 0, Name = "金" }, new ItemData() { ID = 1, Name = "権力" }, }; // 人物データのItemIDとアイテムデータのIDが同じものを結合する。 var joinList = persons.Join( items, person => person.ItemID, item => item.ID, ( person, item ) => new { PersonName = person.Name, ItemName = item.Name } ); // 結果発表 System.Console.WriteLine( "persons:{0}", persons.Text() ); System.Console.WriteLine( "items :{0}", items.Text() ); { string text = string.Empty; foreach( var value in joinList ) { text += string.Format( "[Person:{0}, Item:{1}], ", value.PersonName, value.ItemName ); } System.Console.WriteLine( "join :{0}", text ); } // 入力待ち用 System.Console.ReadKey(); } /// <summary> /// 簡易的なコレクションのテキスト取得処理 /// </summary> public static string Text( this IEnumerable i_source ) { string text = string.Empty; foreach( var value in i_source ) { text += string.Format( "[{0}], ", value ); } return text; } } // class Program
persons:[Name:正一郎, ItemID:0], [Name:清次郎, ItemID:1], [Name:誠三郎, ItemID:2], [Name:征史郎, ItemID:0],
items :[ID:0, Name:金], [ID:1, Name:権力],
join :[Person:正一郎, Item:金], [Person:清次郎, Item:権力], [Person:征史郎, Item:金],
比較処理を指定する時
さてここで、先ほど使ったPersonData
とItemData
を変えてみます。
Program.cs
private class PersonData { public string Name { get; set; } public ItemData Item { get; set; } public override string ToString() { return string.Format( "Name:{0}, Item:{1}", Name, Item ); } }
Program.cs
private class ItemData { public string Name { get; set; } public override string ToString() { return string.Format( "{0}", Name ); } }
各自IDをなくし、PersonData
が直接ItemData
を持つ形に変更しました。
この状態で、同じItemData
を持つPersonData
を、相方として結合する処理を書いてみましょう。
Program.cs
using System.Linq; using System.Collections; using System.Collections.Generic; public static class Program { private class PersonData { public string Name { get; set; } public ItemData Item { get; set; } public override string ToString() { return string.Format( "Name:{0}, Item:{1}", Name, Item ); } } private class ItemData { public string Name { get; set; } public override string ToString() { return string.Format( "{0}", Name ); } } static void Main( string[] args ) { // 人物データ PersonData[] persons = new PersonData[] { new PersonData() { Name = "正一郎", Item = new ItemData() { Name = "金" } }, new PersonData() { Name = "清次郎", Item = new ItemData() { Name = "権力" } }, new PersonData() { Name = "誠三郎", Item = new ItemData() { Name = "人望" } }, new PersonData() { Name = "征史郎", Item = new ItemData() { Name = "金" } }, }; // 相方データ PersonData[] partners = new PersonData[] { new PersonData() { Name = "A", Item = new ItemData() { Name = "権力" } }, new PersonData() { Name = "B", Item = new ItemData() { Name = "金" } }, new PersonData() { Name = "C", Item = new ItemData() { Name = "熱き正義の心" } }, }; // 人物データと相方データで同じItemを持っているものを結合する。 var joinList = persons.Join( partners, person => person.Item, partner => partner.Item, ( person, partner ) => new { PersonName = person.Name, PartnerName = partner.Name } ); // 結果発表 System.Console.WriteLine( "persons :{0}", persons.Text() ); System.Console.WriteLine( "partners:{0}", partners.Text() ); { string text = string.Empty; foreach( var value in joinList ) { text += string.Format( "[Person:{0}, `Partner:{1}], ", value.PersonName, value.PartnerName ); } System.Console.WriteLine( "join :{0}", text ); } // 入力待ち用 System.Console.ReadKey(); } /// <summary> /// 簡易的なコレクションのテキスト取得処理 /// </summary> public static string Text( this IEnumerable i_source ) { string text = string.Empty; foreach( var value in i_source ) { text += string.Format( "[{0}], ", value ); } return text; } } // class Program
persons :[Name:正一郎, Item:金], [Name:清次郎, Item:権力], [Name:誠三郎, Item:人望], [Name:征史郎, Item:金],
partners:[Name:A, Item:権力], [Name:B, Item:金], [Name:C, Item:熱き正義の心],
join :
結合したものが一つもない、という結果が返ってきました。
あまりにもダメなマッチングサイトです。
もちろんこれはマッチングサイトが悪いわけではなく、コードが悪いのです。
ItemData
はクラスなので参照型です。
そして、PersonData作成時には、各自new
を使ってインスタンス化しています。
これでは、中身のプロパティが同じ内容では、全て別のインスタンスになっているので、同じ要素扱いにはなりません。
各自new
するのではなく、別でインスタンス化したデータを使うことで解決できるのですが、今回はあくまで中身が同じなら同じ要素扱いになるように、比較処理を自作してみましょう。
比較処理はIEqualityComparer
を継承すれば作成することができます。
IEqualityComparer
IEqualityComparer(T) インターフェイス (System.Collections.Generic)
Program.cs
// ItemData比較用クラス private class ItemComparer : IEqualityComparer<ItemData> { public bool Equals( ItemData i_lhs, ItemData i_rhs ) { // Nameの文字列が同じどうかで判断する。 return i_lhs.Name == i_lhs.Name; } public int GetHashCode( ItemData i_item ) { // 本当はnullチェックしなきゃダメだよ……。 return i_item.Name.GetHashCode(); } }
この比較クラスをJoin
クラスに指定してあげればOKです。
public static IEnumerable<TResult> Join<TOuter, TInner, TKey, TResult>( this IEnumerable<TOuter> outer, IEnumerable<TInner> inner, Func<TOuter, TKey> outerKeySelector, Func<TInner, TKey> innerKeySelector, Func<TOuter, TInner, TResult> resultSelector, IEqualityComparer<TKey> comparer );
https://msdn.microsoft.com/ja-jp/library/bb549267.aspx
Program.cs
using System.Linq; using System.Collections; using System.Collections.Generic; public static class Program { private class PersonData { public string Name { get; set; } public ItemData Item { get; set; } public override string ToString() { return string.Format( "Name:{0}, Item:{1}", Name, Item ); } } private class ItemData { public string Name { get; set; } public override string ToString() { return string.Format( "{0}", Name ); } } // ItemData比較用クラス private class ItemComparer : IEqualityComparer<ItemData> { public bool Equals( ItemData i_lhs, ItemData i_rhs ) { // Nameの文字列が同じどうかで判断する。 return i_lhs.Name == i_lhs.Name; } public int GetHashCode( ItemData i_item ) { // 本当はnullチェックしなきゃダメだよ……。 return i_item.Name.GetHashCode(); } } static void Main( string[] args ) { // 人物データ PersonData[] persons = new PersonData[] { new PersonData() { Name = "正一郎", Item = new ItemData() { Name = "金" } }, new PersonData() { Name = "清次郎", Item = new ItemData() { Name = "権力" } }, new PersonData() { Name = "誠三郎", Item = new ItemData() { Name = "人望" } }, new PersonData() { Name = "征史郎", Item = new ItemData() { Name = "金" } }, }; // 相方データ PersonData[] partners = new PersonData[] { new PersonData() { Name = "A", Item = new ItemData() { Name = "権力" } }, new PersonData() { Name = "B", Item = new ItemData() { Name = "金" } }, new PersonData() { Name = "C", Item = new ItemData() { Name = "熱き正義の心" } }, }; // 人物データと相方データで同じItemを持っているものを結合する。 ItemComparer compare = new ItemComparer(); var joinList = persons.Join( partners, person => person.Item, partner => partner.Item, ( person, partner ) => new { PersonName = person.Name, PartnerName = partner.Name }, compare ); // 結果発表 System.Console.WriteLine( "persons :{0}", persons.Text() ); System.Console.WriteLine( "partners:{0}", partners.Text() ); { string text = string.Empty; foreach( var value in joinList ) { text += string.Format( "[Person:{0}, `Partner:{1}], ", value.PersonName, value.PartnerName ); } System.Console.WriteLine( "join :{0}", text ); } // 入力待ち用 System.Console.ReadKey(); } /// <summary> /// 簡易的なコレクションのテキスト取得処理 /// </summary> public static string Text( this IEnumerable i_source ) { string text = string.Empty; foreach( var value in i_source ) { text += string.Format( "[{0}], ", value ); } return text; } } // class Program
persons :[Name:正一郎, Item:金], [Name:清次郎, Item:権力], [Name:誠三郎, Item:人望], [Name:征史郎, Item:金],
partners:[Name:A, Item:権力], [Name:B, Item:金], [Name:C, Item:熱き正義の心],
join :[Person:正一郎,Partner:B], [Person:清次郎,
Partner:A], [Person:征史郎, `Partner:B],
こんどはきちんと同じ志を持つもの同士で結合することができました。
こんな感じにJoin()
を使って、七兆組ほどカップリングしてあげてください。
LINQのリンク
- LINQ一覧
www.urablog.xyz