オラァ!(挨拶)

世間にある「LINQ入門」が解り辛くて仕方がない。アホどもが。
クソを読まされる初心者の気持ちにもなってみろゴミクズ。

この記事はニュービー、中級者、あるいはなんちゃって上級者を対象としている。

そして、この記事はあきれて物も言えなくなるほど長い。分けてもいいんだけど、なんとなくストーリー性があるから切らないで一本糞垂れ流す。どこまで読んだか分からなくなったら最初っから読み直せ。

※玄人へのおことわり※
いろいろ端折ってるんだからオメーこまけぇ話しにいちいち突っ込むんじゃねぇよ暇人が。シバきまわすぞ。

じゃあ行くぜ

◆追記:2020/2/27

LINQの可読性について補足記事を書いたよ。

【C#】LINQの可読性

◆まず

Q:LINQは便利なの?
⇒超便利。便利っていうか、使用する利点が多い。語弊ありありだが「foreachより便利」といえば伝わるだろうか。

Q:LINQってSQLと関係あるの?
⇒ないよ(あるよ)。むしろ邪魔っけだからSQLと絡めて考えるのはやめよう。ただし、標準的なSQLについて背後に横たわる集合論とかの宇宙まで完全に理解してるんなら絡めて考えてもいいよ。

Q:LINQってなんか書き方が二通りあるように思えるんだけど?
⇒あるね。あるけど全部忘れていいよ。一から教えるから。そして、この記事では片一方の書き方しか教えません。なぜならそれで事足りるからだ。

じゃあやっていく。

コードを試してみたい人は以下から好きなように。

◆LINQはじめ

LINQこわいかもだけど、別に謎の構文ってわけではない。単なる関数です。メソッドとかファンクションとか呼ばれてるやつと変わらない。LINQという大層な名前が付いているがそれは単なるラベルだ。そしてLINQが対象とするデータは「集合」とその仲間たち。身近な表現で言うとコレクション、リスト、配列とか。

※厳密には色々ちがってるんだけど、気にすんな。後で説明させていただく。

一番手近な機能であるSelectメソッドでは「あるデータの集まり」を加工して「あるデータの集まり」を生成する。

こちらをご覧ください。
※「hogeList」を使っている場所を意識すると見やすいだろう。
※細かい説明は後でしているから、今理解しようとしなくてもいい。今後「ん?」と思ったら、とりあえず読み進めてみてくれ。読み進めた結果説明がなかったらごめん。コメントしてくれ。

うん。文字列から文字列に変換している。

▼説明

  • List
    いわばコレクション。今回で言うとstringの集まり。
  • IEnumerable
    LINQ学習における大いなる謎のひとつ。
    これは「繰り返すことが出来る」という性質を表現する。変数に「型」を指定したことは多々あるだろうが、そうではなく性質を指定しているわけだ。
    今までListに対してforeachとか使ってきたと思うんだけど、foreachにかけられるオブジェクトは実はこっそりIEnumerableの性質を持っていた。
  • .Select
    LINQでました。今回の例では、hogeListの内容それぞれについて「っくす」を接続している。hogeListの内容は書き変わっていない。
    例えば、「.ToString()」っていうメソッドを使ったことあるだろう。それと同じノリだ。ToStringは対象の変数自体を文字列にするわけではなく、新しい文字列を生成して返却しているでしょ?その考え方はSelectも同じ。
    元がListで、つまり複数であるわけだから、結果も複数になる。それをIEnumerableな変数で受け取っている。
  • x => x + “っくす”
    これが良くわかんないんでしょ?多分。

◆LINQを知るにあたって

じゃあ、まず元のサンプルをforeachに書き直してみる。

はい。
foreachの中に、xってあるっしょ?
さっきのサンプルと見比べてみてほしい。xはそれぞれ対応している。

例えば

要素の集まり全部に対して操作をしたいんですが?

っていう欲求がforeachじゃないっすか。
で、要素の集まりに対して操作をするんだから、要素の内容を参照できなければいけないわけだよ。その「要素」を参照するための「目印」、言い換えると「変数」が必要になるわけだ。

それが x
数学野郎に説明すると、いわば「関数f(x)」における「x」だ。射影するための関数に渡す引数だ。

▼いや、x => x + “っくす” ってなに

以下の命令を考える。

任意の文字列 x を引数にとり、文字列「っくす」を接続して返却する。

これを極限まで短縮して表現しようと思ったらどうなるだろうか。

その答えが「x => x + “っくす”」だった。これはC#では関数として扱われる。(後述)

そして「x => x + “っくす”」が関数なんだとしたら、さっきの.Selectは「関数を引数に取っていた」ということになる。どういうことだ。関数を引数に動く関数とは。

LINQとソウルメイトになるためには、関数宣言の方法について理解しなければならない。

▼関数をとりまわす

LINQの事はいったん忘れろ。(LINQとは独立した、)LINQで使うことのできる部品についての話をする。

C#において

  • 関数を処理中で宣言することができる。
  • 関数を変数に入れることができる。受け渡しができる。

の二点をまず了解してくれ。

以下のコードを読んでみそ。

★参考:未確認飛行 C:ラムダ式

「Func<string, string>」は「文字列を貰って文字列を返す」

また、例にはないけど「Func<int, string, string>」は「数値と文字列を貰って文字列を返す」

そういうルールだ。

そして、変数「fn4」に代入されている式に見覚えがあるだろう。今回は「fn4」という名前を付けたわけだが、さっきのLINQでは名前すら付けずに運用していたな。

つまるところ、.Selectの正体は「引数として受け取った変換関数を、すべての要素に対してぶっかけうどん」だと説明する事が出来るだろう。

LINQメソッド内で指定されたxの型は、指定してやるまでも無く自動で決定されている。今回は操作対象のListがstringであるから、xの型もstringであると理解されるわけだ。

変数名はxである必要はなくて、 hoge でも fuga でもなんでもいい。お前が決めろ。「x」が使用される理由としては、つまりどんな名前でもいいからだ。適応範囲、すなわちスコープが狭い変数に関しては複雑な名前を付ける必要はない。一文字でいい。

▼そういう事だったのね

そうなんですよ。

▼なんで結果がListじゃなくてIEnumerableなの

うむ。

IEnumerableは「性質」だという話をしたよね。Listもその性質を持っている。とても簡単に言うと、ListよりもIEnumerableのほうが用途が広いからIEnumerableを返却している。

※追記:「用途が広いから」じゃなくて、正しくは「用途が限られてるから」だな。まぎゃくだな。できることが限られているコードのほうが読みやすいよ。ついでに言及しておくと、IEnumerableである理由は実装上の都合でもある。yieldとか使うアレだ。オレオレLINQメソッド書けるくらい強くなりたいなら自分で調べろ。

それで

「LINQはListに対して使用できる」という理解は間違っている。
正しくは

LINQはIEnumerableという性質を実装しているオブジェクトに対して使用できる

ということだ。これは何を意味しているのかっていうと

LINQの結果に対してLINQをぶっかけることが出来る

ということになる。

▼それってつまり

こういうことが可能だ。

Selectの結果に対して更にSelectをかけることができる。

今はSelectしか道具がないからアレだけど、道具が増えるとロマンチックになってくる。

こういった風にメソッドの結果を次のメソッドの引数にすることを「メソッドチェイニング」と呼ぶ。チェーン(鎖)をつないでいくようなイメージだな。

◆勉強:var

変数の宣言が冗長であるから、実際のコードはこうなる。

OKですか?

変数宣言をしていた場所に「var」が現れたな。

varは型の指定を省略する。

varは対応する変数の型が「推測可能」であるときに使用できる

もっと言うと、右辺の結果が静的にわかるのであればvarを使用することが出来る。varは、バンバン使え。

Wikipedia:型推論

型は変数にマウスオーバーすればすぐにわかる。C#はVisual Studioとかいう超絶優秀好評絶賛最高無料なIDEを有している。Visual Studio(とCode)以外でC#の開発をする変態はいないし、いたら殴り殺すなりペンチで前歯を折るなりするべきだ。だから惜しみなくvarを使え。

varを使う利点は・・・

という説明をしてもいいが、しない。

ただ言いたいことは「varはC#みてぇな強い静的型付け言語でのみ使用する」ということだ。dynamic?なにそれ。

とにかく、動的型付けの言語で型指定をしないと脱糞しそうになるから注意だ。何言ってるかわかんないなら気にしなくてもいいけど。

◆勉強:LINQとIEnumerable

ちょっと嘘ついた。許して亭ゆるして

LINQは「IEnumerable<T>」に対して実行できます。単なる「IEnumerable」にはLINQ使えないです。この「<T>」はすなわち任意の型を表現している。文字列であれば「IEnumerable<string>」だし、整数値であれば「IEnumerable<int>」だし。

そういう、「任意の型を内包する型やインターフェース」、言い方を変えると「型をパラメーターとして受け取ることの出来る型やインターフェース」が存在している。ジェネリクスクラスとかジェネリクスインターフェースという呼ばれ方してますね。

とにかく、ただの「IEnumerable」しか実装していない野郎には「.Cast<T>()」メソッドをかけてやれ。<T>のTにはお目当ての型を入れろ。CastもTをパラメーターとして受け取ることが出来るんでジェネリクスメソッドだな。

◆勉強:順序

適当にLINQで加工した結果の順序は、保証されないと考えるべき。今回の出力結果は「hogeList」にAddした順序であったが、別にそれを保証しているわけではない。ただ、勝手にごちゃごちゃにすることもない。
今回の例ではAddした順番以外にはなり得ない。だが、まぁ注意しておくこと。

例えば「LINQまわりを超高速で処理できるアルゴリズム」が新たに発見されたときに、そのアルゴリズムが元のIEnumerableの順序を保証しないかもしれない。(そんなことしたら全世界のプロジェクトがプチ発火するから)ありえない話だけど、意味合い的にはおかしくない話だ。

後で説明するが、OrderByというLINQメソッドを使えばソートをかけることが出来る。TakeとかSkipとか、順序に依存させたいメソッドはOrderByで確定させてから利用すること。

◆Select

さっきも使っていた「Select」は「データ変換」の機能を提供する。

さっきは「stringのListから新たなstringのIEnumerableを生成」した。
そして、データ変換であるのでStringクラス以外のオブジェクトも処理対象となる。

例えば

  1. Humanクラス(NameとAgeを持つ)を定義し
  2. そのListを作り
  3. Listの内容を読み込んでstringのIEnumerableを生成

してみると以下の通りになる。

理解できるだろうか?
読み込めばわかるはずだ。頑張れ。

そして、逆に文字列からHumanのインスタンスを生成しようとしたらこうなる。

紹介しておいてなんだけど、これは脆弱なコードだから参考にしない事。
あくまで「Select」を使用して新しいIEnumerable<Human>を生成できたってことを理解してくれ。

◆勉強:文字列挿入

var results = humanList.Select(x => x.Name + " " + x.Age.ToString() + "歳");

var results = humanList.Select(x => $"{x.Name} {x.Age}歳");

と書くことが出来る。ToStringが消えて幸せ。…元からいらないって?でも…それ何かモヤっとします。いやです。

いらんかもしれんが、以下に例を置いておく。

◆Where

「Where」は「フィルタ」の機能を提供する。
ある集合のうち、必要な要素だけを取り出す。

以下の例は「Ageが3以上のHumanを文字列に変換して返却」している。

わざとちょっと読みにくくしているんだけど、意味は理解できただろうか。
「x =>」の右側に「x.Age >= 3」と書かれている。
「x.Age >= 3」の意味は理解できるだろう。

すなわち「ある条件がtrueとなる要素だけを取り出したい」時、Whereには「ある条件」を指定してやればいい。今回の場合は数値の比較をしただけであるが、文字列の比較でもなんでもいい。
別に知らなくてもいいけど、以下のような書き方ができる。

.Where(x => x.Name.Contains(‘四’))で事足りるんだけど、つまりはbool値をreturnできればいいということだ。

Whereの後に続くSelectには、Whereでフィルタされた結果のみが渡される。

◆勉強:条件式

.Where(x => x.Age >= 3)って読みにくいかもな。

だから

.Where(x => (x.Age >= 3))って書いてもいいよ。書かなくてもいいよ。

◆勉強:型版Where

「OfType<T>()」メソッドを使えば「ある型とみなせるものについて、フィルタしながら変換する」という事が出来る。正確には変換ではないが。

var hoge = hogeBaseList.OfType<Hoge>().Select(x => x.fuga);

基底クラスやらインターフェースのリストがあったときに、任意の継承クラス/実装クラスのみを取り出すことが出来る。

この説明で伝わるんだろうか。伝われ。

互換性判定には「is句」が使用される。つまりnullは無視される。

※今後Null安全が導入されたときにどうなるかは知らん。

◆勉強:遅延評価と再評価のコスト

LINQは遅延評価です。

いままでIEnumerable<string>をLINQで生成してきたけど、お前が想像しているより面白い動きをしている。

それはつまり

LINQメソッドは値が必要になるまで実行されない

という性質だ。

いままで変数「results」を宣言しながらLINQの結果を詰めていた気になっていただろう。しかし、「var results」したときはまだresultsの中身は計算されていないのだ。

foreach (var s in results)で、いざ値を使うぞという段階で初めてLINQメソッドが動く。

遅延評価はなんやかんや便利なんだけど、まず味になる事もある。
以下を照覧あれ。

脳内でシミュレーションしてくれてもいいけど、解らん人は動かしてみれ。

結果は

◆1回目
四郎 4歳
◆2回目
四太郎 1歳
四郎 4歳

となる。

つまり変数「results」に入っているのは

  1. 処理対象
  2. 処理手順

ということになる。「処理結果」ではない。

なんとなく嗅覚のある人は「まずい」と思うだろう。同じ変数に対して同じ関数を実行しているのに、タイミングにより違う結果が帰ってくる。これはまずい。バグが発生したときにその原因を追うのが難しくなる。
だから、LINQの処理対象の中身は書き換えないようにするのが無難だ。

「results」って変数名もどうなの?って気持ちだ。「処理結果そのもの」じゃないもんね。裏切られたような、実際問題そんなに関係ないような。
まぁ…とりあえず変数名に関しては「~s」でいいよ。でも「results」とかいう情報量のない名前じゃなくて、ちゃんと意味の通る名前を付けてあげてね。

▼再評価のコスト

変換結果の集合ではない「results」を対象としたforeachが呼ばれるたび、LINQメソッドのWhereやSelectが動く。

だから、呼ぶ回数には気を付けた方がいい。何度も呼べば当然何度もフィルタ、変換処理が走る。

▼とってつけたような対処法

コード全体の設計見直せやって感じなんだけど、とりあえず遅延評価を正格評価(即時評価)にすることもできる。

「ToList」「ToArray」という関数を使えばよい。
文字通り「IEnumerableをList/Arrayに変換する」というメソッドだ。「.ToList()」とか最後につけてやればListがもらえる。「results」の中身はIEnumerable<T>ではなくList<T>になる。

LINQかけた結果を何度も使いたいのであればListにしちまったほうが良いだろう。だけど、LINQの興を削ぐような行為であるから、あまり頼りすぎない事。何でもかんでもToListするのはオカシイ。

◆勉強(強):匿名型

ちょっとばかし難しい話をする。いや、難しくはないんだけど、C#初心者って人はコンガラガッチュレーションする危険性がある。
(is余裕 == false) って人は飛ばして、次の項目をみてくれ。

匿名型を使用した例を以下に示す。

  1. Selectを使用し「Human型プロパティStudent」と「bool型のプロパティisFemale」を持つ匿名型に変換する。
  2. 名前の最後が「子」である生徒は女性であるので(偏見)、EndsWithの判定結果をisFemaleに格納する。
  3. Whereで判定し、あとはいい感じにする。

なにが新しいって「new」キーワードの後にクラス名が登場しないことだ。「匿名型」っていうか「無名型」だな。翻訳者がアホだからそう呼ばれるようになってしまった。

Studentはプロパティであるのだが、その型の指定はしなくてもよい(できない)。なぜなら、匿名型を使用するときは、そのプロパティに必ず初期値を設定しなければならない。そして、初期値を必ず指定しなければならないのだから型推論が可能だ。

この例では、匿名型にSelectした後に続くLINQメソッドで「x =>」が登場している。このxは「Human」ではなく、宣言した匿名型だ。その匿名型オブジェクト「x」は「Studentプロパティ」と「isFemaleプロパティ」を持っている。

だから「x.Name」ではなく「x.Student.Name」という記述で名前を取得する。

説明くどい?うるせぇよ飛ばせ。

このように匿名型の解説をしたわけであるが、なるべくなら使わないほうがいい。見通し悪いから。この例だとそんなにって感じだが、例えば匿名型にSelectしたあとの結果を持ちまわすことも可能だ。すると「どこに定義があるかわからんクラス」を処理に使用することになる。やめよう。

使いどころさんさえ誤らなければとても便利だ。だけど、慣れないうちはなるべく使わないこと。使うんだとしたら一つのメソッドチェーン内で完結させること。

◆存在確認

LINQはIEnumerableを返すものばかりではない。数値を返したりbool値を返したりもする。

▼カウント

Countを使用することにより、そりゃもうカウントできる。
また、Countする対象の条件を指定することが出来る。Whereのノリでboolを返してやればいい。WhereでフィルタしたあとにCountするんじゃなくて、Countに条件を入れてやってね。

Countした結果をわざわざ「allCount」とかいう変数に詰めているが、そんなことする必要はない。読みやすいように記述してくれ。

例えば、何百万件の大量データに対して今回みたいに複数条件のCountをしたいんだとする。そういう時は大人しくforeachを使おう。LINQは魔法ではなく、内部ではCountを呼んだ回数だけループが回っている。気を付けよう。

▼在るか無いかの二択

if (humanList.Count(x => (x.Age == 3)) == 0) return;とかいうコードを書いてはいけない。
Countは全ての要素をなめるからだ。

ある要素があるかどうかを判定するのはAnyを使用する。

要素が無かったらAnyも全てのデータをなめることになる。やはり大量データを扱うときは注意が必要になる。

▼全部一致するか

「対象のすべての要素が条件に一致するかどうか」を判定したい時はAllを使用する。変数名は…ごめん。でも伝わるでしょ?(思考放棄)

 

AllとAnyは相互に交換可能だ。たとえば「allHumansHaveName」の内容を「NullOrEmptyの要素があるかどうか」のAnyで判定をとってもいいだろう。変数名の意味も逆転しないといけないが。

◆ソート

OrderByを使用する。逆順にしたい時はOrderByDescending。

下記に三例示す。だが、このコードにはpaiza.ioでは動かない記法が含まれる。paiza.ioのコンパイラが古い。
ここで試せなくもないけど、加工しなきゃいけないからちょっと面倒。VS Code使ってくれ。

OrderByでは比較可能な性質を持つ値を返してやらなければならない。もっというとIComparableを実装しているクラスだ。
今回の場合は数値「x.Age」や文字列「x.Name」を返しているので、その値をもとにソートをかけている。

で、気になった方もいるだろう

OrderBy(x => (x.Name, x.Age))

という記述だ。

やっていることは

stringとintのTupleを返却

だ。Tupleはタプルと読む。

Tupleについては後で触れる。
「nameAgeSort」はとにかく、Tupleの左の要素から順にソートをかけている。複数の要素をもとにソートをかけるときはこのような記法になる。今回は二個の値でソートをかけているが、もっと増えても大丈夫だ。多分。

◆サンプル書くまでも無い奴ら

使いがちな奴らの紹介。使わながちな奴らは紹介しない。

▼最初の要素を返す

First または FirstOrDefault を使用する。
どうでもいいから最初の要素を取りたい時に使用する。IEnumerableという性質は順序を保証しているわけではないから、微妙に注意すること。

FirstとFirstOrDefaultの違いは、その要素を見つけられなかった時の挙動の違いだ。FirstはExceptionを投げるし、~OrDefaultは値の規定値を返す。stringだったらnullだったりintだったら0だったりオブジェクトだったらnullだったり。慣れないうちは実際に動かして「規定値」を確認するのが無難だろう。

「値が存在してなかったらシステム的におかしい」ってときはFirstを使った方が壊れてくれるから安全だ。おかしい時に壊れないシステムを作ると、おかしくても動き続けるシステムになる。

んで、CountやらAnyのようにFirstも条件を指定することが出来る。うまく使え。

▼重複除去

Distinctを使用する。

文字列や数値とかのプリミティブな値のリストに対して重複を除いたIEnumerableを取得できるだろう。おおむね。
正確に言うとIEquatableを実装しているクラスについて重複を除くことが出来る。

GroupByを使えばもっと複雑な重複除去も可能だ。後で説明する。

◆SelectMany

以下のルールを想像していただきたい。

  1. 学校があり
  2. 学校には複数の学級があり
  3. 学級には複数の生徒がいる

いいだろうか。

そして「学校全体の生徒について、年齢でソートした結果」を得たいとする。
これは、結構面倒な(ダサげな)コードを書かなければならないだろう。

状況としては「『Aのリストを持つB』のリスト」について全てのAリストを平坦化したいということだ。そういった場合に使用されるのがSelectManyである。以下に例を示す。

※「応用」と書いてある場所は「匿名型」のセンテンスを読んだ人向けに書いてます。いっぱいいっぱいな人は読むと脳みそが溶けて耳からトロトロと出ます。

SelectManyではつまり、IEnumerableを返却すればよい。「(x => x.Students)」はつまりそういう事だ。SelectManyの結果は一本のIEnumerableであるわけだから、OrderByで並べることもできる。

▼(強)学級名も欲しい

生徒を年齢順に並べることが出来て良かったんだけど

生徒の所属する学級名も見たくね?

という要望が出てきてしまった。さて。

案ずることなかれ。僕らにはLINQがついている。
思い出していただきたいのは「Selectはデータ変換」ということだ。つまり、Studentsのデータ変換をして「学級名プロパティ」を持つ匿名型にしてやればいい。

えっでもHumanは学級名を持ってないじゃん

と思われただろうか。うむ。List<Human>がSchoolClassのNameを持っていないが故に、値は取れないではないかと。じゃあコードを見てみる。

school.Classes.SelectMany(x => x.Students.Select(s => new { ClassName = x.Name, Student = s }))

これは、LINQ内にLINQがある状態だ。内側のLINQを抜き出してみる。

x.Students.Select(s => new { ClassName = x.Name, Student = s })

はい。

「x」はSchoolClassで「s」はHumanだ。
※Visual StudioとかCodeだと変数にオンマウスで型が確認できるから便利

ここでの注目ポイントは「x.Name」であろう。つまり変数「x」のスコープはその内部のLINQにまで届いているということだ。
当たり前といえば当たり前なんだけど。

理解できたかな。できなかったんならもっとじっくり見てホラ(どセクハラ)

で、なんだかんだありつつ結果的には匿名型のIEnumerableになっただけなんで、その結果をOrderByでソートすることもできるだろう。

このように匿名型は便利であるが、やはり多用は禁物だ。一つのメソッド内で完結するのならいいが、そうでないならしかるべきクラスを用意してやろう。

▼読みにくかったんだけど?

うん。そうした。ごめん。
さっきのコードの読みづらさは、ひとえに変数「x」の名前によるものだ。

この場合、「x」のスコープの広さを鑑み、「class」という変数名をあてがってやれば分かりやすくなるだろう。…と言いたいところだがclassは予約語であるため「@class」としてやればよい。

けど

「この@classって何?」

とか聞かれるのも面倒だからschoolClassぐらいにしておくのが無難か。

◆(強)勉強:Tuple

Tupleは「値の組」と表現される。
匿名クラスに近いような感じだと思っていただいてもいい。違うけど。
例えばSelectを使用してTupleを生成することができる。

表現方法は丸カッコで囲むだけ。例えば(値1, 値2)みたいな感じだ。
Tupleはpaiza.ioじゃ動かないから注意な。

記事書くの疲れてきたから説明省いてみるけど、hoge1とhoge2_sortは同じ結果になる。
Tupleの要素にどこで名前を付けているのかに注目してみてくれ。そうすればhoge2のLINQメソッドに直接OrderByを繋げられない理由もわかるだろう。

Tupleには他にもいろいろな生成、アクセス方法がある。興味があるんなら頑張って勉強してみろ。

Tupleはそれ自体が「型」のようなものであるから、Tupleを含むTupleを作ったりもできる。色々な使い方ができる。
ただ、やはり使いすぎない事。コードの行数を短くできたとしても、それ自体に意味はない。すべては可読性を上げるためだ。

◆勉強:条件演算子

最後にGroupByを紹介するにあたり、先に条件演算子の話をしておきたい。
が、三項演算子については使い時を誤るとマジに可読性がゴミと化すから、極力使うな。

「べんりだー♪」って使っていいものではない。

釘を刺しつつ、演算子の例を示す。

▼三項演算子

var num2 = (num == 1) ? 2 : 3;

つまり

[型] [変数名] = [条件] ? [条件がtrueであった場合の値] : [条件がfalseであった場合の値]

という構造だ。

どうやれば可読性を落とすことが出来るかというと、三項演算子を使用すれば可読性は落ちる。使用するタイミングは「使用できるとき」ではなく「使用しない場合のほうが使用した場合よりも可読性が落ちるとき」に限る。いいか。

1行だから読みやすいってことにはならない。

コードは書く時間よりも読む時間のほうが長い。上からさらっていったときに文章のように読めるコードを書け。

null処理の例をこの後に書くから、そこでもちょっと説明する。

▼null合体演算子

var str2 = str ?? "str is null.";

strがnullの時は「??」の右側の値が適応される。
便利ね。

▼null条件演算子

var element = list?.Take(1);

「?.」により、その左側の変数がnullであった場合はnullを返却する。
便利ね。

null合体演算子もそうだが、一度に使用できる数の制限はないので

var name = list?.Take(1)?.Name;

のように書くこともできる。この例だと意味不明だけど。

◆おまけ:三項演算子の回避

視聴者プレゼント

3 > 4 > 1 > 2

って感じだろうか。

三項演算子の「は?」感がすごい。一行で単一の値を返す癖に三項あって、なおかつジャンプが含まれるのが辛い。
「ここで三項演算子を使っている」っていう認識があればそりゃ当然すっと読める。でも読む前からそれを知っているのは書いた人だけだ。

じゃあおめぇ一生三項演算子使うんじゃねーぞ

って話をされそうだが、そうはいかない。わざわざ紹介をしたのは、それを使うからだ。じゃ、GroupByをやっていこう。

◆ToDictionary

嘘だよ!馬鹿が!先にToDictionaryだ!

じゃあ以下のクラスを考える。

「名前、年齢、学級名、女性フラグ、点数」を持つStudentクラス

例えば年齢でStudentを取得するDictionaryを作りたいとする。
LINQではそれを一行で作ることが出来る。

説明…説明いる?
キーにしたい値をToDictionaryで返却するだけだ。ここまで読み進めてきたんなら理解できるはずだ。

二個目はちょっと特殊。だけど構造は単純。

x => x.Age
⇒キーの指定。
x => $"{x.ClassName} {x.Name} {x.Age}歳 {x.Score}点"
⇒Dictionaryに格納される値。

左がKeyで右がValueってだけだ。生成されたDictionaryにキーをくれてやれば変換された値が取れるだろう。

その下にTupleのDictionaryの例も記載してみた。感心したり感心しなかったりしてくれ。

◆おまけ:GetValueOrDefault

Dictionaryに入ってないKeyで値を取り出そうとするとエラーになるでしょう。そして、一致するKeyが無かった場合に適当な値を返したいって需要もあるでしょう。nullとか。

そういう時はGetValueOrDefaultを使いなさい(神託)

ただし(神託)

そのKeyが無いとシステムのアレ的にヤバい時(神託)

は使ってはいけませんよ(神託)

◆GroupBy

実は、GroupByを使いこなせればToDictionaryはお役御免だったりする。すまんな。

GroupByはDictionaryと少し似ている。つまり

ある値を起点として値をグループ(要素のあつまり)にまとめる

ということだ。GroupByもKeyを持つ。その値でグループのメンバーを取得できるっていう寸法だ。

ちょっと例を出す。いきなりヤバい例を出すから気絶しないように。

したいことは

  • 学級名でグルーピング
  • グループを学級名でソートして
  • Dictionaryにする

GropuByの引数はToDictionaryと同じ。すなわちキーだ。今回は「学級名」をキーにグループを作っている。

GroupByメソッドは戻り値が特殊で、今回の場合は

IEnumerable<IGrouping<string, Student>>

となる。見ればわかるが「IGrouping<string, Student>」の「IEnumerable」だ。察しのいいホモガキならピンと来たかもしれないが、IGroupingも「性質」であり、「Represents a collection of objects that have a common key」で、つまり「共通のキーを持つコレクションという性質」だ。基本的に「I」から始まるアレは性質を表現している。

未確認飛行 C:インターフェース

IGroupingのstringは、今回「学級名」を指定したためにstringになっている。
stringはキーに当たる部分だ。そしてStudentはStudentだ。

どう理解すればいいかというと

IGroupingは「キー」と「キーに対応するIEnumerable」を持っている

ってな感じにやっていただければいい。

▼IGroupingから値を取得

まず、Keyが取れます。

.OrderBy(x => x.Key)

これは良かろう。
問題は

.ToDictionary(x => x.Key, x => x.ToList())

x.ToList()だ。

なんか…納得できないだろう。

ToListってIEnumerableの性質を持つ奴に使えるんじゃないの?

って疑問だ。
その答えとしては

IGroupingという性質はIEnumerableという性質を実装している

ということだ。
だからIGroupingに対してLINQメソッドを適応することが出来る。

こんな説明で大丈夫だろうか。

で、結果として「StudentのListを持つ、学級名をキーにしたDictionary」を作ることが出来たわけだ。良かった。さらに言うと生徒を点数順に並べることができるな。OrderByを使うわけだが、それをどこに入れればいいのか考えてみてもいい。

「理解できなかったよ」って人は…読み返して💖

▼IGroupingの補足

IGroupingはIEnumerableを実装しているので、それ自体をIEnumerableとみなすことが出来る。
つまり

.ToDictionary(x => x.Key, x => x.ToList())

.ToDictionary(x => x.Key, x => x as IEnumerable<Student>)

なんて書き方にすることもできる。いぇーい!ワオワオ!キャッフゥーァ!(説明放棄)

◆GroupByと集計

あー書くの疲れた。いい加減にしろ。読むのも疲れたでしょう。やめる?

やめないけどね(暗黒微笑

これで最後だからもう少し付き合ってくれな。

さて、LINQには「集計」機能を持つメソッドが存在する。難しくもない。

ね、簡単でしょう?

集計メソッドもやはりIEnumerableに対して使用することが出来る。つまりはIGroupingに使用することが出来る。それがどういう需要に適合するかというと

学級の平均得点のリストが欲しい

とか

学級で得点が1位である人間のリストが欲しい

ってケースだ。
実はそんなに難しくないし、考えてくれるのが一番だけど例を示してしまう。ただ、サンプルソースをコピペしろって言ってるんじゃないよ。
この記事の意味は、テメーらヒヨコちゃんどもにLINQというアーツのポテンシャルをノーティスして頂き、プロジェクトにコミットさせることだ。それにより工数を減らして早く帰宅させることができたら俺の勝ち。できなかったらお前らの負けだ。

▼学級の平均得点のリスト

こうなる。
GroupByで生成されるのはIGroupingのIEnumerableである。だから、x.Keyでキーが取得できるし、xに対して集計関数のAverageを適応することが出来る。フヒヒ。

▼学級&性別の平均得点のリスト

ちょっと無理やりだけどクラスを増やす。

「学級名、性別(文字列)、平均点数」を持つAverageScoreクラス

LINQによりこのクラスを生成しようとすると、以下のようになる。

ア゛ー…ギモヂガヨひ…(ビクンビクン)

それはそうと、三項演算子が出てきましたね。これは、使った方が見やすいから使ってます。それ以上でも以下でもありません。使わない場合は以下の通りになります。

ちょい微妙っしょ?
三項演算子を使っても(個人的に)許せるケースは

  • 条件の値が一貫して変化しない
    ⇒つまり条件の演算をそのメソッド内で行わない
  • メソッドの行数が超少ない
  • 代入をする

って感じかな。アレがアレなら、IEnumerableからAverageScoreを生成するメソッドを別に生やして、それをLINQ内で呼んでやるのが一番きれいだろう。

▼女子を一律減点したい(医大並感)

ちょうどいい時事ネタがあってよかった。(不謹慎)

ちょっとデータ設計がアレで可読性が落ちるんだけど、「こういうことできる」ってことを紹介したい。Averageの中身に注目だ。

はぁぁあああっ・・・!!(畏怖)

AverageもSumもMinもMaxも、集計中に値を加工することが出来る。別の例を挙げると「返品フラグがfalseならそのままSum。trueならマイナスしてSum。」みてぇな挙動も楽々さ。

▼得点1位リスト

各学級について、得点が1位だった生徒のリストを作成したい。

やわらか頭で考えてみろ。いわばLINQはパズルよ。

あー素晴らしい。(恍惚)

しかし可読性が高いかといわれると微妙ではあるな。コメントで補足を入れるべきだろう。

同じ処理をレガシーなコードで実装しようとした場合

  • foreachまわして「組」で「最高得点」を引くDictionaryを生成
  • もっかいforeachまわして、最高点の生徒をListにつめる。

とかだろな。Dictionary<string, List<Student>>作るって手もあるけど、いずれにせよforeachは二回まわるし、二個目のforeachの中でもっかいforeachがまわるだろうな。可読性も低い。OrderByもできない。とれる結果もIEnumerableじゃなくてListだ。

だからLINQ使えっつってんです。

・補足①

student.Score == maxの「max」をstudentEnum.Max(s2 => s2.Score)に置き換えても動く。一行で書けるから気持ちがいい。気持ちがいいんだけど、沢山ループが回っちゃうからやめようね…!

・補足②

GroupByは値の加工機能がある。すなわち、Selectを使わない方法もある。

解説すると…

解説…?(自問)

しない!(自答)

あんまり好みじゃないし(わがまま)。Select書けば?
処理速度がどうなるかは知らない。すまんな。

◆ToLookup

正格評価なGroupBy。GroupByの結果を何回か使いたいときはToLookupで固めておこう。

◆以上です

くぅ~疲れましたw これにて完結です! 

実は、エンジニアしたら開発の話を持ちかけられたのが始まりでした
本当は使用可能な工数なかったのですが←
品質を犠牲にするわけには行かないのでC#3.0のネタで挑んでみた所存ですw
以下、メソッド達のみんなへのメッセジをどぞ

Select「みんな、見てくれてありがとう
ちょっとジェネリックなところも見えちゃったけど・・・気にしないでね!」

Where「いやーありがと!
無名関数のかわいさは二十分に伝わったかな?」

OrderBy「使ってくれたのは嬉しいけどちょっとunstableね・・・」

GroupBy「見てくれありがとな!
正直、as句で変換不能な値の結果はnullだよ!」

SelectMany「・・・ありがと」フラテニング

では、

Select、Where、OrderBy、GroupBy、SelectMany、Join「皆さんありがとうございました!」

Select、Where、OrderBy、GroupBy、SelectMany「って、なんでJoinくんが!?
改めまして、ありがとうございました!」

本当の本当に終わり

◆最後に

はい。お疲れさま。ガッテンしていただけましたでしょうか?

\ガッテン!/\ガッテン!/\ガッテン!/

金のガッテンはメロンパン入れになってるとかなんとか。

まぁいいや。どうでも。しゃらくせぇ。

さて、いかがだっただろうか。丁寧に書いたつもりではあるが。

説明してないメソッドもある。Joinもそうだし、Aggregate、Union、Intersect、Except、Concatとか。あとContainsはISet<T>と仲良しとか。TakeもSkipもその辺は自分で何とかしろ。俺はもうすぐ死ぬ。

この記事を脳にパンチングできたんならLINQerと言って差し支えなかろう。
お前の楽しいプログラマーライフを心から願っているよ。

じゃ、あったかくして寝ろよ