なぜLINQを学ぶのか
私個人の事情をいうとC#のパーサコンビネータSpracheをさわってみようとしたところ、構文がLINQを使用することが判明し、
先に今までなあなあな認識で甘えていたLINQを腰を据えて勉強しようと思った次第です。
LINQはforeachで基本的に書きなおせるため、C#を業務で使っている方でも避けて通れる道だと思いますが、
これを使えると**「できる人」っぽい雰囲気を出せる**(これ重要)のと単純にコードが綺麗になり可読性が増す(個人差があるかもしれませんが)ので
今まで「まだ覚えなくてもいいや」と思っていた人もこれを機会に覚えてしまってもいいと思います。
ですが、とっつきずらさを考えて今回は最低限覚えておきたい内容のみを記述しておきました。
それ以外は新しく記事に書こうかなと思います。
前提知識
C#のコードを書いたことがあればおおよそOKでしょう。
遅延評価の項目で書いたコードにプロパティが若干あることくらいでしょうか懸念点としては。
筆者もこの記事を書くまでLINQの知識はほとんどありませんでした。
というよりはっきりいってこの記事を書きながら覚えていってます。
2019/03/22更新しました。
他言語の関数などと比較するときもありますが、基本的にはそういうもんなんだなと思ってくれればけっこうです。
そもそもLINQとは?
wikiを見ると
様々な種類のデータ集合に対して標準化された方法でデータを問い合わせること(クエリ)を可能にするために、言語に統合された機能のことである。
けっこうざっくりですがわかりやすく書いてあります。
SQLをご存知のかたは言語機能にSQLが組み込まれたぐらいの感覚でいいと思います。
まあこれだけじゃわからないという人もこれから挙げていく例を見ればわかると思います。
また、LINQにはクエリ式とメソッド式がありますが、今回はクエリ式に焦点を当てていきます。
理由はそのほうがC#っぽいからというのとクエリ式の構文が理解できればメソッド式の構文もすぐに理解できると思ったからです。
ちなみにクエリ構文
は
var evens = from number in numbers
where number % 2 == 0
select number;
のようにfrom
に始まり、where
やselect
をつかって書かれたクエリ式による構文です。
一方、メソッド構文
は
var evens = numbers.Where(number => number % 2 == 0)
.Select(number => number);
のようにメソッドベースの構文になります。
他言語の経験がある方はメソッドベースの構文のほうが最初はしっくりくると思います。
ただしSQLと同じ感覚で行くとfrom
に該当するものを記述しないことに注意。
クエリキーワードを眺めていく
Linqを理解する一番の近道は
クエリ式を記述するのに必要なキーワードがそれぞれどのような働きをするか理解する
というのが個人的な見解です。
とりあえず公式のドキュメントを参考にいろいろまとめてみました。
*正直、from
とselect
、*where
を覚えておけばあとはなんとかなります。
SQLをご存知のかたは最初にこの3つを覚えたと思います。
以下ではその3つを紹介していきます。他のキーワードは新しく記事にするかもしれません。
from
クエリ式はfrom句
から始まります。
クエリ式ではデータソース(xs)および範囲変数(x)を導入するためにfrom句
が必要になります。
from x in xs
ここでxはxsの連続する各要素への参照です。xの型は推論されるので指定する必要はありません。
**foreachのループの似ていますがクエリ式では基本的にループは発生しないです。**これに関しては後で書きます。
select
クエリ式はselect句
かgroup句
のいずれかで終わります。
select句
では基本的にいままでのすべての句の評価から得た値に対して関数を適用します。
var ys = from x in xs
select f(x)
なにも適用しない場合はselect x
と記述すればOKです。引数をそのまま返す関数を適用しているともいえます。
他言語(Haskell, Lisp, Python, etc...)ならばmap
という関数があったりしますが役割としては同じものであると考えていいでしょう。
where
where句
は述語を各要素に適用し、真を返すような要素を集めたものを返します。
var evens = from number in numbers
where number % 2 == 0
select number;
上の例でいれば、要素numberが偶数であるか?という条件が真であるような要素をフィルタリングしています。
他言語(Haskell, Lisp, Python, etc...)ならばfilter
という関数があったりしますが役割としては同じものであると考えてもいいでしょう。
遅延評価
LINQの最大の特徴として遅延評価というものがあります。
実は基本的にクエリ式は即時に評価されるものではありません。
連続した値(シーケンス)を返すクエリではクエリ変数そのものはクエリの結果を保持しません。クエリのコマンドが保持されるだけです。
クエリ変数がforeach
などのループによって評価されるまでクエリの実行は遅延させられます。
これがいわゆる遅延評価
で、関数型言語にふれたことがある方にとってはなじみ深い言葉だと思います。
逆に変数の値が得られた時点で即座に評価されるものを正格評価
(先行評価)といったりします。
これだけじゃよくわからないよという方も、次のコードを見てもらえばだいたいこんな感じかなというのがわかってもらえると思います。
以下のコードはFooのリストからFoo.idのリストをLINQ
で取得しようとしたものです。
class MainClass
{
public static void Main (string[] args)
{
// Fooのリストを生成
var seq = new List<Foo> {
new Foo { Id = 2 }, new Foo { Id = 0 }, new Foo { Id = 1 },
};
// Fooのリストに対しクエリを発行
var query = from x in seq
select x.Id;
// ループ開始
Console.WriteLine ("--- loop start! ---");
foreach (var q in query) {
Console.WriteLine ("{0}", q);
}
}
}
public class Foo {
private int id;
public int Id {
get {
Console.WriteLine ("call get id {0}", id);
return id;
}
set => id = value;
}
}
--- loop start! ---
call get id 2
2
call get id 0
0
call get id 1
1
LINQ
が**遅延評価
ではなく正格評価
を行うならば**結果は
call get id 2
call get id 0
call get id 1
--- loop start! ---
2
0
1
となっていたでしょう。
foreach文
で書きなおそうとすればこの違いはより明確になるはずです。
また、上のコード例のclass FooでC#のプロパティを使用しました。
あまり馴染みがないという方はリンク先を参考にしてください。
ただし、単一の値を返すクエリは即時実行されます。
単一の値を返すクエリの例としてはMax
, Average
, First
, Count
などです。
単一の値を返さないクエリを即時実行したい場合はToList
やToArray
、ToDictionary
メソッドを呼びだせばOKです。
var query = (from x in seq
select x.Id).ToArray ();
上のようにqueryを書き直した場合は予想通り
call get id 2
call get id 0
call get id 1
--- loop start! ---
2
0
1
といった結果になりました。
さいごに
これでLINQ
の基本的な部分は最低限抑えられたかなと思います。
LINQ
の応用的な部分もこの基本的な部分を押さえていればすぐに理解できるはずです。
C#の記事を書いたのがはじめてであるのと、そもそも筆者のC#力がまだまだというのを考えると
けっこう変なこと言っている部分もあると思うのでどんどん指摘してもらって構いません。