.NET LINQ

# はじめに

## .NET LINQとは
https://learn.microsoft.com/ja-jp/dotnet/csharp/linq/

.NETはLINQに始まりLINQで終わるのです。(嘘)
やっていきましょう。

## 既に記事があるよ？
やっていきましょう。(個人的な備忘録なので)

# クエリ演算子一覧

https://learn.microsoft.com/ja-jp/dotnet/api/system.linq.enumerable?view=net-10.0

System.Linq.Enumerableの.NET10時点のメソッドになります。

:::note warn
[LeftJoin](#leftjoin)、[RightJoin](#rightjoin)は、.NET10から使えます。
.NET10正式は、2025年11月頃にリリース予定です。
:::

| 用途 | メソッド |
|:--|:--|
|  集計 | [Aggregate](#aggregate)、[AggregateBy](#aggregateby)、[Average](#average)、[Count](#count)、[CountBy](#countby)、[LongCount](#longcount)、[Max](#max)、[MaxBy](#maxby)、[Min](#min)、[MinBy](#minby)、[Sum](#sum) |
| 判定 | [All](#all)、[Any](#any)、[SequenceEqual](#sequenceequal) |
| レコード足す | [Append](#append)、[Prepend](#prepend) |
| 分割・結合 | [Chunk](#chunk)、[Concat](#concat)、[Skip](#skip)、[SkipLast](#skiplast)、[SkipWhile](#skipWhile)、[Take](#take)、[TakeLast](#takelast)、[TakeWhile](#takeWhile) |
| 型 | [Cast](#cast)、[Oftype](#oftype) |
| デフォルト | [DefaultIfEmpty](#defaultifempty)、[ElementAtOrDefault](#elementatordefault)、[FirstOrDefault](#firstordefault)、[SingleOrDefault](#singleordefault) |
| 重複削除 | [Distinct](#distinct)、[DistinctBy](#distinctby) |
| レコード取得 | [ElementAt](#elementat)、[ElementAtOrDefault](#elementatordefault)、[First](#first)、[FirstOrDefault](#firstordefault)、[Last](#last)、[LastOrDefault](#lastordefault)、[Single](#single)、[SingleOrDefault](#singleordefault) |
| 集合 | [Except](#except)、[ExceptBy](#exceptby)、[Intersect](#intersect)、[IntersectBy](#intersectby)、[Union](#union) 、[UnionBy](#unionby) |
| グルーピング | [CountBy](#countby)、[GroupBy](#groupby)、[GroupJoin](#groupjoin)、[ToLookup](#tolookup) |
| テーブル連結 | [GroupJoin](#groupjoin)、[Join](#join)、[LeftJoin](#leftjoin)、[RightJoin](#rightjoin)、[Zip](#zip) |
| 順序 | [Order](#order)、[OrderBy](#orderby)、[OrderDescending](#orderdescending)、[OrderByDescending](#orderbydescending)、[Reverse](#reverse)、[ThenBy](#thenby)、[ThenByDescending](#thenbydescending) |
| セレクト | [Select](#select)、[SelectMany](#selectmany) |
| フィルタ | [Oftype](#oftype)、[Single](#single)、[SingleOrDefault](#singleordefault)、[Where](#where) |
| 変換 | [Index](#index)、[ToArray](#toarray)、[ToDictionary](#todictionary)、[ToHashSet](#tohashset)、[ToList](#tolist)、[ToLookup](#tolookup)、[Zip](#zip) |
| その他 | [AsEnumerable](#asenumerable)、[Empty](#empty)、[Range](#range)、[Repeat](#repeat)、[TryGetNonEnumeratedCount](#trygetnonenumeratedcount) |

## サンプルソースに利用するテーブル
※この異世界では、名前は主キーなんだから。
```CSharp
// 名前と誕生日
record BirthdayRecord(string Name, DateTime Birthday);
// 名前と肩書
record KatagakiRecord(string Name, string Katagaki);

// 誕生日テーブル
var BirthdayTable = new var BirthdayTable[]{ ... };
// 肩書テーブル
var KatagakiTable = new var BirthdayTable[]{ ... };
```

## [Aggregate](https://learn.microsoft.com/ja-jp/dotnet/api/system.linq.enumerable.aggregate)
### 集計
カスタマイズされた集計を実現するときはこちら。
```Csharp
// 一番若い人はだれか？
var retAggregate = BirthdayTable.Aggregate((youngest, target) => youngest.Birthday > target.Birthday ? youngest : target);
System.Console.WriteLine($"retAggregate = {retAggregate.Name}"); // りんく太郎
```

## [AggregateBy](https://learn.microsoft.com/ja-jp/dotnet/api/system.linq.enumerable.aggregateby)
### GroupBy＋Aggregate
```Csharp
// 年代毎の人数を集計する
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](https://learn.microsoft.com/ja-jp/dotnet/api/system.linq.enumerable.all)
###  全てTrue
```Csharp
// 若人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](https://learn.microsoft.com/ja-jp/dotnet/api/system.linq.enumerable.any)
###  一部True
```Csharp
// 若人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](https://learn.microsoft.com/ja-jp/dotnet/api/system.linq.enumerable.any)
###  最後に足す
```Csharp
var retAppend = new[] { 1, 2, 3, 4 }.Append(5);
System.Console.WriteLine($"retAppend = {string.Join(",", retAppend)}"); // 1,2,3,4,5
```

## [Average](https://learn.microsoft.com/ja-jp/dotnet/api/system.linq.enumerable.average)
###  平均
```Csharp
// 誕生日から年齢を算出する
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](https://learn.microsoft.com/ja-jp/dotnet/api/system.linq.enumerable.cast)
###  キャスト
```Csharp
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](https://learn.microsoft.com/ja-jp/dotnet/api/system.linq.enumerable.chunk)
###  分割
```Csharp
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](https://learn.microsoft.com/ja-jp/dotnet/api/system.linq.enumerable.concat)
###  連結
```Csharp
// 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](https://learn.microsoft.com/ja-jp/dotnet/api/system.linq.enumerable.count)
###  カウント
```Csharp
// 課長は何人？
var retCount = KatagakiTable.Where(r=>r.Katagaki == "課長").Count();
System.Console.WriteLine($"retCount = {retCount}人"); // 10人
```

## [CountBy](https://learn.microsoft.com/ja-jp/dotnet/api/system.linq.enumerable.countby)
###  GroupBy＋Count
名前はGroupCountじゃ駄目だったん？
```Csharp
// 肩書ごとの人数を数える。
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](https://learn.microsoft.com/ja-jp/dotnet/api/system.linq.enumerable.DefaultIfEmpty)
###  デフォルト

```CSharp
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](https://learn.microsoft.com/ja-jp/dotnet/api/system.linq.enumerable.Distinct)
### 重複削除

```CSharp
var DuplicateTable = new[] { 1, 2, 3, 2, 3, 4, 1 };
var retDistinct = DuplicateTable.Distinct();
System.Console.WriteLine($"retDistinct = {string.Join(",", retDistinct)}");
```

## [DistinctBy](https://learn.microsoft.com/ja-jp/dotnet/api/system.linq.enumerable.DistinctBy)
### キーセレクタDistinct
```CSharp
// ※肩書毎に先頭レコードが選ばれる。
var retDistinctBt = KatagakiTable.DistinctBy(r=>r.Katagaki);
System.Console.WriteLine($"retDistinctBt = {string.Join(",", retDistinctBt)}");
```

## [ElementAt](https://learn.microsoft.com/ja-jp/dotnet/api/system.linq.enumerable.ElementAt)
### 指定位置の要素

```CSharp
//var intArray = new int[] { 1, 2, 3, 4, 5 };
var retElementAt = intArray.ElementAt(3);
System.Console.WriteLine($"retElementAt = {retElementAt}"); // 4
```

## [ElementAtOrDefault](https://learn.microsoft.com/ja-jp/dotnet/api/system.linq.enumerable.ElementAtOrDefault)
### DefaultIfEmpty＋ElementAt

```CSharp
//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](https://learn.microsoft.com/ja-jp/dotnet/api/system.linq.enumerable.Except)
### 差集合 (NOT OR)

```CSharp
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](https://learn.microsoft.com/ja-jp/dotnet/api/system.linq.enumerable.ExceptBy)
### キーセレクタExcept
```CSharp
// 社長、部長、課長以外の肩書をもつ先頭レコードを抽出する。
var retExceptBy = KatagakiTable.ExceptBy(new[] { "社長", "部長", "課長" }, r => r.Katagaki);
System.Console.WriteLine($"retExceptBy = {string.Join(",", retExceptBy)}");
```

## [First](https://learn.microsoft.com/ja-jp/dotnet/api/system.linq.enumerable.First)
###  先頭

```CSharp
var retFirst = KatagakiTable.First();
System.Console.WriteLine($"retFirst = {retFirst}"); // 先頭のレコード
```

## [FirstOrDefault](https://learn.microsoft.com/ja-jp/dotnet/api/system.linq.enumerable.FirstOrDefault)
### DefaultEmpty＋First

```CSharp
var emptyRecord = new BirthdayRecord(string.Empty, DateTime.MinValue);
var retFirstOrDefault = EmptyTable.FirstOrDefault(emptyRecord);
System.Console.WriteLine($"retFirstOrDefault = {retFirstOrDefault}"); // emptyRecord
```

## [GroupBy](https://learn.microsoft.com/ja-jp/dotnet/api/system.linq.enumerable.GroupBy)
### グルーピング

```CSharp
// 肩書毎の名前を一覧する
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](https://learn.microsoft.com/ja-jp/dotnet/api/system.linq.enumerable.GroupJoin)
### Join＋GroupBy


```CSharp
// 1:N関係のテーブルをJoinしてGroupBy
var OuterTable = new Tuple<int, string>[] { new(1, "りんく太郎"), new(2, "りんく次郎") };
var InnerTable = new Tuple<int, string>[] { new(1, "C#"), new(2, "EFCore"), 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
//  りんく次郎 EFCore
```

## [Index](https://learn.microsoft.com/ja-jp/dotnet/api/system.linq.enumerable.Index)
### 連番

```CSharp
var retIndex = BirthdayTable.Index();
Console.WriteLine($"retIndex = ");
foreach (var r in retIndex)
{
    Console.WriteLine($"  {r.Index} {r.Item.Name}");  // 1 りんく太郎
}
```

## [Intersect](https://learn.microsoft.com/ja-jp/dotnet/api/system.linq.enumerable.Intersect)
### 積集合 (AND)

```CSharp
// 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](https://learn.microsoft.com/ja-jp/dotnet/api/system.linq.enumerable.IntersectBy)
### キーセレクタIntersect
```CSharp
// 課長の肩書をもつ先頭レコードを抽出する。
var retIntersectBy = KatagakiTable.IntersectBy(new[] { "課長" }, r => r.Katagaki);
Console.WriteLine($"retIntersectBy = {string.Join(",", retIntersectBy)}");
```

## [Join](https://learn.microsoft.com/ja-jp/dotnet/api/system.linq.enumerable.Join)
### 連結

```CSharp
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](https://learn.microsoft.com/ja-jp/dotnet/api/system.linq.enumerable.Last)
### 最後

```CSharp
var retLast = KatagakiTable.Last();
Console.WriteLine($"retLast = {retLast}"); // 最後のレコード
```

## [LastOrDefault](https://learn.microsoft.com/ja-jp/dotnet/api/system.linq.enumerable.LastOrDefault)
### DefaultIfEmpty＋Last

```CSharp
//var emptyRecord = new BirthdayRecord(string.Empty, DateTime.MinValue);
var retLastOrDefault = EmptyTable.LastOrDefault(emptyRecord);
Console.WriteLine($"retLastOrDefault = {retLastOrDefault}"); // emptyRecord
```

## [LeftJoin](https://learn.microsoft.com/ja-jp/dotnet/api/system.linq.enumerable.LeftJoin)
### 左外部結合
※.NET10 Preview 5 の動作です。
プリミティブな方だとNULL許可(int?)にならないのか、0になってしまいました。
```Csharp
var leftTable = new[] { 1, 2, 3 };
var rightTable = new[] { 1, 3, 4 };
var retLeftJoin = leftTable.LeftJoin // 左テーブルのLeftJoinメソッドを呼ぶ
(
   rightTable, // 右テーブル
   l => l, // 左のジョインキーセレクタ
   r => r, // 右のジョインキーセレクタ
   (l, r) => new Tuple<int, int?>(l, r) // select文
);
Console.WriteLine($"retLeftJoin = {string.Join(",", retLeftJoin)}"); // (1, 1),(2, 0),(3, 3)
```

非プリミティブならNULLになりました。
```CSharp
class IntBox
{
    public int Value { set; get; }
    public IntBox(int Value)
    {
        this.Value = Value;
    }

    public override string ToString()
    {
        return Value.ToString();
    }
}
var leftIntBoxTable = new IntBox[] { new(1), new(2), new(3) };
var rightIntBoxTable = new IntBox[] { new(1), new(3), new(4) };
var retLeftJoin2 = leftIntBoxTable.LeftJoin // 左テーブルのLeftJoinメソッドを呼ぶ
(
   rightIntBoxTable, // 右テーブル
   l => l.Value, // 左のジョインキーセレクタ
   r => r.Value, // 右のジョインキーセレクタ
   (l, r) => new Tuple<IntBox?, IntBox>(l, r) // select文
);
Console.WriteLine($"retLeftJoin2 = {string.Join(",", retLeftJoin2)}"); // (1, 1),(2, ),(3, 3)
```

## [LongCount](https://learn.microsoft.com/ja-jp/dotnet/api/system.linq.enumerable.LongCount)
### カウント(long型)
```Csharp
// 全体で何人？
var retLongCount = KatagakiTable.LongCount();
System.Console.WriteLine($"retLongCount = {retLongCount}人"); // 253人
```

## [Max](https://learn.microsoft.com/ja-jp/dotnet/api/system.linq.enumerable.Max)
### 最大

```CSharp
//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](https://learn.microsoft.com/ja-jp/dotnet/api/system.linq.enumerable.MaxBy)
### キーセレクタMax

```CSharp
//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](https://learn.microsoft.com/ja-jp/dotnet/api/system.linq.enumerable.Min)
### 最小

```CSharp
//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](https://learn.microsoft.com/ja-jp/dotnet/api/system.linq.enumerable.MinBy)
### キーセレクタMin

```CSharp
//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](https://learn.microsoft.com/ja-jp/dotnet/api/system.linq.enumerable.OfType)
### 型でフィルタ

```CSharp
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](https://learn.microsoft.com/ja-jp/dotnet/api/system.linq.enumerable.Order)
### 昇順 

```CSharp
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](https://learn.microsoft.com/ja-jp/dotnet/api/system.linq.enumerable.OrderBy)
### キーセレクタOrder

```CSharp
// 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](https://learn.microsoft.com/ja-jp/dotnet/api/system.linq.enumerable.OrderDescending)
### 降順

```CSharp
// 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](https://learn.microsoft.com/ja-jp/dotnet/api/system.linq.enumerable.OrderByDescending)
### キーセレクタOrderDescending

```CSharp
// 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](https://learn.microsoft.com/ja-jp/dotnet/api/system.linq.enumerable.Prepend)
### 先頭に足す

```CSharp
var retPrepend = new[] { 1, 2, 3, 4 }.Prepend(5);
Console.WriteLine($"retPrepend = {string.Join(",", retPrepend)}"); // 5,1,3,4
```

## [Reverse](https://learn.microsoft.com/ja-jp/dotnet/api/system.linq.enumerable.Reverse)
### 反転

```CSharp
var retReverse = new[] { 1, 2, 3, 4 }.Reverse();
Console.WriteLine($"retReverse = {string.Join(",", retReverse)}"); // 4,3,2,1
```

## [RightJoin](https://learn.microsoft.com/ja-jp/dotnet/api/system.linq.enumerable.RightJoin)
### 右外部結合

※.NET10 Preview 5 の動作です。
プリミティブな型だとNULL許可(int?)にならないのか、0になってしまいました。
```Csharp
var leftTable = new[] { 1, 2, 3 };
var rightTable = new[] { 1, 3, 4 };
var retRightJoin = leftTable.RightJoin // 左テーブルのRightJoinメソッドを呼ぶ
(
   rightTable, // 右テーブル
   l => l, // 左のジョインキーセレクタ
   r => r, // 右のジョインキーセレクタ
   (l, r) => new Tuple<int?, int>(l, r) // select文 
);
Console.WriteLine($"retRightJoin = {string.Join(",", retRightJoin)}"); // (1, 1),(3, 3),(0, 4)
```

非プリミティブならNULLになりました。
```CSharp
class IntBox
{
    public int Value { set; get; }
    public IntBox(int Value)
    {
        this.Value = Value;
    }

    public override string ToString()
    {
        return Value.ToString();
    }
}

var leftIntBoxTable = new IntBox[] { new(1), new(2), new(3) };
var rightIntBoxTable = new IntBox[] { new(1), new(3), new(4) };

var retRightJoin2 = leftIntBoxTable.RightJoin // 左テーブルのRightJoinメソッドを呼ぶ
(
   rightIntBoxTable, // 右テーブル
   l => l.Value, // 左のジョインキーセレクタ
   r => r.Value, // 右のジョインキーセレクタ
   (l, r) => new Tuple<IntBox, IntBox?>(l, r) // select文 
);
Console.WriteLine($"retRightJoin2 = {string.Join(",", retRightJoin2)}"); // (1, 1),(3, 3),(, 4)
```

## [Select](https://learn.microsoft.com/ja-jp/dotnet/api/system.linq.enumerable.Select)
### セレクト(1⇒1)
誕生日テーブルから誕生年を取得する
```CSharp
var retSelect = BirthdayTable.Select(r=>r.Birthday.Year).Distinct();
Console.WriteLine($"retSelect = {string.Join(",", retSelect)}");
```

## [SelectMany](https://learn.microsoft.com/ja-jp/dotnet/api/system.linq.enumerable.SelectMany)
### セレクト(1⇒N)
名前に使われている文字を取得する
```CSharp
var retSelectMany = BirthdayTable.SelectMany(r => r.Name.ToList()).Distinct();
Console.WriteLine($"retSelectMany = {string.Join(",", retSelectMany)}");
```

## [SequenceEqual](https://learn.microsoft.com/ja-jp/dotnet/api/system.linq.enumerable.SequenceEqual)
### 等値

```CSharp
//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](https://learn.microsoft.com/ja-jp/dotnet/api/system.linq.enumerable.Single)
### 一意にするフィルタ

```CSharp
// 指定された生年月日の人がいない
// 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](https://learn.microsoft.com/ja-jp/dotnet/api/system.linq.enumerable.SingleOrDefault)
### DefaultIfEmpty＋Single

```CSharp
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](https://learn.microsoft.com/ja-jp/dotnet/api/system.linq.enumerable.Skip)
### 前方スキップ

```CSharp
//var tableA = Enumerable.Range(1, 10);
var retSkip = tableA.Skip(5);
Console.WriteLine($"retSkip = {string.Join(",", retSkip)}"); // 6,7,8,9,10
```

## [SkipLast](https://learn.microsoft.com/ja-jp/dotnet/api/system.linq.enumerable.SkipLast)
### 後方スキップ

```CSharp
//var tableA = Enumerable.Range(1, 10);
var retSkipLast = tableA.SkipLast(5);
Console.WriteLine($"retSkipLast = {string.Join(",", retSkipLast)}"); // 1,2,3,4,5
```

## [SkipWhile](https://learn.microsoft.com/ja-jp/dotnet/api/system.linq.enumerable.SkipWhile)
### 条件満たす間スキップ

```CSharp
//var tableA = Enumerable.Range(1, 10);
var retSkipWhile = tableA.SkipWhile(r=>r < 8);
Console.WriteLine($"retSkipWhile = {string.Join(",", retSkipWhile)}"); // 8,9,10
```

## [Sum](https://learn.microsoft.com/ja-jp/dotnet/api/system.linq.enumerable.Sum)
### 合計

```CSharp
//var tableA = Enumerable.Range(1, 10);
var retSum1 = tableA.Sum();
Console.WriteLine($"retSum1 = {retSum1}"); // 55

var numArray = new object[] { 1, (double)2.3, (float)5.1, (decimal)4.4, "7.6" };
decimal ConvertNumber(object obj)
{
    if (Decimal.TryParse(obj.ToString(), out decimal num))
    {
        return num;
    }
    throw new ArgumentException();
}
var retSum2 = numArray.Sum(r => ConvertNumber(r));
Console.WriteLine($"retSum2 = {retSum2}"); // 20.4
```

## [Take](https://learn.microsoft.com/ja-jp/dotnet/api/system.linq.enumerable.Take)
### 先頭から指定数の要素を取得

```CSharp
//var tableA = Enumerable.Range(1, 10);
var retTake = tableA.Take(4);
Console.WriteLine($"retTake = {string.Join(",", retTake)}"); // 1,2,3,4
```

## [TakeLast](https://learn.microsoft.com/ja-jp/dotnet/api/system.linq.enumerable.TakeLast)
### 後方から指定数の要素を取得

```CSharp
//var tableA = Enumerable.Range(1, 10);
var retTakeLast = tableA.TakeLast(4);
Console.WriteLine($"retTakeLast = {string.Join(",", retTakeLast)}"); // 7,8,9,10
```

## [TakeWhile](https://learn.microsoft.com/ja-jp/dotnet/api/system.linq.enumerable.TakeWhile)
### 先頭から条件を満たす要素を取得

```CSharp
//var tableA = Enumerable.Range(1, 10);
var retTakeWhile = tableA.TakeWhile(r => r < 8);
Console.WriteLine($"retTakeWhile = {string.Join(",", retTakeWhile)}"); // 1,2,3,4,5,6,7
```

## [ThenBy](https://learn.microsoft.com/ja-jp/dotnet/api/system.linq.enumerable.ThenBy)
### 昇順(複数列)

```CSharp
// 誕生日の昇順 ⇒ 名前の昇順
var retThenBy = BirthdayTable.OrderBy(r => r.Birthday).ThenBy(r=>r.Name).ToList();
```

## [ThenByDescending](https://learn.microsoft.com/ja-jp/dotnet/api/system.linq.enumerable.ThenByDescending)
### 降順(複数列)

```CSharp
// 誕生日の昇順 ⇒ 名前の降順
var retThenBy = BirthdayTable.OrderBy(r => r.Birthday).ThenByDescending(r=>r.Name).ToList();
```

## [ToArray](https://learn.microsoft.com/ja-jp/dotnet/api/system.linq.enumerable.ToArray)
### 配列に変換

```CSharp
//var tableA = Enumerable.Range(1, 10);
Console.WriteLine($"tableA is {tableA.GetType().FullName}"); // tableA is System.Linq.Enumerable+RangeIterator
var retToArray = tableA.ToArray();
Console.WriteLine($"retToArray is {retToArray.GetType().FullName}"); // retToArray is System.Int32[]
```

## [ToDictionary](https://learn.microsoft.com/ja-jp/dotnet/api/system.linq.enumerable.ToDictionary)
### Dictionaryに変換

```CSharp
//var tableA = Enumerable.Range(1, 10);
var retToDictionary = tableA.ToDictionary(r=>r, r=>r+r);
Console.WriteLine($"retToDictionary is {retToDictionary.GetType().Name}"); // retToDictionary is Dictionary`2
Console.WriteLine($"retToDictionary = {string.Join(",", retToDictionary)}"); // retToDictionary = [1, 2],[2, 4],[3, 6],[4, 8],[5, 10],[6, 12],[7, 14],[8, 16],[9, 18],[10, 20]
```

## [ToHashSet](https://learn.microsoft.com/ja-jp/dotnet/api/system.linq.enumerable.ToHashSet)
### HashSetに変換
```CSharp
//var tableA = Enumerable.Range(1, 10);
var retToHashSet = tableA.ToHashSet();
Console.WriteLine($"retToHashSet is {retToHashSet.GetType().Name}"); // HashSet`1
// 重複は除去される
var retToHashSet2 = tableA.Concat(tableA).ToHashSet();
Console.WriteLine($"retToHashSet2 = {string.Join(",", retToHashSet2)}"); // 1,2,3,4,5,6,7,8,9,10
```

## [ToList](https://learn.microsoft.com/ja-jp/dotnet/api/system.linq.enumerable.ToList)
### Listに変換
```CSharp
var retToList = tableA.ToList();
Console.WriteLine($"retToList is {retToList.GetType().Name}"); // retToList is List`1

```

## [ToLookup](https://learn.microsoft.com/ja-jp/dotnet/api/system.linq.enumerable.ToLookup)
### Lookupに変換

```CSharp
// 名前の先頭文字でLookup
var retToLookup = BirthdayTable.Select(r=>r.Name).ToLookup(r => r[0]);
Console.WriteLine($"retToLookup['り'] = {string.Join(",", retToLookup['藤'])}"); // りんく太郎,りんく次郎
```

## [Union](https://learn.microsoft.com/ja-jp/dotnet/api/system.linq.enumerable.Union)
### 和集合 (OR)

```CSharp
// var tableA = Enumerable.Range(1, 10); // 1～10
// var tableB = Enumerable.Range(5, 10); // 5～14
var retUnion = tableA.Union(tableB);
Console.WriteLine($"retUnion = {string.Join(",", retUnion)}"); // 1,2,3,4,5,6,7,8,9,10,11,12,13,14
```

## [UnionBy](https://learn.microsoft.com/ja-jp/dotnet/api/system.linq.enumerable.UnionBy)
### 条件指定Union

```CSharp
// 1999年生まれ
var groupA = BirthdayTable.Where(r=>r.Birthday.Year == 1999);
// 10月生まれ
var groupB = BirthdayTable.Where(r => r.Birthday.Month == 10);
// 1999年生まれ、または10月生まれの人一覧
var retUnionBy = groupA.UnionBy(groupB, r => r.Name).Select(r=>r.Name).Order();
Console.WriteLine($"retUnionBy = {string.Join(",", retUnionBy)}");
```

## [Where](https://learn.microsoft.com/ja-jp/dotnet/api/system.linq.enumerable.Where)
### フィルタ

```CSharp
// 1999年生まれでフィルタ
var groupA = BirthdayTable.Where(r=>r.Birthday.Year == 1999);
```

## [Zip](https://learn.microsoft.com/ja-jp/dotnet/api/system.linq.enumerable.Zip)
### 順番通りに連結

```CSharp
// var tableA = Enumerable.Range(1, 10); // 1～10
// var tableB = Enumerable.Range(5, 10); // 5～14
var retZip = tableA.Zip(tableB);
Console.WriteLine($"retZip = {string.Join(",", retZip)}"); // (1, 5),(2, 6),(3, 7),(4, 8),(5, 9),(6, 10),(7, 11),(8, 12),(9, 13),(10, 14)

// 要素数が違う場合
// var tableA = Enumerable.Range(1, 10); // 1～10
var tableD = Enumerable.Range(5, 5); // 5～9
var retZip2 = tableA.Zip(tableD);
Console.WriteLine($"retZip2 = {string.Join(",", retZip2)}"); // (1, 5),(2, 6),(3, 7),(4, 8),(5, 9)
```

## その他
### [AsEnumerable](https://learn.microsoft.com/ja-jp/dotnet/api/system.linq.enumerable.asenumerable)
LINQのメソッドと同名の独自メソッドを隠蔽するために利用する。

## [Empty](https://learn.microsoft.com/ja-jp/dotnet/api/system.linq.enumerable.Empty) 
空のシーケンスを作成する。

```CSharp
var EmptyTable = Enumerable.Empty<BirthdayRecord>();
```

## [Range](https://learn.microsoft.com/ja-jp/dotnet/api/system.linq.enumerable.Range) 
整数シーケンスを作成する。

```CSharp
var RangeTable = Enumerable.Range(2, 10); // {2,3,4,5,6,7,8,9,10,11}
```

## [Repeat](https://learn.microsoft.com/ja-jp/dotnet/api/system.linq.enumerable.Repeat) 
同値シーケンスを作成する。

```CSharp
var RepeatTable = Enumerable.Repeat("hoge", 3);  // {"hoge","hoge","hoge"}
```

## [TryGetNonEnumeratedCount](https://learn.microsoft.com/ja-jp/dotnet/api/system.linq.enumerable.TryGetNonEnumeratedCount) 
Enumeratable.Count()を使わずにカウントできればカウントする。


# クエリ式

## from
` from $record in $enumerable`
CROSS JOIN： ` from $record in $enumerable [from $record in $enumerable]`

```CSharp
var query = from r in BirthdayTable 
            ...
```

```CSharp
var query = from b in BirthdayTable 
            from k in KatagakiTable
            ...
```



## where 
`where $filter_condition`

```CSharp
var query = from r in BirthdayTable
            where r.Birthday.Year == 1999 && r.Birthday.Month = 2
            ...
```


## orderby 
`orderby $order_conditon[, $order_conditon]`
`$order_conditon = record.atttribute [ascending|descending]`

```CSharp
var query = from r in BirthdayTable
            orderby r.Birthday descending, r.Name
            ...
```

## group
`group $record by $key_selector [into]`


```CSharp
var query = from r in BirthdayTable
            group r by r.Birthday.Year;
```

```CSharp
var query = from r in BirthdayTable
            group r by new { r.Birthday.Year, r.Birthday.Month };
```


```CSharp
var query = from r in BirthdayTable
            group r by r.Birthday.Year into g
            select new { key = g.Key, first = g.FirstOrDefault(emptyRecord) };
```

## join 
`join $join_record in $join_enumerable on $key_conditon`
単一キー：`$key_conditon = $record.attribute equals $join_record.attributes`
複合キー：`$key_conditon = new {key1=$record.attribute1, key2=$record.attribute2} equals {key1=$join_record.attribute1, key2=$join_record.attribute2}`

```CSharp
var query = from b in BirthdayTable
            join k in KatagakiTable on b.Name equals k.Name
            select new { b.Name, b.Birthday, k.Katagaki };
```

### outer join

```CSharp
var leftTable = new[] { 1, 2, 3 };
var rightTable = new[] { 1, 3, 4 };

var innerJoin = from left in leftTable
                join right in rightTable on left equals right
                select new Tuple<int, int>(left, right);

var leftOuterJoin = from left in leftTable
                    join right in rightTable on left equals right into rightTable2
                    from right2 in rightTable2.DefaultIfEmpty(-1)
                    select new Tuple<int, int>(left, right2);

var rightOuterJoin = from right in rightTable
                     join left in leftTable on right equals left into leftTable2
                     from left2 in leftTable2.DefaultIfEmpty(-1)
                     select new Tuple<int, int>(left2, right);

var fullOuterJoin = leftOuterJoin.Union(rightOuterJoin);

Console.WriteLine($"innerJoin = {string.Join(",", innerJoin)}");
Console.WriteLine($"leftOuterJoin = {string.Join(",", leftOuterJoin)}");
Console.WriteLine($"rightOuterJoin = {string.Join(",", rightOuterJoin)}");
Console.WriteLine($"fullOuterJoin = {string.Join(",", fullOuterJoin)}");
// innerJoin = (1, 1),(3, 3)
// leftOuterJoin = (1, 1),(2, -1),(3, 3)
// rightOuterJoin = (1, 1),(3, 3),(-1, 4)
// fullOuterJoin = (1, 1),(2, -1),(3, 3),(-1, 4)
```

## select
単項：`select $record.attribute`
複数：`select new {$record.attribute1, $record.attribute2 ... }`
複数(ラベル明示)：`select new {Name1=$record.attribute1, Name2=$record.attribute2 ... }`

## let ～

## サブクエリ

```CSharp
var subquery = from r in KatagakiTable where r.Katagaki == "課長" select r;

var query = from b in BirthdayTable
            join k in subquery on b.Name equals k.Name
            select new { b.Name, b.Birthday, k.Katagaki };
```

# Parallel LINQ (PLINQ)
前に書いた記事を載せとく。

https://qiita.com/kashin777/items/ee22b60618fcce62efb8