.NET 8でLINQに追加されたメソッド


# 概要

2023年11月に[.NET 8](https://devblogs.microsoft.com/dotnet/announcing-dotnet-8/)と[C# 12](https://devblogs.microsoft.com/dotnet/announcing-csharp-12/)がリリースされました。

この投稿では、.NET 8でLINQに追加された「[ToDictionary](https://learn.microsoft.com/en-us/dotnet/api/system.linq.enumerable.todictionary?view=net-8.0)の複数のオーバーロード」について紹介します。

昨年リリースの「.NET 7」で追加されたメソッドは、[こちら](https://qiita.com/RyotaMurohoshi/items/71a84fe7a5cf8df57054)をどうぞ！

https://qiita.com/RyotaMurohoshi/items/71a84fe7a5cf8df57054

一昨年リリースの「.NET 6」で追加されたメソッドは[こちら](https://qiita.com/RyotaMurohoshi/items/14efd4492ac14ee635cd)をどうぞ！

https://qiita.com/RyotaMurohoshi/items/14efd4492ac14ee635cd

# ToDictionaryメソッドとは？

ToDictionaryメソッドは、次のコードのようにIEnumerable\<T\>から、Dictionary\<TKey, TSource\>を作成するメソッドです。

```csharp:Skillレコード
public readonly record struct Skill(int Id, string Name);
```

```csharp:ToDictionaryの利用例
List<Skill> skills = [
    new Skill(
        Id: 0,
        Name: "ファイアー"
    ),
    new Skill(
        Id: 1,
        Name: "エルファイアー"
    ),
    new Skill(
        Id: 2,
        Name: "サンダー"
    ),
    new Skill(
        Id: 3,
        Name: "サンダーストーム"
    ),
    new Skill(
        Id: 4,
        Name: "エイルカリバー"
    )
];

var skillNameDictionary = skills.ToDictionary(it => it.Id, it => it.Name);

Console.WriteLine(skillNameDictionary[0]); // ファイアー
Console.WriteLine(skillNameDictionary[1]); // エルファイアー
```

.NET 7までは、次の4種のオーバーロードがありました。

* [ToDictionary\<TSource,TKey,TElement\>(IEnumerable\<TSource\>, Func\<TSource,TKey\>, Func\<TSource,TElement\>)](https://learn.microsoft.com/en-us/dotnet/api/system.linq.enumerable.todictionary?view=net-8.0#system-linq-enumerable-todictionary-3(system-collections-generic-ienumerable((-0))-system-func((-0-1))-system-func((-0-2))))
* [ToDictionary\<TSource,TKey,TElement\>(IEnumerable\<TSource\>, Func\<TSource,TKey\>, Func\<TSource,TElement\>, IEqualityComparer\<TKey\>)](https://learn.microsoft.com/en-us/dotnet/api/system.linq.enumerable.todictionary?view=net-8.0#system-linq-enumerable-todictionary-3(system-collections-generic-ienumerable((-0))-system-func((-0-1))-system-func((-0-2))-system-collections-generic-iequalitycomparer((-1))))
* [ToDictionary\<TSource,TKey\>(IEnumerable\<TSource\>, Func\<TSource,TKey\>)](https://learn.microsoft.com/en-us/dotnet/api/system.linq.enumerable.todictionary?view=net-8.0#system-linq-enumerable-todictionary-2(system-collections-generic-ienumerable((-0))-system-func((-0-1))))
* [ToDictionary\<TSource,TKey\>(IEnumerable\<TSource\>, Func\<TSource,TKey\>, IEqualityComparer\<TKey\>)](https://learn.microsoft.com/en-us/dotnet/api/system.linq.enumerable.todictionary?view=net-8.0#system-linq-enumerable-todictionary-2(system-collections-generic-ienumerable((-0))-system-func((-0-1))-system-collections-generic-iequalitycomparer((-1))))

一見複雑そうですが、次の組み合わせで4パターンとなります。

* キーセレクターのみ(Func\<TSource,TKey\>)を引数に取る or キーセレクター(Func\<TSource,TKey\>)とバリューセレクター(Func\<TSource,TElement\>)両方を引数に取る
* IEqualityComparer\<TKey\>を引数に取る or 引数に取らない

既存のオーバーロードは、すべてIEnumerable\<TSource\>の拡張メソッドであることに注目してください。

# 新たにToDictionaryメソッドに追加されたオーバーロードは？

新たにToDictionaryメソッドに追加されたオーバーロードは、次の4個です。

* [ToDictionary\<TKey,TValue\>(IEnumerable\<KeyValuePair\<TKey,TValue\>\>)](https://learn.microsoft.com/en-us/dotnet/api/system.linq.enumerable.todictionary?view=net-8.0#system-linq-enumerable-todictionary-2(system-collections-generic-ienumerable((system-collections-generic-keyvaluepair((-0-1))))))
* [ToDictionary\<TKey,TValue\>(IEnumerable\<KeyValuePair\<TKey,TValue\>\>, IEqualityComparer\<TKey\>)](https://learn.microsoft.com/en-us/dotnet/api/system.linq.enumerable.todictionary?view=net-8.0#system-linq-enumerable-todictionary-2(system-collections-generic-ienumerable((system-collections-generic-keyvaluepair((-0-1))))-system-collections-generic-iequalitycomparer((-0))))
* [ToDictionary\<TKey,TValue\>(IEnumerable\<ValueTuple\<TKey,TValue\>\>)](https://learn.microsoft.com/en-us/dotnet/api/system.linq.enumerable.todictionary?view=net-8.0#system-linq-enumerable-todictionary-2(system-collections-generic-ienumerable((system-valuetuple((-0-1))))))
* [ToDictionary\<TKey,TValue\>(IEnumerable\<ValueTuple\<TKey,TValue\>\>, IEqualityComparer\<TKey\>)](https://learn.microsoft.com/en-us/dotnet/api/system.linq.enumerable.todictionary?view=net-8.0#system-linq-enumerable-todictionary-2(system-collections-generic-ienumerable((system-valuetuple((-0-1))))-system-collections-generic-iequalitycomparer((-0))))

既存のオーバーロードは、すべてIEnumerable\<TSource\>の拡張メソッドでした。新たに追加されたオーバーロードは

IEnumerable\<KeyValuePair\<TKey,TValue\>\>

または

IEnumerable\<ValueTuple\<TKey,TValue\>\>

の拡張メソッドです。

次の組み合わせで4パターンとなります。

* IEnumerable\<KeyValuePair\<TKey,TValue\>\>の拡張メソッド or IEnumerable\<ValueTuple\<TKey,TValue\>\>の拡張メソッド
* IEqualityComparerを引数に取る or IEqualityComparerを引数に取らない

# どんなふうに使う？何が嬉しい？

新たに加わったオーバーロードにより、IEnumerable\<KeyValuePair\<TKey,TValue\>\>またはIEnumerable\<ValueTuple\<TKey,TValue\>\>に対して、
「`ToDictionary(it => it.Key, it => it.Value)`」のような記述を、
「`ToDictionary()`」と簡潔に記述できるようになります。

例えば、Dictionary\<TKey, TSource\>は、IEnumerable\<KeyValuePair\<TKey,TValue\>\>を実装しています。そのため、次のようにDictionary\<TKey, TSource\>をフィルタリングして、Dictionary\<TKey, TSource\>を再構成するコードが簡潔にかけます。

```csharp:ToDictionaryの利用例
// TODO もっと.NET 8っぽく書く
List<Skill> skills = [
    new Skill(
        Id: 0,
        Name: "ファイアー"
    ),
    /* 略 */
];

var skillNameDictionary = skills.ToDictionary(it => it.Id, it => it.Name);

var onlyFireSkillNameDictonary = skillNameDictionary
    .Where(it => it.Value.Name.Contains("ファイアー"))
    .ToDictionary(); // .NET 7までは、「.ToDictionary(it => it.Key, it => it.Value)」ってやってた
```


なお、新たに追加されたオーバーロードも既存のオーバーロードと同様、キーの重複に対してArgumentExceptionをスローすることに注意してください
# まとめ

.NET 8でLINQに追加された「[ToDictionary](https://learn.microsoft.com/en-us/dotnet/api/system.linq.enumerable.todictionary?view=net-8.0)の複数のオーバーロード」について紹介しました。

新たに加わったオーバーロードにより、IEnumerable\<KeyValuePair\<TKey,TValue\>\>またはIEnumerable\<ValueTuple\<TKey,TValue\>\>で、「`ToDictionary(it => it.Key, it => it.Value)`」と記述をしていた箇所を「`ToDictionary()`」と簡潔に記述できるよになりました。

個人的には、.NET 8でのLINQの追加は、「そんなに自分が活用する機会は多くないかもな〜」とも思いますが、今後の進化も楽しみです。

# リンク

* [追加PR](https://github.com/dotnet/runtime/pull/85811)
* [追加提案Issue](https://github.com/dotnet/runtime/issues/65925)
* [ToDictionary公式ドキュメント](https://learn.microsoft.com/en-us/dotnet/api/system.linq.enumerable.todictionary?view=net-8.0)