今回は、SelectMany拡張メソッドについてです。
SelectMany拡張メソッドは、Select拡張メソッドと同じように
特定のシーケンスに対して射影を行いますが、最後に結果のシーケンスを
平坦化して返してくれるメソッドです。
なんか文字で書くと、書きづらいのですので、実際のクエリ定義と交えて説明します。
以下のような、データクラスが存在するとします。
class Team { public string Name { get; set; } public IEnumerable<string> Members { get; set; } }
上記のクラスは、チームメンバの名前としてIEnumerable
これを、
from team in teams
select team.Members;
という風に、クエリ式を定義して取得するとクエリ結果は
IEnumerable<IEnumerable<string>>
となります。Membersプロパティの戻り値がIEnumerable
そのとおりですね。
で、フラットな形で取得するには、以下のようにクエリを定義します。
from team in teams from member in team.Members select member;
上記のクエリの場合、2段階でfromを利用して各メンバの段階まで
抽出をしているので、クエリ結果は、
IEnumerable<string>
となります。SelectMany拡張メソッドは、まさに上記のクエリのように
IEnumerable<IEnumerable<string>>
となっているような構造を、
IEnumerable<string>
のようにフラットな状態にしてシーケンスを返してくれます。
実は、LINQではクエリを定義する際にfromを2つ以上利用している場合
クエリの最後にて結果をSelectする部分では、SelectMany拡張メソッドに
置換されます。(fromが一つの場合は、Select拡張メソッドが利用されます。)
なので、上記のクエリ
from team in teams from member in team.Members select member;
は以下のものと同じです。
teams.SelectMany(team => team.Members);
SelectMany拡張メソッドは、4つのオーバーロードを持っており
以下の書式となっています。
public static IEnumerable<TResult> SelectMany<TSource, TResult>( this IEnumerable<TSource> source, Func<TSource, IEnumerable<TResult>> selector )
public static IEnumerable<TResult> SelectMany<TSource, TCollection, TResult>( this IEnumerable<TSource> source, Func<TSource, IEnumerable<TCollection>> collectionSelector, Func<TSource, TCollection, TResult> resultSelector )
public static IEnumerable<TResult> SelectMany<TSource, TResult>( this IEnumerable<TSource> source, Func<TSource, int, IEnumerable<TResult>> selector )
public static IEnumerable<TResult> SelectMany<TSource, TCollection, TResult>( this IEnumerable<TSource> source, Func<TSource, int, IEnumerable<TCollection>> collectionSelector, Func<TSource, TCollection, TResult> resultSelector )
4つオーバーロードが存在しますが、大別すると2パターンです。
selectorを指定してシーケンスを取得するものと、 collectionSelectorとresultSelectorを指定して
シーケンスを取得するものです。
後は、それぞれのパターンに対して、シーケンス内のインデックスを引数に追加してくれる
ものがオーバーライドとして用意されています。
処理によっては、インデックス値が必要な場合もありますので、その時に
このオーバーロードを利用します。
collectionSelectorとresultSelectorの2つを指定するオーバーロードの場合、
collectionSelectorにて、必要な要素を決定し、さらに結果を構築する際に元のシーケンスと
抽出されたデータを元に、最終結果を作成する場合などに利用します。
サンプルの中に、シンプルなパターンを書いていますのでご参照下さい。
以下、サンプルです。
#region LinqSamples-21 public class LinqSamples21 : IExecutable { class Team { public string Name { get; set; } public IEnumerable<string> Members { get; set; } } public void Execute() { var teams = new List<Team> { new Team { Name = "Team A", Members = new List<string>{ "gsf_zero1", "gsf_zero2" } }, new Team { Name = "Team B", Members = new List<string>{ "gsf_zero3", "gsf_zero4" } } }; // // SelectMany拡張メソッドは、コレクションを平坦化する際に利用できる。 // 例えば、上記で作成したteams変数は以下のような構造になっている。 // // teams -- [0]:Teamオブジェクト // └Members:IEnumerable<string> // [1]:Teamオブジェクト // └Members:IEnumerable<string> // // 各Teamオブジェクトは、Membersプロパティを持っているので // SelectManyではなく、Select拡張メソッドを利用して // teams.Select(team => team.Members) // とすると、結果は、IEnumerable<IEnumerable<string>>となる。 // // このような状態で、SelectMany拡張メソッドを利用して // teams.SelectMany(team => team.Members) // とすると、結果は、IEnumerable<string>となる。 // つまり、SelectMany拡張メソッドは、各Selectorが返すシーケンスを最終的に // 平坦化してから結果を返してくれる。 // // 尚、SelectMany拡張メソッドは、クエリ式にて2段以上のfrom句を利用している場合 // 暗黙的に利用されている。上記のteams.SelectMany(team => team.Members)は // 以下のクエリ式と同じである。 // // from team in teams // from member in team.Members // select member // // 実行時には、最後のselectの部分がSelectManyに置換される。 // Console.WriteLine("===== Func<TSource, IEnumerable<TResult>>のサンプル ====="); foreach (var member in teams.SelectMany(team => team.Members)) { Console.WriteLine(member); } Console.WriteLine("===== Func<TSource, int, IEnumerable<TResult>>のサンプル ====="); foreach (var member in teams.SelectMany((team, index) => (index % 2 == 0) ? team.Members : new List<string>())) { Console.WriteLine(member); } Console.WriteLine("===== collectionSelectorとresultSelectorを利用しているサンプル (1) ====="); var query = teams.SelectMany ( team => team.Members, // collectionSelector (team, member) => new { Team = team.Name, Name = member } // resultSelector ); foreach (var item in query) { Console.WriteLine(item); } Console.WriteLine("===== collectionSelectorとresultSelectorを利用しているサンプル (2) ====="); var query2 = teams.SelectMany ( (team, index) => (index % 2 != 0) ? team.Members : new List<string>(), // collectionSelector (team, member) => new { Team = team.Name, Name = member } // resultSelector ); foreach (var item in query2) { Console.WriteLine(item); } } } #endregion
出力結果は、以下のようになります。
===== Func>のサンプル ===== gsf_zero1 gsf_zero2 gsf_zero3 gsf_zero4 ===== Func >のサンプル ===== gsf_zero1 gsf_zero2 ===== collectionSelectorとresultSelectorを利用しているサンプル (1) ===== { Team = Team A, Name = gsf_zero1 } { Team = Team A, Name = gsf_zero2 } { Team = Team B, Name = gsf_zero3 } { Team = Team B, Name = gsf_zero4 } ===== collectionSelectorとresultSelectorを利用しているサンプル (2) ===== { Team = Team B, Name = gsf_zero3 } { Team = Team B, Name = gsf_zero4 }
================================
過去の記事については、以下のページからご参照下さい。
- いろいろ備忘録日記まとめ
サンプルコードは、以下の場所で公開しています。
- いろいろ備忘録日記サンプルソース置き場