はじめに
このページでは、C# を扱う上で便利な LINQ の機能を紹介します。
LINQ とは?
LINQとは、配列やリストなどのコレクション要素を処理するメソッドを集めたライブラリのことです。
LINQを駆使することでコレクションの複雑な処理を簡潔にできたり、読みやすいコードが書けます。
使用するには、using ステートメントにて以下を宣言する必要があります。
using System.Linq;
便利な関数の紹介
要素の取得(単一)
ElementAt
指定したインデックス位置にある要素を返します。
ElementAtOrDefault だと、インデックスが範囲外の場合は既定値を返します。
int[] source = new[] { 5, 10, 22, 7, 3, 16 };
var result = source.ElementAt(2);
// -> 22
First
シーケンスの最初の要素を返します。
指定した条件があれば、条件を満たす最初の要素を返します。
FirstOrDefault だと、要素が見つからない場合に既定値を返します。
int[] source = new[] { 5, 10, 22, 7, 3, 16 };
var result = source.First();
// -> 5
var result2 = source.First(e => e > 8);
// -> 10
var result3 = source.FirstOrDefault(e => e > 30);
// -> 0
Last
シーケンスの最後の要素を返します。
指定した条件があれば、条件を満たす最後の要素を返します。
LastOrDefault だと、シーケンスが空のときに既定値を返します。
int[] source1 = new[] { 5, 10, 22, 7, 3, 16 };
var result = source1.Last();
// -> 16
var result2 = source1.Last(e => e < 8);
// -> 3
int[] source2 = new int[] { };
var result3 = source2.LastOrDefault();
// -> 0
Single
シーケンスの 1 つの特定の要素を返します。
要素が複数存在する場合は例外をスローします。
SingleOrDefault だと、シーケンスが空のときに既定値を返します。要素が複数の場合は例外が発生するので注意して下さい。
int[] source1 = new[] { 5, 10, 22, 7, 3, 16 };
var result = source1.Single(e => e > 20);
// -> 22
var result2 = source1.Single(e => e > 8);
// -> InvalidOperationException エラー
int[] source2 = new int[] { };
var result3 = source2.SingleOrDefault();
// -> 0
int[] source3 = new int[] { 1, 2, 3};
var result4 = source3.SingleOrDefault();
// -> InvalidOperationException エラー
要素の取得(複数)
Where
条件を満たす要素をすべて返します。
int[] source = new[] { 5, 10, 22, 7, 3, 16 };
var result = source.Where(e => e > 15);
// -> 22,16
Distinct
重複を除いたシーケンスを返します。
int[] source = new[] { 5, 10, 22, 10, 3, 3 };
var result = source.Distinct();
// -> 5,10,22,3
Skip
先頭から指定した数の要素をスキップし、残りを返します。
int[] source = new[] { 5, 10, 22, 10, 3, 3 };
var result = source.Skip(2);
// -> 22,10,3,3
SkipWhile
指定した条件が満たされる限り先頭から要素をスキップした後、残りの要素を返します。
int[] source = new[] { 5, 10, 22, 30, 42, 50 };
var result = source.SkipWhile(e => e < 20);
// -> 22,30,42,50
Take
先頭から指定した数の要素を返します。
int[] source = new[] { 5, 10, 22, 30, 42, 50 };
var result = source.Take(3);
// -> 5,10,22
TakeWhile
先頭から指定した条件を満たされる限りの要素を返した後、残りの要素をスキップします。
int[] source = new[] { 5, 10, 22, 30, 42, 50 };
var result = source.TakeWhile(e => e < 15);
// -> 5,10
集計
Max
最大値を返します。
int[] source = new[] { 5, 10, 22, 7, 3, 14 };
var result = source.Max();
// -> 22
Min
最小値を返します。
int[] source = new[] { 5, 10, 22, 7, 3, 14 };
var result = source.Min();
// -> 3
Sum
合計値を返します。
int[] source = new[] { 5, 10, 3 };
var result = source.Sum();
// -> 18
Average
平均値を返します。
int[] source = new[] { 5, 10, 3 };
var result = source.Average();
// -> 6
Aggregate
アキュムレータ関数を適用した結果を返します。
第一引数に初期値としてシード値を設定することもできます。
int[] source = new[] { 1, 2, 3 };
var result = source.Aggregate((n, next) => n + next * 2);
// -> 11 (計算内容: 1 + (2*2) + (3*2) = 11)
// 初期値となるシード値 100 を設定
var result2 = source.Aggregate(100, (n, next) => n + next * 2);
// -> 112 (計算内容: 100 + (1*2)+(2*2)+(3*2) = 112)
判定
All
すべての要素が条件を満たしているかを判断します。
int[] source = new[] { 1, 5, 2, 6 };
var result = source.All(e => e < 10);
// -> True
var result2 = source.All(e => e > 5);
// -> False
Any
条件を満たす要素があるかを判断します。
int[] source = new[] { 1, 5, 2, 6 };
var result = source.Any(e => e > 5);
// -> True
Contains
指定した要素があるかどうかを調べます。
int[] source = new[] { 1, 5, 2, 6 };
var result = source.Contains(5);
// -> True
var result = source.Contains(3);
// -> False
SequenceEqual
2 つのシーケンスが等しいかどうかを判断します。
int[] source1 = new[] { 1, 5, 2, 6 };
int[] source2 = new[] { 1, 5, 7, 10 };
var result = source1.SequenceEqual(source2);
// -> False
集合
Union
2つのシーケンスの和集合を生成します。
int[] source1 = new[] { 1, 5, 2, 6 };
int[] source2 = new[] { 1, 5, 7, 10 };
var result = source1.Union(source2);
// -> 1,5,2,6,7,10
Except
2つのシーケンスの差集合を生成します。
int[] source1 = new[] { 1, 5, 2, 6 };
int[] source2 = new[] { 1, 5, 7, 10 };
var result = source1.Except(source2);
// -> 2,6
Intersect
2つのシーケンスの積集合を生成します。
int[] source1 = new[] { 1, 5, 2, 6 };
int[] source2 = new[] { 1, 5, 7, 10 };
var result = source1.Intersect(source2);
// -> 1,5
ソート
OrderBy
要素を昇順に並べ替えます。
Pet[] pets = {
new Pet { Name = "Pochi", Age = 4 },
new Pet { Name = "Tama", Age = 8 },
new Pet { Name = "Nyanko", Age = 1 },
new Pet { Name = "Wanko", Age = 1 }
};
var result = pets.OrderBy(e => e.Age);
// -> {{ Name = Nyanko, Age = 1 },
// { Name = Wanko, Age = 1 }
// { Name = Pochi, Age = 4 },
// { Name = Tama, Age = 8 }},
OrderByDescending
要素を降順に並べ替えます。
Pet[] pets = {
new Pet { Name = "Pochi", Age = 4 },
new Pet { Name = "Tama", Age = 8 },
new Pet { Name = "Nyanko", Age = 1 },
new Pet { Name = "Wanko", Age = 1 }
};
var result = pets.OrderByDescending(e => e.Age);
// -> {{ Name = Tama, Age = 8 },
// { Name = Pochi, Age = 4 },
// { Name = Nyanko, Age = 1 }
// { Name = Wanko, Age = 1 }},
ThenBy
要素を並び替えた後、追加の条件で昇順に並べ替えます。
Pet[] pets = {
new Pet { Name = "Pochi", Age = 4 },
new Pet { Name = "Tama", Age = 8 },
new Pet { Name = "Nyanko", Age = 1 },
new Pet { Name = "Wanko", Age = 1 }
};
var result = pets.OrderBy(e => e.Age)
.ThenBy(e => e.Name.Length);
// -> {{ Name = Wanko, Age = 1 },
// { Name = Nyanko, Age = 1 }
// { Name = Pochi, Age = 4 },
// { Name = Tama, Age = 8 }},
ThenByDescending
要素を並び替えた後、追加の条件で降順に並べ替えます。
Pet[] pets = {
new Pet { Name = "Pochi", Age = 4 },
new Pet { Name = "Tama", Age = 8 },
new Pet { Name = "Nyanko", Age = 1 },
new Pet { Name = "Wanko", Age = 1 }
};
var result = pets.OrderBy(e => e.Age)
.ThenByDescending(e => e.Name.Length);
// -> {{ Name = Nyanko, Age = 1 },
// { Name = Wanko, Age = 1 }
// { Name = Pochi, Age = 4 },
// { Name = Tama, Age = 8 }},
Reverse
要素の順序を反転させます。
int[] source = new[] { 1, 2, 3, 4, 5 };
var result = source.Reverse();
// -> 5,4,3,2,1
射影
Select
各要素すべてに処理を行い、その結果を1つのシーケンスとして返します。
int[] source = new[] { 1, 2, 3, 4, 5 };
var result = source.Select(e => e * 2);
// -> 2,4,6,8,10
SelectMany
各要素すべてに処理を行い、その結果を1つのシーケンスに平坦化します。
平坦化とは、例えば List<List<int>> を1つのList<int> にまとめたりすることです。
List<List<int>> lists = new List<List<int>>
{
new List<int> { 1, 2, 3 },
new List<int> { 4, 5, 6 },
new List<int> { 7, 8, 9 },
};
var result = lists.SelectMany(e => e);
// -> 1,2,3,4,5,6,7,8,9
GroupBy
指定のキーで要素をグループ化します。
Pet[] pets = {
new Pet { Name = "Pochi", Age = 4 },
new Pet { Name = "Tama", Age = 8 },
new Pet { Name = "Nyanko", Age = 1 },
new Pet { Name = "Wanko", Age = 1 }
};
var result = pets.GroupBy(e => e.Age);
// -> {Key = 4, Source = {{ Name = "Pochi", Age = 4 }},
// Key = 8, Source = {{ Name = "Tama", Age = 8 }},
// Key = 1, Source = {{ Name = "Nyanko", Age = 1 }, { Name = "Wanko", Age = 1 }}}
結合
Join
一致するキーに基づいて、2つのシーケンスの要素を結合します。
ItemData[] items = new ItemData[]
{
new ItemData() { Name = "Pencil", ID = 1 },
new ItemData() { Name = "Table", ID = 2 },
new ItemData() { Name = "Chair", ID = 3 },
new ItemData() { Name = "Lamp", ID = 4 },
};
AmountData[] amounts = new AmountData[]
{
new AmountData() { ItemID = 2, Amount = 10 },
new AmountData() { ItemID = 3, Amount = 20 },
};
var result = items.Join(amounts,
item => item.ID,
amount => amount.ItemID,
(item, amount) => new {item.Name, amount.ItemID, amount.Amount});
// -> {{ Name = Table, ItemID = 2, Amount = 10 },
// { Name = Chair, ItemID = 3, Amount = 20 }}
GroupJoin
2つのシーケンスを指定したキーに基づいて結合してグループ化します。
var items = new[]
{
new { Name = "Pencil", ID = 1 },
new { Name = "Table", ID = 2 },
new { Name = "Chair", ID = 3 },
new { Name = "Lamp", ID = 4 },
};
var amounts = new[]
{
new { ItemID = 2, Amount = 10 },
new { ItemID = 3, Amount = 20 },
};
var result = items.GroupJoin(amounts,
item => item.ID,
amount => amount.ItemID,
(i, a) => new { ID = i.ID, Name = i.Name, Amount = a.DefaultIfEmpty() })
.SelectMany(x => x.Amount, (x, a) => new
{
ID = x.ID,
Name = x.Name,
Amount = a != null ? a.Amount : -1
});
// -> {{ ID = 1, Name = Pencil, Amount = -1 },
// { ID = 2, Name = Table, Amount = 10 },
// { ID = 3, Name = Chair, Amount = 20 },
// { ID = 4, Name = Lamp, Amount = -1 }}
Concat
2つのシーケンスを連結します。
var items1 = new[]
{
new { Name = "Pencil", Amount = 1 },
new { Name = "Table", Amount = 2 },
new { Name = "Chair", Amount = 4 },
};
var items2 = new[]
{
new { Name = "Notebook", Amount = 3 },
new { Name = "Lamp", Amount = 1 },
};
var result = items1.Concat(items2);
// -> {{ Name = Pencil, Amount = 1 },
// { Name = Table, Amount = 2 },
// { Name = Chair, Amount = 4 },
// { Name = Notebook, Amount = 3 },
// { Name = Lamp, Amount = 1 }}
DefaultIfEmpty
シーケンスを返します。
シーケンスが空の場合は規定値もしくは任意の要素を返します。
var items = new[]
{
new { Name = "Pencil", Amount = 1 },
new { Name = "Table", Amount = 2 },
new { Name = "Chair", Amount = 4 },
};
var result = items.DefaultIfEmpty();
// -> {{ Name = Pencil, Amount = 1 },
// { Name = Table, Amount = 2 },
// { Name = Chair, Amount = 4 }}
Zip
2つのシーケンスに指定した関数を適用し、結果を1つのシーケンスとして生成します。
int[] numbers = new int[] { 1, 2, 3, 4 };
string[] characters = new string[] { "A", "B", "C" };
var result = numbers.Zip(characters, (n, c) => string.Format("{0}-{1}", n, c));
// -> {1-A, 2-B, 3-C}
変換
OfType
要素を指定した型でフィルター処理します。
ArrayList list = new ArrayList { 'A', 2, 7.5, "cat", "dog" };
var result = list.OfType<string>();
// -> { cat, dog }
Cast
要素を指定した型にキャストします。
キャストできない場合は例外をスローします。
ArrayList list1 = new ArrayList { "cat", "dog", "pig" };
var result1 = list1.Cast<string>();
// -> { cat, dog, pig }
ArrayList list2 = new ArrayList { 2, "cat", "dog" };
var result2 = list2.Cast<string>();
// -> InvalidCastException エラー
ToArray
シーケンスから配列を作成します。
ToDictionary
シーケンスから Dictionary<TKey, TValue> を作成します。
ToList
シーケンスから List<T> を作成します。
ToLookup
シーケンスの特定のデータをキーにしたシーケンスにまとめて返します。
AsEnumerable
IEnumerable を返します。