ImportedLinqのススメ ~C#でも他の言語にあるMaxByやFlattenを！~

プログラミングにおいて、配列、リスト、シーケンスなどのコレクションを扱う機会は非常に多いです。「プログラミング言語でコレクションがどれくらい扱いやすいか」は重要な点だと筆者は考えます。

さてC#には、コレクション関連の言語機能として「LINQ」が、10年以上前、C# 3.0の頃からあります。そんなLINQですが、後発のプログラミング言語のコレクション機能と比べると、不足している部分があると筆者は考えました。

そんな考えから筆者は「`ImportedLinq`」というライブラリを作成しました。

この投稿では、「`ImportedLinq`」作成の背景と使用例を紹介します。

# こんなことしたいけど、LINQじゃできない

このようなクラスを使って説明します。ゲームでのロジックをイメージしてください。

```csharp
class Monster
{
    public int Level { get; private set; }
    /* 略 */
}
```

---

モンスターのリストがあります。この中から一番高いレベルの値を求めます。どんなコードを書けばいいでしょうか?


```csharp
IReadOnlyList<Monster> monsterList = LoadMonsterList();
int maxMonsterLevel = ???;
```

このような場合、LINQのMaxメソッドを使うことで、非常に簡潔に、次のコードのように1行で書くことができます。


```csharp
IReadOnlyList<Monster> monsterList = LoadMonsterList();
int maxLevel = monsterList.Max(it => it.Level);
```

このように、LINQを使えば、短く、読みやすいコードで実装できます。

---

モンスターのリストがあります。この中から「一番レベルの高いやつ」を探します。どんなコードを書けばいいですか?

```csharp
IReadOnlyList<Monster> monsterList = LoadMonsterList();
Monster maxLevelMonster = ???;
```

先ほどと似たような問題ですが、違いに注目してください。見つけたいのは、「一番高いレベル」ではなく、「一番高いレベルを持ったモンスターの要素」です。

実は、これを実現できるLINQのメソッドは残念ながらありません。

---

さて、解決策・コードを紹介する前に、次の例を示します。

モンスターのリストがあります。表示のために、先頭から8個ごとにグループ分けし、リストのリストを作りたいです。 どんなコードを書けばいいでしょうか？

```csharp
IReadOnlyList<Monster> monsterList = LoadMonsterList();

// 型は
// List<List<Monster>>？
// IEnumerable<IList<Monster>？
// IEnumerable<IReadOnlyList<Monster>？
var monsterListOfList = ???
```

残念ながら、これも一発でかけるC#・LINQのメソッドはありません。

---

次の例です。

モンスターのリストのリストがあります。 これを平滑化し、IEnumerable\<Monster\>なmonstersを作りたいです。どんなコードを書けばいいでしょうか？

```csharp
List<List<Monster>> monsterListOfList = LoadMonsterListOfList();
IEnumerable<Monster> monsters = ???
```

これはLINQを使って一行で書けます。

```csharp
List<List<Monster>> monsterListOfList = LoadMonsterListOfList();
IEnumerable<Monster> monsters = monsterListOfList.SelectMany(it => it);
```

SelectManyを使えば、リストのリストの平滑化は実現することできます。ですが、上記のSelectManyのコードは 「平滑化する」という意図が伝わりにくいと思います。


---

最後の例です。


IEnumerable\<Monster\>なmonstersがあります。 これが空かどうかを調べたいです。 どんなコードを書けばいいですか?


```csharp
IEnumerable<Monster> monsters = LoadMonsters();
bool isEmptyMonsters = ???
```


これもLINQで書けます。しかし、゙注意が必要です。

```csharp
IReadOnlyList<Monster> monsterList = LoadMonsterList(); // 良い例
bool isEmptyMonsters = !monsterList.Any();

// 悪い例
// DBから読み場合最後まで読むという無駄な処理が必要
// 無限なシーケンスだと終わらない
// bool isEmptyMonsters = monsterList.Count() == 0;
```

コメントにもある通り、IEnumerable\<T\>のシーケンスがから空かどうかの判定は、Countではなく、Anyを使うべきです。

LINQおよび配列などのメソッド・プロパティとして「空かどうかを判別する」ためのメンバは存在しません。

---

* モンスターのリストから一番高いレベルのモンスターを探したい
* モンスターのリストを先頭からn個ごとにまとめたい
* モンスターのリストのリストを平滑して、モンスターのシーケンスを作りたい
* モンスターのリストが空かどうかを判別したい

ゲーム開発、ウェブ開発、モバイル開発、デスクトップアプリ開発などにおいて、このような処理をやりたい状況があると思います。

* モンスターのリストから一番高いレベルのモンスターを探したい
* モンスターのリストを先頭からn個ごとにまとめたい

残念ながらこれらができるLINQはありません。このような処理ができるメソッドがあれば嬉しくないですか？

* モンスターのリストのリストを平滑して、モンスターのシーケンスを作りたい
* モンスターのリストが空かどうかを判別したい

これらはLINQで書けますが、もっとわかりやすくならないでしょうか？

# 他の言語にはある

* モンスターのリストから一番高いレベルのモンスターを探したい
* モンスターのリストを先頭からn個ごとにまとめたい
* モンスターのリストのリストを平滑して、モンスターのシーケンスを作りたい
* モンスターのリストが空かどうかを判別したい

Java、Kotlin、Scala、Groovy、F#、Haskellなど他のプログラミング言語にはこれらができるコレクション機能があります。

しかし、C#・LINQにはそのようなメソッドがありません。

他の言語のコレクションメソッド群と比べて 、「C#・LINQがちょっと弱いのでは？負けているのでは？」と感じてしまいました。また、もっとC#・LINQをもっと改善したい、拡充したいと思いました。

# ということで作った!

ということで作りました！

「`ImportedLinq`」というライブラリです。


『「他の言語のコレクションで一般的だけど、C#・LINQにはないメソッド」を、 他のプログラミング言語から輸入(Imported)して、 C#・LINQにフィットするようにしたコレクションメソッドライブラリ』

です。

# ImportedLinqの利用例を紹介!

それでは、「ImportedLinq」の利用例を紹介します。

---

「MaxBy」を使ってモンスターのリストから、一番高いレベルのモンスターを探すコード。

```csharp
IReadOnlyList<Monster> monsterList = LoadMonsterList();

// MaxByは一番大きい要素(複数)を探すメソッド
IReadOnlyCollection<Monster> maxLevelMonsters = monsterList.MaxBy(it => it.Level);
```

---

「Buffer」を使って モンスターのリストを、8個ごとにグループ分けするコード。

```csharp
IReadOnlyList<Monster> monsterList = LoadMonsterList();

// Bufferは先頭から指定数ごとにまとめるメソッド
IEnumerable<IReadOnlyList<Monster>> bufferedMonsters = monsterList.Buffer(8);
```

---

「Flatten」を使ってモンスターのリストのリストを平滑化し、IEnumerable<Monster>を作るコード。

```csharp
List<List<Monster>> monsterListOfList = LoadMonsterListOfList();

// Flattenは、リストのリストなどをIEnumerable<T>に平滑化するメソッド
// 「平滑化するよ」ということがメソッド名から伝わる
IEnumerable<Monster> monsters = monsterListOfList.Flatten();
```

---

「IsEmpty」を使ってIEnumerable<Monster>なmonstersが、空どうかを調べるコード


```csharp
IEnumerable<Monster> monsters = LoadMonsterList();

// メソッド名が超わかりやすい!
// 無限シーケンスでも終わるし、余分な処理しない 
bool isEmptyMonster = monsters.IsEmpty();
```

---

* モンスターのリストから一番高いレベルのモンスターを探したい
* モンスターのリストを先頭からn個ごとにまとめたい

上記の処理はLINQを使って書くことができませんが、`ImprotedLinq`を使えば一発で書くことができます。

* モンスターのリストのリストを平滑して、モンスターのシーケンスを作りたい
* モンスターのリストが空かどうかを判別したい

上記の処理は、`ImprotedLinq`を使えばより読みやすいコードで実装することができます。

# 「便利なコレクションメソッドの詰め合わせ」ではない

「`ImportedLinq`」は 「便利なコレクションメソッドの詰め合わせ」 ではありません。

『「`ImportedLinq`」は「他の言語のコレクションで一般的だけど、C#・LINQにはないメソッド」を、 他のプログラミング言語から輸入(Imported)して、 C#・LINQにフィットするようにしたコレクションメソッドライブラリ』です。

「他の言語の便利なコレクションメソッドを、C#でも使いたい」というのが目的です。

また

「他の言語に慣れている人の「C#になんでこのコレクションメソッド無いの」を解決したい」というのも目的です。

今後、他の言語で活用されていて、C#でも活躍する場面がありそうなメソッドがあれば、追加していきたいと思います。


# 他のコレクションライブラリと比べて

C#におけるコレクションライブラリ、「`ImportedLinq`」以外にもあります。たとえば次の二つです。

* Ix.NET
* MoreLINQ

`ImportedLinq`はこれらとは目的やモットーが異なります。

---

Ix.NETはRx.NETのIEnumerable<T>版です。

Ix.NETはRx.NETにないメソッドは入りません。「こんなメソッドがあればいいのに」と思ってもRx.NETになければ入らないのです。

---

MoreLINQは便利なメソッドがたくさんあるライブラリです。

覚えきれないくらいたくさんあります。しかし、現段階ではFlattenとかIsEmptyなどは実装しないそうです。詳しくは、[こちら](https://github.com/morelinq/MoreLINQ/pull/561)をご覧ください。

---

繰り返しになりますが、`ImportedLinq`は

『「他の言語のコレクションで一般的だけど、C#・LINQにはないメソッド」を、 他のプログラミング言語から輸入(Imported)して、 C#・LINQにフィットするようにしたコレクションメソッドライブラリ』

というライブラリです。

# 使ってみてね！ImportedLinq！

ソースコードは[こちら](https://github.com/RyotaMurohoshi/ImportedLinq)。他のメソッドの紹介もあります！

NuGetは[こちら](https://www.nuget.org/packages/ImportedLinq/)。

Unityでの利用は、[こちら](https://github.com/RyotaMurohoshi/ImportedLinq/releases)からunitypackageをダウンロードできます。


使ってみてください！