こんにちは。ゆとり(@yutori_techblog)です。
C#初心者の皆さん、LINQはC#のなんか特別な機能だと思って敬遠していませんか?
実は、LINQと呼ばれているのはC#の特別な機能でもなんでもなく、ただの「拡張メソッドの集まり」です。メソッドですメソッド。何も怖がる必要はありません!
目次
LINQはただの拡張メソッド!
LINQはただのIEnumerable<T>の拡張メソッドの集まりなので、とても簡単です。
有名なものからそのことを確認していきましょう。
Where拡張メソッド:抽出する
Whereはソースとなるコレクションから任意の条件で抽出した結果を返すIEnumerable<T>の拡張メソッドです。
定義は次のとおりです。

非常に長くてわかりづらいですね…。
このわかりづらさも、LINQが敬遠される理由の一つと思います。
しかし、実際はただの「拡張メソッド」です!
次のように分解するとわかりやすいでしょう。

もう少し噛み砕くと次のようになります(①〜④の順番に読んでください)。

Whereはコレクションを「任意の条件で抽出する」メソッドでした。
このとき、②で指定したメソッドが抽出条件となります。
すなわち、コレクションの一つひとつの要素を、②で指定したメソッドの引数にして②のメソッドを実行し、戻り値がtrueとなるものを抽出する、ということになります。
実際の使用例は次のようになります。
List<int> sourceList = new List{ 2, 4, 9, 5, 13, 27, -2 };
IEnumerable<int> extractedList = sourceList.Where(n => n > 10); //10より大きい要素を抽出
//13, 27
ここで気になるのがWhereの引数にある「n => n > 10」という表記ですよね?
ここには「メソッド」を指定するはずです。どうしてこのような表記が可能なのでしょうか?
メソッドを簡単に表記できる「ラムダ式」
「n => n > 10」という表記は「ラムダ式」といって、メソッドを簡単に表記することができる仕組みです。
普通だったら、メソッドにはひとつひとつ名前をつけて定義しないといけません。
しかし、1回しか利用しない短いメソッドなどは毎回名前をつけて定義するのが面倒です。
そこで、メソッドにわざわざ名前をつけて定義しなくても、その場で匿名で定義することができる仕組みが「ラムダ式」です。
特徴的な「=>」は見た目そのまま「矢印」です。
「矢印」左側がメソッドの引数で、右側がメソッドの戻り値を表します。
(引数)=>(戻り値)の形で定義する。
ラムダ式は、読み方を工夫するとわかりやすいです。
正式な読み方があるのかどうかはわかりませんが、僕は「nを入れたらn>10が出てくる」と読んでいます。
コレクションの要素をひとつずつ入れて、n>10かどうかを判定し、条件に合致するものだけを抽出するということですね。
ちなみに、「n」は引数の名前で、なんでも好きな名前をつけることができます。
「なぜ宣言していないのに、勝手にnという文字を使っていいのか」疑問に思う方もいると思いますが、これはコンパイラが勝手に型を推論してくれるために、わざわざ宣言する必要がないためです。
Whereメソッドの引数は、Func<TSource, bool>でしたよね?
つまり、引数はTSource(ソースコレクションの型引数)と確定しています。
そのため、引数の型をわざわざ指定するまでもないのです。
ラムダ式を使わないことももちろんできる
LINQはラムダ式を多用しますが、ラムダ式を使用せずに次のようにすることももちろん可能です。
bool ExtractValue(int n){
return n > 10;
}
List<int> sourceList = new List{ 2, 4, 9, 5, 13, 27, -2 };
IEnumerable<int> extractedList = sourceList.Where(ExtractValue);
//13, 27
この例では、正当に(?)「コレクションの型(この例ではint型)を引数にとり、bool型を返すメソッド」をWhereの引数に指定しています。
もちろんこれでも動作しますが、Whereメソッドを使えば使うほど、細かいメソッドが大量にできてしまいます…。
このように、きちんと「メソッド」自体を指定することもできますが、LINQは使えば使うほど細かいメソッドが大量にできてしまうため、一般的にはラムダ式を使用するようにします。
Select拡張メソッド:変換する
SelectもLINQで非常によく使う拡張メソッドの一つです。
Selectは、ソースのコレクションの一つ一つの要素に一定の処理をかけて別の値に変換し、その結果をIEnumerable<T>として返します。
Select拡張メソッドの定義は次のとおりです。
public static IEnumerable<TResult> Select<TSource, TResult> (this IEnumerable<TSource> source, Func<TSource, TResult> selector);
どうですか?Whereとほぼ同じであることが分かりますでしょうか?
SelectもWhereと同様にして、メソッドを引数にとりIEnumerableを返す、IEnumerableの拡張メソッドとして定義されています。
異なる部分は引数Funcの戻り値がboolではなくTResultとなっています。
TResultというのは型引数なので、Whereのように戻り値が指定されていません。
つまり、任意の型に変換できるということになります。
実際の使用例は次のようになります。
List<int> sourceList = new List{ 2, 4, 9, 5, 13, 27, -2 };
IEnumerable<float> circleAreas = sourceList.Select(r => r * r * 3.14f);
元のint型コレクションの値を半径として、それぞれの円の面積をで1行で求めることができました。
また、元のコレクションはint型だったのに対して、出力されたコレクションは型がfloat型になっています。
このように、Selectメソッドで異なる型に変換できていることがわかります。
Selectメソッド自体の戻り値も、それに伴ってIEnumerable<float>となっていることにも注目してください。
LINQの頻出パターン
上で説明した2つの拡張メソッドは、LINQで非常によく使うメソッドです。
そして、その両方が、
- ソースをIEnumerableで受け取る
- それに引数で指定したメソッドをかけて何らかの処理を施す
- その結果を再びIEnumerableとして返す
というパターンになっていることがわかると思います。
LINQにはWhere、Select以外にも様々なメソッドが用意されていますが、その多くはこのパターンに当てはめることができます。
これに慣れてくると、LINQへの嫌悪感(?)もかなり減ってくるのではないでしょうか。
メソッドチェーン
LINQの素晴らしい点が「メソッドチェーン」です。
メソッドチェーンとは、メソッドをチェーンのように繋げ、連続して実行することです。
どういうことかというと、WhereもSelectも、IEnumerable<T>を返すメソッドでしたよね?さらにそれらは、IEnumerable<T>の拡張メソッドとして定義されていましたよね?
ならば、Whereの戻り値に、さらにSelectをかけ、その結果をさらにWhereし、…といったように、無限にメソッドをつなげることができます。
これがメソッドチェーンです。
もっともポピュラーな使い方は、Whereでフィルタリングした結果にSelectで一定の処理をかけた結果を得る次のようなパターンです。
List<int> sourceList = new List{ 2, 4, 9, 5, 13, 27, -2 };
IEnumerable<float> circleAreas = sourceList.Where(r => r > 0)
.Select(r => r * r * 3.14f);
先ほどと同様、int型の値を半径として円の面積を計算するコードですが、半径が-2といった不正な値でも構わず計算してしまっていました。
そこで、Selectの前にWhereを噛ませて正の値のみでフィルタリングすることで、半径の値が正常であるもののみ面積を得ることができます。
もっともっと複雑な操作をしたい場合でも、LINQを使えばメソッドチェーンで順番に一連の流れとして処理することができるため、非常に分かりやすくコードを書くことができます。
まとめ
いかがでしたでしょうか?
LINQは一見複雑で難しそうに見えますが、実はただのIEnumerableの拡張メソッドの集まりだったことがお分かりいただけたと思います。
そして、多くのLINQメソッドが「IEnumerableをソースとして受ける」→「中身の要素を一つずつ引数のメソッドにかけて何らかの処理をする」→「結果をまたIEnumerableとして返す」という流れで使用できるため、1つ使い方を覚えてしまえば、あとは簡単に使いこなせるようになるでしょう。
この記事が皆様のお役に立てたら幸いです。