前回のLINQ to Objects 備忘録3に続いて備忘録を掲載します。今回はFirst, FirstOrDefault, Last, LastOrDefault, Single, SingleOrDefault, ElementAt, ElementAtOrDefault, DefaultIfEmpty, Concat, SequenceEqual, AsEnumerable, ToArray, ToList, ToDictionary, ToLookupを扱います。データはLINQ to Objects 備忘録1で作成したデータを使用します。

確認環境は.NET 3.5, 開発環境はVisual Studio 2008 Professionalです。

今回取り扱っているクエリ式,拡張メソッド

クエリ式,拡張メソッド 簡単な説明
First,FirstOrDefault拡張メソッド 先頭の要素を取得します。
Last,LastOrDefault拡張メソッド 末尾の要素を取得します。
Single,SingleOrDefault拡張メソッド 1つの要素からなく演算結果の集合から要素を1つ取得します。
ElementAt,ElementAtOrDefault拡張メソッド インデックスで指定した位置の要素を取得します。
DefaultIfEmpty拡張メソッド 集合から空の場合、デフォルト値を持つ要素を1つ含む集合を作成します。
Concat拡張メソッド 2つの要素の集合を連結します。
SequenceEqual拡張メソッド 2つの要素の集合のすべてを要素の位置も含めて同一化を調べます。
AsEnumerable,ToArray,ToList拡張メソッド 集合を特定の型のコレクションに変換します。
ToDictionary,ToLookup拡張メソッド 要素の集合を特定の連想配列コレクションに変換します。

1.LINQクエリメモ

1.1 First,FirstOrDefault拡張メソッド

要素の集合の中で先頭の要素を取得する場合にFirst拡張メソッドを使用します。述部をパラメータに指定する場合は、述部に該当する最初の要素を返します。見つからない場合はInvalidOperationExceptionがスローされます。

var values = new int[] { 1, 2, 3, 4, 5 };
Console.WriteLine(values.First());
Console.WriteLine(values.First(v => v > 3));

実行結果です。

1
4

要素の集合の中で先頭の要素を取得し、存在しない場合はInvalidOperationExceptionではなく、default(T)を返すようにしたい場合は、FirstOrDefault拡張メソッドを使用します。

var categories = InitializeData();
Console.WriteLine(categories.FirstOrDefault());
Console.WriteLine((categories.FirstOrDefault(c =>  c.ID > 6) == null) ? "null" : "not null");

実行結果です。

Category:ID=1, Name=CS
  Article:ID=1, Title=CSArticle1, ReleaseDate=2004/01/01, ArticleType=Code
  Article:ID=2, Title=CSArticle2, ReleaseDate=2004/02/01, ArticleType=Memo

null

1.2 Last,LastOrDefault拡張メソッド

First拡張メソッドとは逆に集合の中で最後の要素を取得したい場合はLast拡張メソッドを使用します。要素がなければInvalidOperationExceptionがスローされます。

var values = new int[] { 1, 2, 3, 4, 5 };
Console.WriteLine(values.Last());
Console.WriteLine(values.Last(v => v > 3));

実行結果です。

5
5

Last拡張メソッドと同じ動作で、要素がない場合はInvalidOperationExceptionではなく、default(T)の値を取得したい場合はLastOrDefault拡張メソッドを使用します。

var values = new int[] { 1, 2, 3, 4, 5 };
Console.WriteLine(values.Last());
Console.WriteLine(values.LastOrDefault(v => v < 0));

実行結果です。

5
0

1.3 Single,SingleOrDefault拡張メソッド

Single拡張メソッドは要素が1つしか存在しない集合から要素を1つ取得します。要素が0or2以上の場合はInvalidOperationExceptionがスローされます。述部を指定することで、述部に該当する要素を取得します。述部に該当する要素が複数ある場合もInvalidOperationExceptionがスローされます。

var values = new int[] { 1, 1, 2, 3, 4 };
Console.WriteLine(values.Single(v => v == 2));
// throw InvalidOperationException
//Console.WriteLine(values.Single(v => v == 1));
//Console.WriteLine(Enumerable.Empty<int>().Single());

実行結果です。

2

Single拡張メソッドと同様な機能で、要素が空の場合はdefault(T)を返したい場合はSingleOrDefault拡張メソッドを使用します。要素が2以上の場合はInvalidOperationExceptionがスローされます。

var values = new int[] { 1, 1, 2, 3, 4 };
// throw InvalidOperationException
// Console.WriteLine(values.SingleOrDefault(v => v == 1));
Console.WriteLine(Enumerable.Empty<int>().SingleOrDefault());

実行結果です。

0

1.4 ElementAt,ElementAtOrDefault拡張メソッド

ElementAt,ElementAtOrDefault拡張メソッドを使用すると、パラメータで指定した位置の要素を取得します。要素が存在しない場合はArgumentOutOfRangeExceptionがスローされます。範囲外を指定したときにdefault(T)を返したい場合はElementAtOrDefault拡張メソッドを使用します。

var values = new int[] { 1, 2, 3, 4, 5 };
Console.WriteLine(values.ElementAt(1));
Console.WriteLine(values.ElementAtOrDefault(5));

実行結果です。

2
0

1.5 DefaultIfEmpty拡張メソッド

DefaultIfEmpty拡張メソッドは、集合の要素が空の場合にdefault(T)値が格納された集合を返します。オーバーロード版に集合が空の場合のデフォルト値を指定することができます。

var query = Enumerable.Empty<int>().DefaultIfEmpty();
foreach (var item in query)
{
   Console.WriteLine(item);
}
var query2 = Enumerable.Empty<int>().DefaultIfEmpty(2);
foreach (var item in query2)
{
   Console.WriteLine(item);
}

実行結果です。

0
2

1.6 Concat拡張メソッド

Concat拡張メソッドを使用すると2つの集合を連結することができます。

var v1 = new int[] { 1, 2, 3, 4, 5 };
var v2 = new int[] { 1, 4, 8, 9 };
foreach (var item in v1.Concat(v2))
{
    Console.WriteLine(item);
}

実行結果です。

1
2
3
4
5
1
4
8
9

1.7 SequenceEqual拡張メソッド

SequenceEqual拡張メソッドは、2つの集合のそれぞれの位置の要素を比較し、すべてが等しい場合はTrueを返します。要素の比較は要素の型のGetHashCodeとEqualsが使用されます。比較方法を変更したい場合は、オーバーロード版を使用し、パラメータにIEqualityComparer<T>を実装したクラスのインスタンスを渡します。

var v1 = new []{ new { Name = "book", Price = 2000 }, new { Name = "comic", Price = 500 } };
var v2 = new[] { new { Name = "book", Price = 2000 }, new { Name = "comic", Price = 500 } };
Console.WriteLine(v1.SequenceEqual(v2));
var v3 = new[] { new { Name = "book", Price = 2000 }, new { Name = "comic", Price = 500 }, new{Name="novel", Price=420} };
Console.WriteLine(v1.SequenceEqual(v3));
var v4 = new[] { new { Name = "book", Price = 2000 }, new { Name = "comic", Price = 510 } };
Console.WriteLine(v1.SequenceEqual(v4));

実行結果です。

True
False
False

1.8 AsEnumerable,ToArray,ToList拡張メソッド

AsEnumerable拡張メソッドは、要素の集合をIEnumerable<TSource>に変換します。これが使用されるシナリオは、特定の要素の集合の型(List型を継承したCategories型など)に対するWhere等の拡張メソッドを定義している場合に、既定の拡張メソッドを適用したい状況です。AsEnumerable拡張メソッドでIEnumerable<TSource>を変換することで、Enumerableで定義されている既定の拡張メソッド(Where等)を適用できるようになります。

var v1 = new[] { new { Name = "book", Price = 2000 }, new { Name = "comic", Price = 500 } };
var query = v1.AsEnumerable();
foreach (var item in query)
{
    Console.WriteLine(item);
}

実行結果です。

{ Name = book, Price = 2000 }
{ Name = comic, Price = 500 }

ToArray,ToList拡張メソッドは集合から特定の型の配列またはList<T>のコレクションを作成します。配列を作成する場合にToArray,List型のコレクションを作成する場合にToListを使用します。

var categories = InitializeData();
Category[] c1 = categories.ToArray();
List<Category> c2 = categories.ToList();

実行するとエラーなく処理が終了します。

1.9 ToDictionary,ToLookup拡張メソッド

ToDictionary拡張メソッドを使用すると、要素の集合から連想配列型コレクションDictionary<T,K>を作成します。オーバーロード版を使用するとValueインスタンスの作成方法やキー値の比較に使用するIEqualityComparer(TKEY)を指定することができます。一つのキーに複数の値が紐づく連想配列コレクションを作成する場合はToLookup拡張メソッドを使用します。

var categories = InitializeData();
Dictionary<int,Category> dic =  categories.ToDictionary(c => c.ID);
foreach (var item in dic.Values)
{
    Console.WriteLine(item);
}
var query = categories.ToDictionary(c => c.ID, c => new { c.ID, c.Name });
foreach (var item in query)
{
    Console.WriteLine(item);
}

実行結果です。

Category:ID=1, Name=CS
  Article:ID=1, Title=CSArticle1, ReleaseDate=2004/01/01, ArticleType=Code
  Article:ID=2, Title=CSArticle2, ReleaseDate=2004/02/01, ArticleType=Memo

Category:ID=2, Name=ASP
  Article:ID=3, Title=CSArticle1, ReleaseDate=2004/01/01, ArticleType=Code
  Article:ID=4, Title=AspArticle1, ReleaseDate=2002/12/21, ArticleType=Memo

Category:ID=3, Name=SQL
  Article:ID=5, Title=SqlArticle1, ReleaseDate=2006/02/03, ArticleType=Code

Category:ID=4, Name=CS
  Article:ID=6, Title=SqlArticle1, ReleaseDate=2008/10/03, ArticleType=Memo

[1, { ID = 1, Name = CS }]
[2, { ID = 2, Name = ASP }]
[3, { ID = 3, Name = SQL }]
[4, { ID = 4, Name = CS }]

ToLookup拡張メソッドを使用すると、要素の集合から連想配列型コレクションLookup<T,K>を作成できます。パラメータに指定したキーセレクタをキーとするLookup型を作成します。Lookup型はToDictionary型と異なり、同一キーで複数の値を持つことができます。実際には、1つのキーに対して複数の値(IGroup型)を持つことができます。

var v1 = new []{ new { Name = "book", Price = 2000 }, new { Name = "comic", Price = 500 }, new {Name ="book", Price=1400} };
var query = v1.ToLookup(v => v.Name);
foreach (var item in query)
{
    Console.WriteLine(item.Key);
    foreach (var v in item)
    {
        Console.WriteLine(v);
    }
}

実行結果です。

book
{ Name = book, Price = 2000 }
{ Name = book, Price = 1400 }
comic
{ Name = comic, Price = 500 }

サンプルの掲載は以上です。

今回でLINQ to Objects 備忘録を終わります。拡張メソッドのオーバーロード版を使用すると、比較や要素の選択をカスタマイズできるようになります。ただし、linqのクエリ式では直接表現することができません。また、クエリ式内でクラス内で定義したメソッドを使用することもできます。

間違い等があればご指摘ください。