はじめに
絶賛編集中。
LINQとは
.NETはLINQに始まりLINQで終わるのです。(嘘)
やっていきましょう。
サンプルソースに利用するシーケンス
※この異世界では、名前は主キーなんだから。
// 名前と誕生日
record BirthdayRecord(string Name, DateTime Birthday);
// 名前と肩書
record KatagakiRecord(string Name, string Katagaki);
// 誕生日テーブル
var BirthdayTable = new var BirthdayTable[]{ ... };
// 肩書テーブル
var KatagakiTable = new var BirthdayTable[]{ ... };
クエリ演算子一覧
System.Linq.Enumerableのメソッドです。
.NET9時点になります。
用途 | メソッド |
---|---|
集計 | Aggregate、AggregateBy、Average、Count、CountBy、LongCount、Max、MaxBy、Min、MinBy、Sum |
判定 | All、Any、SequenceEqual |
レコード足す | Append、Prepend |
分割・結合 | Chunk、Concat、Skip、SkipLast、SkipWhile、Take、TakeLast、TakeWhile |
型 | Cast、Oftype |
デフォルト | DefaultIfEmpty、ElementAtOrDefault、FirstOrDefault、SingleOrDefault |
重複削除 | Distinct、DistinctBy |
レコード取得 | ElementAt、ElementAtOrDefault、First、FirstOrDefault、Last、LastOrDefault、Single、SingleOrDefault |
集合 | Except、ExceptBy、Intersect、IntersectBy、Union 、UnionBy |
グルーピング | CountBy、GroupBy、GroupJoin |
テーブル連結 | GroupJoin、Join、Zip |
順序 | Order、OrderBy、OrderDescending、OrderByDescending、Reverse、ThenBy、ThenByDescending |
セレクト | Select、SelectMany |
フィルタ | Oftype、Single、SingleOrDefault、Where |
変換 | Index、ToArray、ToDictionary、ToHashSet、ToList、ToLookup、Zip |
その他 | AsEnumerable、Empty、Range、Repeat、TryGetNonEnumeratedCount |
Aggregate
集計
一番若人を探す。
var retAggregate = BirthdayTable.Aggregate((youngest, target) => youngest.Birthday > target.Birthday ? youngest : target);
System.Console.WriteLine($"retAggregate = {retAggregate.Name}"); // りんく太郎
AggregateBy
GroupBy+Aggregate
年代毎の人数を集計する。
var retAggregateBy = BirthdayTable.AggregateBy(r => r.Birthday.Year / 10 * 10, 0, (count, r) => ++count).OrderBy(r=>r.Key);
System.Console.WriteLine($"retAggregateBy = ");
foreach(var r in retAggregateBy)
{
System.Console.WriteLine($" {r.Key}年代 = {r.Value}人"); // 1990年代 49人
}
All
全てTrue
若人Top10は、全員2000年代生まれか?
var YoungestTop10 = BirthdayTable.OrderByDescending(r => r.Birthday).Take(10);
var retAll = YoungestTop10.All(r => r.Birthday.Year >= 2000);
System.Console.WriteLine($"retAll = {retAll}"); // True or False
Any
一部True
若人Top10に、2000年より前の生まれの人がいるか?
// var YoungestTop10 = BirthdayTable.OrderByDescending(r => r.Birthday).Take(10);
var retAny = YoungestTop10.Any(r => r.Birthday.Year < 2000);
System.Console.WriteLine($"retAny = {retAny}"); // True or False
Append
最後に足す
var retAppend = new[] { 1, 2, 3, 4 }.Append(5);
System.Console.WriteLine($"retAppend = {string.Join(",", retAppend)}"); // 1,2,3,4,5
Average
平均
平均年齢は?
// 誕生日から年齢を算出する
int CalcAge(DateTime birthday)
{
var today = DateTime.Today;
int age = today.Year - birthday.Year;
if (today < birthday.AddYears(age))
{
age--;
}
return age;
}
var AgeTable = BirthdayTable.Select(r => new { Name=r.Name, Age=CalcAge(r.Birthday)});
var retAverage = AgeTable.Select(r=>r.Age).Average();
System.Console.WriteLine($"retAverage = {retAverage:0.00}"); // 42.22
Cast
キャスト
var objectArray = new object[] { 1, 2, 3 };
// System.Console.WriteLine($"retCast = {objectArray.Average()}"); ※コンパイルエラー
var retCast = objectArray.Cast<int>();
System.Console.WriteLine($"retCast = {retCast.Average()}"); // 2
Chunk
分割
var intArray = new int[] { 1, 2, 3, 4, 5 };
var retChunk = intArray.Chunk(3).ToArray();
System.Console.WriteLine($"retChunk.Length = {retChunk.Length}, retChunk[0]={string.Join(",", retChunk[0])}, retChunk[1]={string.Join(",", retChunk[1])} ");
// retChunk.Length = 2, retChunk[0]=1,2,3, retChunk[1]=4,5
Concat
連結
// var intArray = new int[] { 1, 2, 3, 4, 5 };
// var retChunk = intArray.Chunk(3).ToArray();
var retConcat = retChunk[0].Concat(retChunk[1]);
System.Console.WriteLine($"retConcat = {string.Join(",", retConcat.ToArray())}"); // 1,2,3,4,5
Count
カウント
課長は何人?
var retCount = KatagakiTable.Where(r=>r.Katagaki == "課長").Count();
System.Console.WriteLine($"retCount = {retCount}人"); // 10人
CountBy
GroupBy+Count
肩書ごとの人数を数える。
var retCountBy = KatagakiTable.CountBy(r => r.Katagaki);
System.Console.WriteLine($"retCountBy = ");
foreach (var r in retCountBy)
{
System.Console.WriteLine($" {r.Key} = {r.Value}人"); // 課長 = 10人
}
DefaultIfEmpty
デフォルト
var EmptyTable = new List<BirthdayRecord>();
// var retDefaultIfEmpty = EmptyTable.First(); ※System.InvalidOperationException: 'Sequence contains no elements'
var retDefaultIfEmpty = EmptyTable.DefaultIfEmpty(null).First();
System.Console.WriteLine($"retDefaultIfEmpty = {retDefaultIfEmpty}"); // 空文字(null)
Distinct
重複削除
var DuplicateTable = new[] { 1, 2, 3, 2, 3, 4, 1 };
var retDistinct = DuplicateTable.Distinct();
System.Console.WriteLine($"retDistinct = {string.Join(",", retDistinct)}");
DistinctBy
キーセレクタDistinct
肩書毎に先頭レコードが選ばれる。
var retDistinctBt = KatagakiTable.DistinctBy(r=>r.Katagaki);
System.Console.WriteLine($"retDistinctBt = {string.Join(",", retDistinctBt)}");
ElementAt
指定位置の要素
//var intArray = new int[] { 1, 2, 3, 4, 5 };
var retElementAt = intArray.ElementAt(3);
System.Console.WriteLine($"retElementAt = {retElementAt}"); // 4
ElementAtOrDefault
DefaultIfEmpty+ElementAt
//var intArray = new int[] { 1, 2, 3, 4, 5 };
// var retElementAt = intArray.ElementAt(5); ※System.ArgumentOutOfRangeException: 'Index was out of range. Must be non-negative and less than the size of the collection.
var retElementAtOrDefault = intArray.DefaultIfEmpty(0).ElementAtOrDefault(5);
System.Console.WriteLine($"retElementAtOrDefault = {retElementAtOrDefault}"); // 0
Except
差集合
var tableA = Enumerable.Range(1, 10); // 1~10
var tableB = Enumerable.Range(5, 10); // 5~15
var retExcept = tableA.Except(tableB);
System.Console.WriteLine($"retExcept = {string.Join(",", retExcept)}"); // 1,2,3,4
var retExcept2 = tableB.Except(tableA);
System.Console.WriteLine($"retExcept2 = {string.Join(",", retExcept2)}"); //11,12,13,14
ExceptBy
キーセレクタExcept
社長、部長、課長以外の肩書をもつ先頭レコードを抽出する。
var retExceptBy = KatagakiTable.ExceptBy(new[] { "社長", "部長", "課長" }, r => r.Katagaki);
System.Console.WriteLine($"retExceptBy = {string.Join(",", retExceptBy)}");
First
先頭
var retFirst = KatagakiTable.First();
System.Console.WriteLine($"retFirst = {retFirst}"); // 先頭のレコード
FirstOrDefault
DefaultEmpty+First
var emptyRecord = new BirthdayRecord(string.Empty, DateTime.MinValue);
var retFirstOrDefault = EmptyTable.FirstOrDefault(emptyRecord);
System.Console.WriteLine($"retFirstOrDefault = {retFirstOrDefault}"); // emptyRecord
GroupBy
グルーピング
肩書毎の名前を一覧する
var retGroupBy = KatagakiTable.GroupBy(r=>r.Katagaki);
Console.WriteLine($"retGroupBy = ");
foreach(var group in retGroupBy)
{
Console.WriteLine($" {group.Key} {string.Join(",", group.Select(r => r.Name))}"); // 肩書 社員A,社員B...
}
GroupJoin
Join+GroupBy
1:N関係のテーブルをJoinしてGroupBy
var OuterTable = new Tuple<int, string>[] { new(1, "りんく太郎"), new(2, "りんく次郎") };
var InnerTable = new Tuple<int, string>[] { new(1, "C#"), new(1, "LINQ") };
var retGroupJoin = OuterTable.GroupJoin(InnerTable, o => o.Item1, i => i.Item1, (o, i) => new { Outer = o, Inner = i.ToList() });
Console.WriteLine($"retGroupJoin = ");
foreach (var r in retGroupJoin)
{
Console.WriteLine($" {r.Outer.Item2} {string.Join(",", r.Inner.Select(r=>r.Item2))}");
}
//retGroupJoin =
// りんく太郎 C#,LINQ
// りんく次郎
Index
連番
var retIndex = BirthdayTable.Index();
Console.WriteLine($"retIndex = ");
foreach (var r in retIndex)
{
Console.WriteLine($" {r.Index} {r.Item.Name}"); // 1 りんく太郎
}
Intersect
積集合
// var tableA = Enumerable.Range(1, 10); // 1~10
// var tableB = Enumerable.Range(5, 10); // 5~15
var retIntersect = tableA.Intersect(tableB);
Console.WriteLine($"retIntersect = {string.Join(",", retIntersect)}"); // 5,6,7,8,9,10
IntersectBy キーセレクタIntersect
課長の肩書をもつ先頭レコードを抽出する。
var retIntersectBy = KatagakiTable.IntersectBy(new[] { "課長" }, r => r.Katagaki);
Console.WriteLine($"retIntersectBy = {string.Join(",", retIntersectBy)}");
Join
連結
var retJoin = KatagakiTable.Join(BirthdayTable, o => o.Name, i => i.Name, (o, i) => new { Outer = o, Inner = i });
Console.WriteLine($"retJoin = ");
foreach (var r in retJoin)
{
Console.WriteLine($" {r.Outer.Name} {r.Outer.Katagaki} {r.Inner.Birthday:yyyy/MM/dd}"); // りんく次郎 プログラマ 2007/11/19
}
Last
最後
var retLast = KatagakiTable.Last();
Console.WriteLine($"retLast = {retLast}"); // 最後のレコード
LastOrDefault DefaultIfEmpty+Last
//var emptyRecord = new BirthdayRecord(string.Empty, DateTime.MinValue);
var retLastOrDefault = EmptyTable.LastOrDefault(emptyRecord);
Console.WriteLine($"retLastOrDefault = {retLastOrDefault}"); // emptyRecord
LongCount
カウント(long型)
全部で何人?
var retLongCount = KatagakiTable.LongCount();
System.Console.WriteLine($"retLongCount = {retLongCount}人"); // 253人
Max
最大
//var AgeTable = BirthdayTable.Select(r => new { Name = r.Name, Age = CalcAge(r.Birthday) });
var retMax = AgeTable.Select(r => r.Age).Max();
System.Console.WriteLine($"retMax = {retMax:0.00}"); // 65.00
MaxBy
キーセレクタMax
//var AgeTable = BirthdayTable.Select(r => new { Name = r.Name, Age = CalcAge(r.Birthday) });
var retMaxBy = AgeTable.MaxBy(r => r.Age);
System.Console.WriteLine($"retMaxBy = {retMaxBy.Age:0.00}"); // 65.00
Min
最小
//var AgeTable = BirthdayTable.Select(r => new { Name = r.Name, Age = CalcAge(r.Birthday) });
var retMin = AgeTable.Select(r => r.Age).Min();
System.Console.WriteLine($"retMin = {retMin:0.00}"); // 17.00
MinBy
キーセレクタMin
//var AgeTable = BirthdayTable.Select(r => new { Name = r.Name, Age = CalcAge(r.Birthday) });
var retMinBy = AgeTable.MinBy(r => r.Age);
System.Console.WriteLine($"retMinBy = {retMinBy.Age:0.00}"); // 17.00
OfType
型でフィルタ
var objectArray2 = new object[] { 2.4, "a", 1, 3.5 };
var retOfType = objectArray2.OfType<Double>();
System.Console.WriteLine($"retOfType = {string.Join(",", retOfType)}"); // 2.4,3.5
Order
昇順
var randomArray = new[] { 5, 2, 3, 1, 4 };
var retOrder = randomArray.Order();
System.Console.WriteLine($"retOrder = {string.Join(",", retOrder)}"); // 1,2,3,4,5
OrderBy
キーセレクタOrder
// BirthdayRecordの昇順
// var retOrder = BirthdayTable.Order().ToList(); System.InvalidOperationException: 'Failed to compare two elements in the array.'
// 誕生日で昇順
var retOrderBy = BirthdayTable.OrderBy(r=>r.Birthday).ToList();
OrderDescending
降順
// var randomArray = new[] { 5, 2, 3, 1, 4 };
var retOrderDescending = randomArray.OrderDescending();
System.Console.WriteLine($"retOrderDescending = {string.Join(",", retOrderDescending)}"); // 5,4,3,2,1
OrderByDescending
キーセレクタOrderDescending
// BirthdayRecordの降順
// var retOrderDescending = BirthdayTable.OrderDescending().ToList(); System.InvalidOperationException: 'Failed to compare two elements in the array.'
// 名前の降順
var retOrderByDescending = BirthdayTable.OrderByDescending(r=>r.Name).ToList();
Prepend
先頭に足す
var retPrepend = new[] { 1, 2, 3, 4 }.Prepend(5);
Console.WriteLine($"retPrepend = {string.Join(",", retPrepend)}"); // 5,1,3,4
Reverse
反転
var retReverse = new[] { 1, 2, 3, 4 }.Reverse();
Console.WriteLine($"retReverse = {string.Join(",", retReverse)}"); // 4,3,2,1
Select
セレクト(1⇒1)
誕生日テーブルから誕生年を取得する
var retSelect = BirthdayTable.Select(r=>r.Birthday.Year).Distinct();
Console.WriteLine($"retSelect = {string.Join(",", retSelect)}");
SelectMany
セレクト(1⇒N)
名前に使われている文字を取得する
var retSelectMany = BirthdayTable.SelectMany(r => r.Name.ToList()).Distinct();
Console.WriteLine($"retSelectMany = {string.Join(",", retSelectMany)}");
SequenceEqual
等値
//var tableA = Enumerable.Range(1, 10);
//var tableB = Enumerable.Range(5, 10);
var tableC = Enumerable.Range(5, 10).ToList();
var retSequenceEqual = tableA.SequenceEqual(tableB);
Console.WriteLine($"retSequenceEqual = {retSequenceEqual}"); // False
var retSequenceEqual2 = tableC.SequenceEqual(tableB);
Console.WriteLine($"retSequenceEqual2 = {retSequenceEqual2}"); // True
Single
一意にするフィルタ
// 指定された生年月日の人がいない
// var retSingle = BirthdayTable.Single(r => r.Birthday == new DateTime(9999, 12, 31)); System.InvalidOperationException: 'Sequence contains no matching element'
// 同じ生年月日の人がいる
// var retSingle = BirthdayTable.Single(r => r.Birthday == new DateTime(2002, 10, 10)); System.InvalidOperationException: 'Sequence contains more than one matching element'
var retSingle = BirthdayTable.Single(r => r.Birthday == new DateTime(2007, 11, 19));
Console.WriteLine($"retSingle = {retSingle.Name}"); // りんく次郎
SingleOrDefault
DefaultIfEmpty+Single
var retSingleOrDefault = BirthdayTable.SingleOrDefault(r => r.Birthday == new DateTime(9999, 12, 31), new BirthdayRecord(string.Empty, DateTime.MinValue));
Console.WriteLine($"retSingleOrDefault = {retSingleOrDefault}"); // { Name = , Birthday = 0001/01/01 0:00:00 }
Skip
前方スキップ
//var tableA = Enumerable.Range(1, 10);
var retSkip = tableA.Skip(5);
Console.WriteLine($"retSkip = {string.Join(",", retSkip)}"); // 6,7,8,9,10
SkipLast
後方スキップ
//var tableA = Enumerable.Range(1, 10);
var retSkipLast = tableA.SkipLast(5);
Console.WriteLine($"retSkipLast = {string.Join(",", retSkipLast)}"); // 1,2,3,4,5
SkipWhile
条件満たす間スキップ
//var tableA = Enumerable.Range(1, 10);
var retSkipWhile = tableA.SkipWhile(r=>r < 8);
Console.WriteLine($"retSkipWhile = {string.Join(",", retSkipWhile)}"); // 8, 9, 1
Sum
合計
Take
先頭から指定数の要素を取得
TakeLast
後方から指定数の要素を取得
TakeWhile
先頭から条件を満たす要素を取得
ThenBy
昇順(複数列)
ThenByDescending
降順(複数列)
ToArray
変換
ToDictionary
変換
ToHashSet
変換
ToList
変換
ToLookup
変換
Union
和集合
UnionBy
条件指定Union
Where
フィルタ
Zip
順番通りに連結
その他
AsEnumerable
LINQのメソッドと同名の独自メソッドを隠蔽するために利用する。
Empty
空のシーケンスを作成する。
var EmptyTable = Enumerable.Empty<BirthdayRecord>();
Range
整数シーケンスを作成する。
var RangeTable = Enumerable.Range(2, 10); // {2,3,4,5,6,7,8,9,10,11}
Repeat
同値シーケンスを作成する。
var RepeatTable = Enumerable.Repeat("hoge", 3); // {"hoge","hoge","hoge"}
TryGetNonEnumeratedCount
Enumeratable.Count()を使わずにカウントできればカウントする。
クエリ式
from ~ in ~
where ~
orderby ~
group ~ by ~
group ~ by ~ into ~
join ~ in ~ on ~ equals ~
select ~
select new {}
let ~
サブクエリ
Parallel LINQ (PLINQ)
前に書いた記事を載せとく。