読者です 読者をやめる 読者になる 読者になる

いろいろ備忘録日記

主に .NET 絡みのメモを公開しています。

Linq入門記-15 (LINQ To Object, 変換演算子, ToLookup)

C# Linq


前回に引き続き、変換演算子についてです。
今回は、ToLookupメソッドです。


ToLookupメソッドは、ToDictionaryメソッドと似ていて
どちらも、キーと値の組み合わせを持ちます。
違いは、一対多のマッピングデータを構築出来る点です。


ToDictionaryメソッドの一対多版のような感じです。
個人的によく利用します。


ToLookupメソッドの書式は、以下のようになっています。

public static ILookup<TKey, TSource> ToLookup<TSource, TKey>(
    this IEnumerable<TSource> source,
    Func<TSource, TKey> keySelector
)
public static ILookup<TKey, TElement> ToLookup<TSource, TKey, TElement>(
    this IEnumerable<TSource> source,
    Func<TSource, TKey> keySelector,
    Func<TSource, TElement> elementSelector
)


書式的には、ToDictionaryメソッドと同じです。
戻り値で返ってくるオブジェクトが異なっています。

戻り値である、ILookupは初めて見た時には混乱するかもしれません。
以下のようなインターフェース定義となっています。

public interface ILookup<TKey, TElement> : IEnumerable<IGrouping<TKey, TElement>>, IEnumerable
{
    ...
}


ややこしいですね・・・
要は、ILookup自体が、IGroupingのリストのような感じです。
IGroupingは、クエリ式でgroup byを行った際に利用されているものです。


てことで、ILookupを処理する場合は、通常以下のように2重でループを
行います。

ILookup<int, xxx> aLookup = query.ToLookup(item => item.Id);

// 各グループ取得
foreach (var group in aLookup)
{
    // キー
    Console.WriteLine(group.Key);
    
    // グループのデータを列挙.
    foreach (var data in group)
    {
        Console.WriteLine(data);
    }
}


他にも、ILookup自体にインデクサが定義されているので
キー値が分かっている場合は、以下のようにもできます。

if (aLookup.Contains(100))
{
    foreach (var data in aLookup[100])
    {
        Console.WriteLine(data);
    }
}


以下、サンプルです。

    #region LinqSamples-15
    public class LinqSamples15 : IExecutable
    {
        class Person
        {
            public int    Id   { get; set; }
            public string Name { get; set; }
            public string Team { get; set; }
        }
        
        public void Execute()
        {
            var persons = new List<Person>
            {
                 new Person{ Id = 1, Name = "gsf_zero1", Team = "TeamA" }
                ,new Person{ Id = 2, Name = "gsf_zero2", Team = "TeamB" }
                ,new Person{ Id = 3, Name = "gsf_zero3", Team = "TeamA" }
                ,new Person{ Id = 4, Name = "gsf_zero4", Team = "TeamA" }
                ,new Person{ Id = 5, Name = "gsf_zero5", Team = "TeamB" }
                ,new Person{ Id = 6, Name = "gsf_zero6", Team = "TeamC" }
            };
            
            var query = from   aPerson in persons
                        select aPerson;
            
            //
            // Teamの値をキーとして、グルーピング処理.
            // これにより、各キー毎に合致するPersonオブジェクトが紐付いた構造に変換される。
            //
            // 実際のオブジェクトの型は
            //     Lookup<Grouping<string, Person>>
            // となっている。
            //
            // 尚、Lookupオブジェクトを外部から新規で構築することはできない。
            //
            // 以下では、keySelectorを指定している。
            ILookup<string, Person> lookup = query.ToLookup(aPerson => aPerson.Team);
            
            //
            // ILookupに定義されているプロパティにアクセス.
            //   通常、Lookupオブジェクトはループを経由してデータを取得するが、キーを指定して、アクセスすることもできる。
            //
            Console.WriteLine("カウント={0}", lookup.Count);
            foreach (Person teamAPerson in lookup["TeamA"])
            {
                Console.WriteLine(teamAPerson);
            }
            
            //
            // ILookup<TKey, TElement>は、IEnumerable<IGrouping<TKey, TElement>>を継承しているので
            // ループさせることで、IGrouping<TKey, TElement>を取得することができる。
            //
            // このIGrouping<TKey, TElement>が一対多のマッピングを実現している。
            // 尚、IGrouping<TKey, TElement>は、クエリ式にてgroup byを行った際にも取得できる。
            //
            Console.WriteLine("=========== ToLookupに対してkeySelectorを指定した版 =============");
            foreach (IGrouping<string, Person> grouping in lookup)
            {
                Console.WriteLine("KEY={0}", grouping.Key);
                foreach (var aPerson in grouping)
                {
                    Console.WriteLine("\tID={0}, NAME={1}", aPerson.Id, aPerson.Name);
                }
            }
            
            //
            // keySelectorとelementSelectorを指定してみる。
            //
            var lookup2 = query.ToLookup(aPerson => aPerson.Team, aPerson => string.Format("{0}_{1}", aPerson.Id, aPerson.Name));
            
            Console.WriteLine("=========== ToLookupに対してkeySelectorとelementSelectorを指定した版 =============");
            foreach (var grouping in lookup2)
            {
                Console.WriteLine("KEY={0}", grouping.Key);
                foreach (var element in grouping)
                {
                    Console.WriteLine("\t{0}", element);
                }
            }
        }
    }
    #endregion