【C#】SelectManyの使い方を確認してみる【LINQ】

久しぶりに SelectMany を使おうと思ったとき、すぐに使い方がピンと来なかったので備忘録として残しておきます。

どんなとき使うの?

このメソッドは、2つのIEnumerable<T>のシーケンス1つのIEnumerable<T>平坦化するときに使います。
とは言っても難しいと思うので今回は2つの例を出してみます。

例1: コレクションの中のコレクションの要素を取り出す

以下のようなコードがあります。

using System;
using System.Collections.Generic;

namespace CSharp
{
    public static class Program
    {
        public static void Main()
        {
            IEnumerable<Group> groups = new[]
            {
                new Group { Name = "Aグループ", Members = new[] { "Aさん", "Bさん", "Cさん" } },
                new Group { Name = "Bグループ", Members = new[] { "Dさん", "Eさん", "Fさん" } },
                new Group { Name = "Cグループ", Members = new[] { "Gさん", "Hさん", "Iさん" } }
            };

            Console.WriteLine("全グループの全メンバーを列挙します。");

            foreach (var group in groups)
            {
                foreach (var member in group.Members)
                {
                    Console.WriteLine(member);
                }
            }
        }
    }

    public class Group
    {
        public string Name { get; set; }

        public IEnumerable<string> Members { get; set; }
    }
}

この例では、全グループの全メンバーを列挙するための操作を考えてみます。

やりたいこと

さて、軽くやりたいことを書き出してみましょう。

この例1ではコレクションの中のコレクションの要素を取り出すということをすると言いましたが、
ソースのコレクションというのが Group型のコレクション である groups ですね。
で、その中のコレクションというのが 文字列型のコレクション である Members ですね。

つまり、groups から Membersプロパティ を取り出し、その要素を取り出すということをしたいわけです。

シグネチャを確認する

今回はSelectManyの使い方を確認するという記事ですので、当然SelectMany拡張メソッドを使うのですが、シグネチャをMSDNで確認しておきましょう。

public static IEnumerable<TResult> SelectMany<TSource, TResult>(this IEnumerable<TSource> source, Func<TSource, IEnumerable<TResult>> selector)

第二引数が Func<TSource, IEnumerable<TResult>> selector となっていますが、今回のコードの場合は、Func<Group, IEnumerable<string>> selector というようになります。

つまり、引数に変数groupの各要素 (Group型) を受け取り、戻り値でその中のコレクション (IEnumerable<string>型) を返すような関数を指定するということです。

実際のコード

以下が実際のコードになります。

Console.WriteLine("全グループの全メンバーを列挙します。");
var members = groups.SelectMany(group => group.Members);

foreach (var member in members)
{
    Console.WriteLine(member);
}

まぁ何も難しいことはないですね。
そのまんまです。

例2: 2つのコレクションから取り出す

お次はこのようなコードです。

using System;
using System.Collections.Generic;
using System.Linq;

namespace CSharp
{
    public static class Program
    {
        public static void Main()
        {
            IEnumerable<int> 掛けられる数の数列 = Enumerable.Range(1, 9);
            IEnumerable<int> 掛ける数の数列 = Enumerable.Range(1, 9);

            Console.WriteLine("九九を出力します。");

            foreach (var 掛けられる数 in 掛けられる数の数列)
            {
                foreach (var 掛ける数 in 掛ける数の数列)
                {
                    string result = $"{掛けられる数} x {掛ける数} = {掛けられる数 * 掛ける数}";
                    Console.WriteLine(result);
                }
            }
        }
    }
}

九九を出力したいと思います。

やりたいこと

さて、またやりたいことを整理してみましょう。

今回は 掛けられる数の数列掛ける数の数列 の2つのコレクション (IEnumerable<int>型) があります。
そして、この2つのコレクションから文字列を作るということをやります。

シグネチャを確認する

またまたSelectManyのシグネチャを確認します。
今回は別のオーバーロードを使います。
MSDNはこちらです。

public static IEnumerable<TResult> SelectMany<TSource, TCollection, TResult>(this IEnumerable<TSource> source, Func<TSource, IEnumerable<TCollection>> collectionSelector, Func<TSource, TCollection, TResult> resultSelector)

まず、第二引数は Func<TSource, IEnumerable<TCollection>> collectionSelector となっています。
この例2の場合は Func<int, IEnumerable<int>> collectionSelector ですね。

これは2つ目のコレクションを指定する関数を取ります。
この関数は引数に1つ目のコレクションの各要素を取るのですが、今回の場合、2つ目のコレクションである掛ける数の数列はこの引数に影響されないので必要ないでしょう。

次に、第三引数ですが、Func<TSource, TCollection, TResult> resultSelector となっています。
この例2の場合は Func<int, int, string> です。

これは1つ目のコレクションの要素、2つ目のコレクションの要素を引数に取って、変形した値を返す関数を取ります。

実際のコード

以下がコードです。

using System;
using System.Collections.Generic;
using System.Linq;

namespace CSharp
{
    public static class Program
    {
        public static void Main()
        {
            IEnumerable<int> 掛けられる数の数列 = Enumerable.Range(1, 9);
            IEnumerable<int> 掛ける数の数列 = Enumerable.Range(1, 9);

            Console.WriteLine("九九を出力します。");

            var 九九 = 掛けられる数の数列.SelectMany(_ => 掛ける数の数列, (掛けられる数, 掛ける数) => $"{掛けられる数} x {掛ける数} = {掛けられる数 * 掛ける数}");

            foreach (var str in 九九)
            {
                Console.WriteLine(str);
            }
        }
    }
}

まぁこんなもんでしょう。

さいごに

そこまで難しくはないと思いますが、初めての人の理解の手助けになればなぁと思い記事にしました。
ちなみに、クエリ式形式で書いたときの2重from句についても解説を入れようかとも思ったんですが、 まぁ自分がクエリ式形式で書くことはほとんどないので省略させてもらいました。

おまけ

九九をクエリ式形式で書いた場合

using System;
using System.Linq;

namespace CSharp
{
    public static class Program
    {
        public static void Main()
        {
            var 九九 =
                from 掛けられる数 in Enumerable.Range(1, 9)
                from 掛ける数 in Enumerable.Range(1, 9)
                select $"{掛けられる数} x {掛ける数} = {掛けられる数 * 掛ける数}";

            foreach (var str in 九九)
            {
                Console.WriteLine(str);
            }
        }
    }
}

こうなります。

2017/05/21 23:48:19
コメントを投稿する