LINQ好きはLINQを書く時にどう考えているのか？

# LINQ好きですか？
最近ではLINQが書けるプログラマは大分多くなってきたと思いますが、一方でLINQに対する温度は人それぞれだと思います。「LINQは読めるけど、自分で書くとあまりLINQが出てこない」人もいれば「LINQでできることは全部LINQにしたい」人もいるでしょう。
ちなみに私は後者です。

もちろんLINQが常に絶対正義という訳でもないでしょうが、同僚がLINQを使いまくっているけど、読みにくい、慣れないと思っているですとか、LINQを使いこなしたいと思っているけど中々実戦でぱっと出てこない、という悩みがある方もいらっしゃるのではないかと思い、そうした方の一助になればと思い、この記事を書いてみました。

何でもLINQでやりたい族の人がなぜLINQにしたがるのか、どういう風に考えてLINQを組み立てているのかをサンプルコードを元に文章化してみます。

# お題
以下のお題をLINQを使わない昔ながらの実装と、LINQを使った実装で書いてみます。

> パス「C:\temp」の子フォルダの中のファイルのうち、拡張子「txt」と「log」ものを全て列挙する

# 実装例
LINQを使わないパターンでよく見かけるのが、以下のような実装です。
 
```csharp

var extensions = new[] { ".txt", ".log" };
var files = new List<string>();

foreach (var directory in Directory.GetDirectories(@"C:\temp"))
{
    foreach (var file in Directory.GetFiles(directory))
    {
        if (extensions.Contains(Path.GetExtension(file)))
            files.Add(file);
    }
}
```

まず「C:\temp」直下のフォルダをforeachで列挙して、そのフォルダでGetFilesしたファイルをforeachで列挙。拡張子が関係ないものはif文で弾く、という感じ。
foreachがネストするので、ちょっとインデントが深くなるところがイヤな感じです。
一方で、LINQを使った例としては、以下のような実装があります。

```csharp
var extensions = new[] { ".txt", ".log" };

var files = Directory.EnumerateDirectories(@"C:\temp")
                     .SelectMany(d => Directory.EnumerateFiles(d))
                     .Where(f => extensions.Contains(Path.GetExtension(f)))
                     .ToArray();
```

LINQ使いならGetFilesじゃなくてEnumerateFilesですよね（？）
foreachを使う例に比べると、かなりあっさりした感じの実装になります。文的には２行だけ。
（extensionsは配列じゃなくてHashSetの方が役割とか考えるといい気もしますが、本筋と離れるのでとりえずこれで）

で、LINQが書きたいのに上手く書けないという人は、たぶん頭の中には１番目の実装が浮かんでいる状態なのだと思います。すでに１番目の実装が頭に浮かんでいるのに、それを２番目の実装に「翻訳」しようとすると、結構ストレスになるのではないかなと思います。わざわざ書き直しているような感じになるので。

人によるかもしれませんが、私の場合、コードを書いている時に１番目の実装というのはほとんど意識に浮かんでいなくて、いきなり２番目の実装で書いている感じです。どういう思考プロセスで２番目の実装が組み立てされるのかを文章にしてみます。

# LINQで書く時の思考プロセス
## 問題文の一番左をコードに書き始める

問題文を左から読んだ順番にコードにしていきます。まずは問題文の『パス「C:\temp」の子フォルダ』というところを、以下のコードで表現してみます。

```csharp
Directory.EnumerateDirectories(@"C:\temp") // 戻り値 IEnumerable<string> （フォルダのリスト）
```

## 後ろにとりあえずSelectを続けてみる
当然ながらEnumerateDirectoriesだけだと本来ほしいファイルのデータにならないので、とりあえず後ろにSelectを続けてみます。割と「とりあえずSelectして様子見る」的な感じがあり、Selectしてラムダ式に渡ってきた変数の型を見ながら、続きがどうなればいいのかを考えたりしています。
さて、問題文の続きは『パス「C:\temp」の子フォルダの中のファイルのうち』なので、Selectした先でファイル列挙してみます。

```csharp
Directory.EnumerateDirectories(@"C:\temp")
         .Select(d => Directory.EnumerateFiles(d)) // 戻り値 IEnumerable<IEnumerable<string>>　（フォルダごとのファイルリストのリスト）
```
ここで、IntelliSenseなどで戻り値をチェックすると、EnumerateFilesの戻り値がIEnumerable（というか複数）なので、最終的な戻り値はIEnumerable&lt;IEnumerable&lt;string&gt;&gt;になっていることが分かります。しかし、最終的にほしいのは「リストのリスト」ではなくただのリストなので、「あー、Selectじゃないな」と考え直します。大抵の場合、とりあえずSelectで書いて進めて、Selectした先が１対１ならそのままにし、予期せず１対ＮになったらSelectManyに替えてやり直す、みたいな感じのプロセスでやっています。

## SelectManyに替えてみる

という訳で、SelectだったところをSelectManyに替えてみます。

```csharp
Directory.EnumerateDirectories(@"C:\temp")
         .SelectMany(d => Directory.EnumerateFiles(d)) // 戻り値 IEnumerable<string>　（ファイルのリスト）
```

戻り値がIEnumerable<string>になったので、ほしい結果の型と一致していていい感じな気がします！

## 絞り込み条件を加える
上の状態になると、全部のファイルを列挙することはできているので、後は絞り込みを追加するだけです。残るお題の部分『パス「C:\temp」の子フォルダの中のファイルのうち、拡張子「txt」と「log」ものを全て列挙する』を実現するため、Whereでいらないデータを弾きます。

```csharp
Directory.EnumerateDirectories(@"C:\temp")
         .SelectMany(d => Directory.EnumerateFiles(d))
         .Where(f => new[] { ".txt", ".log" }.Contains(Path.GetExtension(f)))
```

これで概ね完成です。
後は、拡張子の配列を毎回作るのが無駄なのでLINQの上に持ってきたり、ToArrayして結果を変数で受けたり、とすれば回答例の状態になります。

# まとめ：LINQは思考とコードの流れが一致する
私がLINQが好きな理由の１つが、LINQを使うと問題文を左から右に素直にコードにしていけば実現できることが多いためです。LINQを使わない場合foreachのネストになるので、試行錯誤の過程でコードが上に行ったり下に行ったり、結構広い範囲を移動しないといけなくなることがあります。LINQの場合は、比較的狭い範囲の行き来で済むことが多く、コードを書くことに集中できる感じがします。また、変数の間違いが少ないのも集中が削がれにくい要素です。
LINQを使わない場合、間違えてリストに積むのをfileではなくdirectoryにしても（さすがにそんな間違いはあまりしないでしょうが、例えばの話として）、コンパイル時にエラーで教えてくれることはありません。
LINQの場合、EnumerateDirectoriesで列挙された「d」とEnumerateFilesで列挙された「f」はスコープが違うので、間違って使うことはありません。
（だからこそ変数名を「d」のように短くしてもリスクが少ないです）
変数間違いを気にしなくていいのは、やりたいことに集中ができて気持ちがいいです。

# 最後に
コードを書く時の思考を文章化するというのは初めてやったので、読んでくださった方の参考になったか分からないですが・・・
LINQを使いこなしたいと思っている方の役に、少しでも立てれば幸いです。