LoginSignup
15

More than 3 years have passed since last update.

やさしいLINQ【超入門編】

Last updated at Posted at 2018-02-02

なぜLINQを学ぶのか

私個人の事情をいうとC#のパーサコンビネータSpracheをさわってみようとしたところ、構文がLINQを使用することが判明し、
先に今までなあなあな認識で甘えていたLINQを腰を据えて勉強しようと思った次第です。

LINQはforeachで基本的に書きなおせるため、C#を業務で使っている方でも避けて通れる道だと思いますが、
これを使えると「できる人」っぽい雰囲気を出せる(これ重要)のと単純にコードが綺麗になり可読性が増す(個人差があるかもしれませんが)ので
今まで「まだ覚えなくてもいいや」と思っていた人もこれを機会に覚えてしまってもいいと思います。

ですが、とっつきずらさを考えて今回は最低限覚えておきたい内容のみを記述しておきました。
それ以外は新しく記事に書こうかなと思います。

前提知識

C#のコードを書いたことがあればおおよそOKでしょう。
遅延評価の項目で書いたコードにプロパティが若干あることくらいでしょうか懸念点としては。

筆者もこの記事を書くまでLINQの知識はほとんどありませんでした。
というよりはっきりいってこの記事を書きながら覚えていってます。

2019/03/22更新しました。

他言語の関数などと比較するときもありますが、基本的にはそういうもんなんだなと思ってくれればけっこうです。

そもそもLINQとは?

wikiを見ると

様々な種類のデータ集合に対して標準化された方法でデータを問い合わせること(クエリ)を可能にするために、言語に統合された機能のことである。

けっこうざっくりですがわかりやすく書いてあります。
SQLをご存知のかたは言語機能にSQLが組み込まれたぐらいの感覚でいいと思います。
まあこれだけじゃわからないという人もこれから挙げていく例を見ればわかると思います。

また、LINQにはクエリ式とメソッド式がありますが、今回はクエリ式に焦点を当てていきます。
理由はそのほうがC#っぽいからというのとクエリ式の構文が理解できればメソッド式の構文もすぐに理解できると思ったからです。

ちなみにクエリ構文

numbersから偶数を取り出すクエリ式
var evens = from number in numbers
            where number % 2 == 0
            select number;

のようにfromに始まり、whereselectをつかって書かれたクエリ式による構文です。
一方、メソッド構文

numbersから偶数を取り出すメソッド式
var evens = numbers.Where(number => number % 2 == 0)
                   .Select(number => number);

のようにメソッドベースの構文になります。
他言語の経験がある方はメソッドベースの構文のほうが最初はしっくりくると思います。
ただしSQLと同じ感覚で行くとfromに該当するものを記述しないことに注意。

クエリキーワードを眺めていく

Linqを理解する一番の近道は
クエリ式を記述するのに必要なキーワードがそれぞれどのような働きをするか理解する
というのが個人的な見解です。

とりあえず公式のドキュメントを参考にいろいろまとめてみました。

正直、fromselectwhereを覚えておけばあとはなんとかなります。
SQLをご存知のかたは最初にこの3つを覚えたと思います。
以下ではその3つを紹介していきます。他のキーワードは新しく記事にするかもしれません。

from

クエリ式はfrom句から始まります。
クエリ式ではデータソース(xs)および範囲変数(x)を導入するためにfrom句が必要になります。

from
from x in xs

ここでxはxsの連続する各要素への参照です。xの型は推論されるので指定する必要はありません。
foreachのループの似ていますがクエリ式では基本的にループは発生しないです。これに関しては後で書きます。

select

クエリ式はselect句group句のいずれかで終わります。
select句では基本的にいままでのすべての句の評価から得た値に対して関数を適用します。

selectの簡単な例~関数fの適用~
var ys = from x in xs
         select f(x)

なにも適用しない場合はselect xと記述すればOKです。引数をそのまま返す関数を適用しているともいえます。
他言語(Haskell, Lisp, Python, etc...)ならばmapという関数があったりしますが役割としては同じものであると考えていいでしょう。

where

where句は述語を各要素に適用し、真を返すような要素を集めたものを返します。

numbersから偶数を取り出すクエリ式(再掲)
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などです。
単一の値を返さないクエリを即時実行したい場合はToListToArrayToDictionaryメソッドを呼びだせばOKです。

ToArrayで正格評価
var query = (from x in seq
             select x.Id).ToArray ();

上のようにqueryを書き直した場合は予想通り

ToArrayで正格評価
call get id 2
call get id 0
call get id 1
--- loop start! ---
2
0
1

といった結果になりました。

さいごに

これでLINQの基本的な部分は最低限抑えられたかなと思います。
LINQの応用的な部分もこの基本的な部分を押さえていればすぐに理解できるはずです。

C#の記事を書いたのがはじめてであるのと、そもそも筆者のC#力がまだまだというのを考えると
けっこう変なこと言っている部分もあると思うのでどんどん指摘してもらって構いません。

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
15