LINQの仕組みと遅延評価の基礎知識

新年明けましておめでとうございます。その第一弾の記事は実践 F# 関数型プログラミング入門の書評にしようと思っていたのですが、もう少し時間がかかりそうなので、せっかくの年始は基礎から考えようということで、LINQと遅延評価について最初から解説します。まあ、何をもって最初だとか基礎だとか言うのも難しいので私的な適当な基準で。つまり役に立たな(ry。なお、ここではLinq to Objects、IEnumerable<T>の連鎖についてのみ扱いますので、IQueryableについてはまた後日というか実のところ私はQueryableは全然分かってなくてやるやる詐欺が今も続いているといううがががが。

メソッドチェーン != return this

例によって単純なコードで。

var query = Enumerable.Range(1, 10).Select(i => i * i).Take(5);
foreach (var item in query)
{
    Console.WriteLine(item); // 1, 4, 9, 16, 25
}

1から10を二乗したうちの先頭5つを出力という、それだけのコードです。foreachする場合のinの右側が長くなるのは個人的に好きじゃないので、わざわざ変数に置いたりするのをよくやるのですが、これは好みですかねえ。なのでリスト内包表記とかあんま好きじゃなかったりはする、記法的に。

それはともかく、ドットで繋げていると実体が隠れてしまいがちなので、分解します。

var rangeEnumerable = Enumerable.Range(1, 10);
var selectEnumerable = rangeEnumerable.Select(i => i * i);
var takeEnumerable = selectEnumerable.Take(5);
foreach (var item in takeEnumerable)
{
    Console.WriteLine(item);
}

変数だらけでゴチャゴチャして余計に分からない。良く分からないものは、とりま図で。

こうなってます。中に、一つ前のものを内包している新しいオブジェクトを返しています。メソッドチェーンというと所謂ビルダー的な、もしくはjQueryなんかを想像してしまってチェーン毎に内部の状態が変化して return this するか、もしくは完全に新しいものを生成して返す(array.filter.mapしたら.filterで完全に新しい配列が生成され返って、.mapでも、的な。DeepCopyも同じようなものですか)みたいなのを想像してしまう感もあるのですが、そのどちらでもない。中に仕舞い込んで新しい包を返す。実に副作用レスでピュアい。

このことはデバッガで確認出来ます。

面白いのはSelectの戻り値の型で、WhereSelectEnumerableIteratorとなっていて、名前のとおりWhereとSelectが統合されていたりします。これは、Where->Selectが頻出パターンのためパフォーマンス向上のためでしょうねえ。面白いですがユーザー的にはあまり気にすることではないので深追いしないで次へ。

Takeの戻り値であるTakeIteratorはsourceとして中にSelectの戻り値であるWhereSelectEnumerableIteratorを抱えていて、Selectの戻り値はRangeの戻り値であるRangeIteratorを、中に抱えています。という連鎖が成り立っていることがしっかり確認できました。Visual Studioのデバッガは大変見やすくてよろしい。

遅延評価と実行

hogeEnumerableに包まれている状態では、まだ何も実行は開始されていません。そう、遅延評価!このままWhereやSkipを繋いでも、新たなhogeEnumerableで包んで返されるだけで実行はされません。ではいつ実行されるかといえば、IEnumerable<T>以外の、何らかの結果を要求した時です。それはToArrayであったり、Maxであったり、foreachであったり。

foreachを実行した時の動きを、図(但し致命的に分かりづらい)で見ると……

まず最初は最外周のtakeEnumerableに対しGetEnumeratorを実行し、IEnumerator<T>を取り出します。そして取り出したIEnumerator<T>に対しMoveNextの実行をすると、その先ではまた中に抱えたIEnumerable<T>に対しGetEnumeratorでIEnumerator<T>を取り出し、の連鎖が大元(この場合はrangeEnumerable)に届くまで続きます。

大元まで届いたら、いよいよMoveNextの結果が返されます。trueか、falseか。trueの場合は、通常は即座に現在値(Current)の取得も行うので、Currentが根本から下まで降りていくイメージとなります。あとは、どこかのMoveNextがfalseを返してくるまで、その繰り返し。今回はRangeが10個出力、Takeが5個出力なので、Rangeが5回分余りますがTakeで列挙は途中打ち切り。falseを流して終了させます。SumやCountなど値を返すものは、falseが届いたら結果を返しますが今回はforeachなのでvoid、何もなしで終了。

イテレータの実装

ついでなので、動作の実態であるイテレータも実装します。単純な、0から10までを返すだけのものを例として。

public class ZeroToTenIterator : IEnumerator<int>
{
    private int current = -1;
 
    public int Current
    {
        get { return current; }
    }
 
    public bool MoveNext()
    {
        return ++current <= 10;
    }
 
    // 必要でなければ空でもいいや、という感じ
    public void Dispose() { }
 
    // TじゃないほうはTのほうを返すようにするだけでおk
    object System.Collections.IEnumerator.Current { get { return Current; } }
 
    // Resetは産廃なのでスルー、実装しなくていいです、Interfaceからも削られて欲しいぐらい
    public void Reset() { throw new NotImplementedException(); }
}
 
// 使うときはこんな感じでしょーか
// IEnumerator<T>利用時はusingも忘れないように……
using (var e = new ZeroToTenIterator())
{
    while (e.MoveNext())
    {
        Console.WriteLine(e.Current);
    }
}

IEnumerator<T>ですが、見てきたとおり、中核となるのはMoveNextとCurrentです、といってもCurrentはキャッシュした値を中継するだけなので、実質実装しなければならないのはMoveNextだけ(場合によりDisposeも)。

見たとおりに一行の超単純な、10超えるまでインクリメントでtrue、超えたらfalse。なんかとってもいい加減な感じで、falseだろうとMoveNext()を呼んだらCurrentの値がどんどん増加していっちゃって大丈夫か?というと、全然問題ない。と、いうのも、そういうのは利用側の問題であって実装側が気にする必要はないから。

MoveNextする前のCurrentの値は保証されていないので使うな、であり、MoveNextがfalseを返した後のCurrentの値は保証されてないので使うな、です。大事なお約束です。お約束を守れない人は生イテレータを使うべからず。Linqのクエリ演算子やforeachは、そんな他所事を考えないで済むようになっているので、それらを使いましょう。生イテレータを取得したら負けです(拡張メソッド定義時は除く、つまりライブラリ的な局面以外では避けましょう)

ちなみにStringのイテレータは列挙前/列挙後のCurrentへのアクセスで例外が飛び、List<T>は列挙前は0、列挙後も0にリセットされ、Enumerable.Rangeでは列挙後は最後の値が返る、といったように、実際に挙動はバラバラです。

実装側が守らなければならないルールは、MoveNextが一度falseを返したら、以後はずっとfalseを返し続けること。で、その観点で、このZeroToTenIteratorを見ると、実のところ全然ダメです。MoveNextがint.MaxValue回呼び出されるとcurrentがオーバーフローしてint.MinValueになって、つまりはMoveNextの結果もfalseからtrueに変わってしまいます。腐ってますね。殺害されるべき。誰がそんなに呼ぶんだよ、という感じに普段はあんま気にしないゆとりな私ですが、いえいえ、こういう時ぐらいは気にしたりします。

オーバーフローはうっかりで見落としがちなので、ヘラヘラゆとりゆとりと笑ってないで、普段から注意すべきだと自戒するこの頃。

まあ、今時はイテレータの手実装なんてする必要ないのですがね!シンプルなものならばLinqの組み合わせで実現出来ますし、そうでないものはyield returnを使えばいいので。手実装じゃなきゃダメなシチュエーションってなにかある、かなあ?

まとめ

「return thisじゃなくて新しいオブジェクトを返してる」「配列的なイメージで扱えるけれど実体はストリームのほうが近い」「デバッガ素晴らしすぎる」「生禁止」の以上四点でした。

Linq to ObjectsのJavaScript移植であるlinq.jsも同じ仕組みでやっているので、そちらのコードのほうがブラックボックスでなく、また、素直に書いているので分かりやすくお薦め、かどうかは、微妙なところですんがー。ブラックボックスになっている部分(yield returnなど)を表に出しているので(というか出さないと実装出来ない)余計分かりにくい感も。

で、基礎からのLinqといえば紹介したいシリーズが一つ。

LondonのGooglerでMicrosoft MVPでC# in Depthの著者でStack Overflowで凄まじい解答量を誇るJon Skeet氏が、BlogでReimplementing LINQ to Objectsと称して、これまた凄まじい勢いで再実装&超詳細な解説をやっているので必見です。

詳細、どころの話じゃなく詳細で大変ヤバ素晴らしすぎる。単純なサンプルコードと結果を貼ってメソッド紹介、などという記事とは一線を画しすぎるクオリティ。私もこういう記事を書いていきたいものです。こんな量とスピードの両立は超人すぎて無理ですが、今年は記事のクオリティは上げたいですね。

C# in Depth 2nd Editionはつい二ヶ月前に出たばかりで、内容も良さそうですね、読んでみたい本です。しかし私の手元には積み本がいっぱいで、とほほ。で、本といえばもう一つ、C# 4.0 Unleashedがもうすぐ(2011/1/14、明日だね)出ます。これは著者がBart De Smet氏なので大注目です。B# .NET BlogでキレキレのLinqコード、だけじゃなくILからSQLからあらゆる領域にエキスパートな凄さを見せているので超楽しみです。こちらは予約してあるので、届くのが本当に楽しみで(洋書なので届くのは月末予定のよう、F#本が読み終えた頃になる予定なのでちょうどいいー)。

Bart氏は大学時代はベルギーのMicrosoft MVP for Visual C#、その後MicrosoftのWPFチームに入り、現在はCloud Programmability Team、つまりRxを開発しているチームに入ってます。氏が入ってからIQbservableとかヘンテコなのが次々と上がってきて、ますます目が離せない状態に。PDC10ではLINQ, Take Two - Realizing the LINQ to Everything Dreamというセッションを行ってましたが、これは本当に必見。Linqの過去、そして未来を見る素晴らしいセッションでした。感動しすぎて3回ぐらい見直した。

LINQは今後「ますます」重要になるので、しっかり土台を固めて、未来へ向かおう!

Comment (0)

Name
WebSite(option)
Comment

Trackback(1) | http://neue.cc/2011/01/13_295.html/trackback

C#erなら当然知ってるよね!?LINQ遅延評価のメリット・デメリット | 勝手にオザマリン : (04/03 01:59)

[…] C#erなら当然知ってるよね!?LINQ遅延評価のメリット・デメリット | 勝手にオザマリン(function(i,s,o,g,r,a,m){i[’GoogleAnalyticsObject’]=r;i[r]=i[r]||function(){(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)})(window,document,’script’,'//www.google-analytics.com/analytics.js’,'ga’);ga(’create’,'UA-89696758-1′,’auto’);ga(’send’,'pageview’);var a2a_config=a2a_config||{};a2a_config.callbacks=a2a_config.callbacks||[];a2a_config.templates=a2a_config.templates||{};a2a_localize={Share:”共有”,Save:”ブックマーク”,Subscribe:”購読”,Email:”Email”,Bookmark:”ブックマーク”,ShowAll:”すべて表示する”,ShowLess:”小さく表示する”,FindServices:”サービスを探す”,FindAnyServiceToAddTo:”追加するサービスを今すぐ探す”,PoweredBy:”Powered by”,ShareViaEmail:”Share via email”,SubscribeViaEmail:”Subscribe via email”,BookmarkInYourBrowser:”ブラウザにブックマーク”,BookmarkInstructions:”Press Ctrl+D or u2318+D to bookmark this page”,AddToYourFavorites:”お気に入りに追加”,SendFromWebOrProgram:”Send from any email address or email program”,EmailProgram:”Email program”,More:”More…”};img#wpstats{display:none} .recentcomments a{display:inline !important;padding:0 !important;margin:0 !important;}.synved-social-resolution-single{display:inline-block;}.synved-social-resolution-normal{display:inline-block;}.synved-social-resolution-hidef{display:none;}@media only screen and (min–moz-device-pixel-ratio:2),only screen and (-o-min-device-pixel-ratio:2/1),only screen and (-webkit-min-device-pixel-ratio:2),only screen and (min-device-pixel-ratio:2),only screen and (min-resolution:2dppx),only screen and (min-resolution:192dpi){.synved-social-resolution-normal{display:none;}.synved-social-resolution-hidef{display:inline-block;}}.site-header-conts{padding-bottom: 0px;padding-top: 25px;}.site-header{background:#edf3fb;}.global-nav{background:#edf3fb;}勝手にオザマリン TOPXamarinプログラミングC#erなら当然知ってるよね!?LINQ遅延評価のメリット・デメリットC#erなら当然知ってるよね!?LINQ遅延評価のメリット・デメリット2017年4月3日プログラミングスポンサードリンク(adsbygoogle=window.adsbygoogle||[]).push({});今回の記事はザマリンに関する記事ではなく、同期から寄せられた「遅延評価って何?」についての記事です。私自身も分かっていたようで実は全然分かってなかったな、ということが分かったので非常に勉強になる単元ですね。一応対象者としてはLINQをこれから勉強する初心者でも分かるように書いています!ただ、あくまでも主眼は遅延評価についてです。会社での会話同期新人研修でLINQの講義やるんだけど、どういう所を喋ればいいかな かりまた遅延評価のとことか同期ごめん、正直、遅延評価の事分かってない…。遅延評価ってなにが良いの…? かりまた俺もふんわり分かってるつもりだけど上手く説明できないな。せや!記事に纏めよう!そもそもLINQってなにさ?りんちゃんLINQとはLanguage Integrated Query の略称で、配列・データテーブル・XMLなどのデータ操作に用いられるのにゃ~。データ操作って、具体的に何ができるの?りんちゃんLINQではデータ操作が出来ると書いたが、実際に何が出来るのかを簡単に書いてみるのにゃ~。Q.1から100までを足した値を出力せよりんちゃん「1から100まで足した値を出力せよ」という問題があった場合、以下の様にかけるのにゃ~。for文で書くとこうなるLINQを使うとこうなるLINQを使って書いてどう変わった?りんちゃん乱暴な言い方をしてしまえば、LINQを使う事でfor文をある程度消すことが出来るようになるのにゃ~。なんだとさんなん…だと…かりまたfor文を消すことが出来れば、ネスト(入れ子)が減ってコード全体の見通しが良くなります。そうするとコードの保守性も当然上がってきますね。Q.「t」が含まれている苗字を出力せよりんちゃんLINQが文字列操作にも強いことを示す為にもう一つ例を挙げてみるのにゃ~。「t」が含まれている苗字を出力するのにゃ~。for文で書くとこうなるLINQを使うとこうなるLINQを使って書いてどう変わった?りんちゃんfor文の中だけで完結させようとした場合、どうしてもif文を書いてやる必要が出てくるのにゃ~。りんちゃんLINQを使うことによって、予め「t」を含んだ苗字のみを抽出したコレクションに対してのみのループになるのにゃ~。遅延評価とは何か?かりまたさて、LINQがどういうものかがざっくり分かった所で、本題の遅延評価について話を進めていきます。意外と知らず知らずのうちに使っている遅延評価りんちゃんまたまた問題なのにゃ~。Q.以下の1から3までに対してReturnメソッドを実行させるプログラムの実行時間は何秒か?りんちゃん正解は 「約6秒」 なのにゃ~。Q.以下の1から3までに対してReturnメソッドを実行させるプログラムの実行時間は何秒か?りんちゃん正解は 「約3秒」 なのにゃ~。上のプログラムと下のプログラムは何が違うのか?かりまた異なる点は一つのみ。ToListメソッドでリスト化しているかどうかです。何故、リスト化すると3秒で、リスト化しないと6秒なのか?りんちゃんまさしく、この答えが「遅延評価」そのものなのにゃ~。りんちゃん一つ目のforeach文にブレークポイントを置いて実行してみると違いが見て取れるのにゃ~。ToListメソッドでリスト化している方のプログラムは約3秒でブレークポイントに到達するのにゃ~。つまり、即時的に式が評価されているのにゃ~。りんちゃんでは、リスト化しなかった方のプログラムはと言うと、1秒も経たずにブレークポイントに到達してるのにゃ~。foreach文で使用されるタイミングになって初めて評価されてるのにゃ~。なので文字通りの「遅延評価」という訳なのにゃ~。かりまたでは何故、約6秒掛かってしまったのかと言うと、 集中評価するメソッドもしくはforeach文で使用される度に実行しなおしている からです。(つまり、キャッシュが効かない)かりまたこのプログラムではforeach文が2回書かれてあり、リスト化をしていない状態であれば2度、squaresが評価されてしまうという訳ですね。集中評価についての補足りんちゃん「遅延評価」に対しての対義的な意味合いを持つ「集中評価」に関して補足するのにゃ~。ToListメソッド・ToArrayメソッド・Sumメソッドなどは集中評価、つまりは即時的に評価されるメソッドなのにゃ~。かりまたToListメソッドなんかはListクラスのインスタンスを生成しているのでメモリ上に値を持つ必要があります。メモリ上に値を持っちゃう事になるので勿論、集中評価な訳です。遅延評価のメリット・デメリットについてりんちゃん遅延評価にはメリットとデメリットがあるのにゃ~。正しく把握することが良いプログラムへの一歩だにゃ~。まずは結論から。遅延評価のメリット省メモリ無限ループにも対応可能返ってきた値を逐次反映させる事ができる遅延評価のデメリットよく分からずに使っているとパフォーマンスを落としてしまう遅延評価のメリットについて深堀り1点目:省メモリりんちゃん遅延評価は省メモリだ!とよく言われているのにゃ~。実際の所どうなんだろう?と測ってみたところ数Kb程度の誤差だったのにゃ~。測り方が不味かったのかもしれにゃいが、コンパイラが最適化を掛けてくれたのだと今の所は思ってるのにゃ~。2点目:無限ループにも対応可能3点目:返ってきた値を逐次反映させる事が可能りんちゃん2点目、3点目は一纏めにして紹介するにゃ~。以下のプログラムは0から1ずつ無限ループ内で増加させたコレクションを10未満の間、出力するというプログラムだにゃ~。一見、正しく動作しなさそうだが、意図した通りに0~9までを1秒置きに出力して実行が完了するのにゃ~。りんちゃんまぁ、無限ループを噛ませても正しく実行するのはTakeWhileメソッドが処理を打ち切ってくれるからなんだにゃ~。かりまた無限ループでなくても、何かしら重い処理が全て終わってから表示!ではなく、処理が終了したものから表示!になればユーザ側からすると非常にストレスを感じにくくなると思います。かりまた例えば、Webページの情報が全て取得できてから表示される!ではなく、取得できた情報から表示される!の方が使っていて安心感がありませんか?遅延評価のデメリットについて深堀りよく分からずに使っているとパフォーマンスを落としてしまうりんちゃん遅延評価される式を何度も集中評価やforeach文などで使ってしまうと、都度都度で無駄に式が評価されてしまうよという事を上の方で書いたのにゃ~。もう少し詳しく知りたい方は以下のサイトを参考にすると良いのにゃ~。neue cc – LINQの仕組みと遅延評価の基礎知識まとめ遅延評価は文字通りに評価を遅延させているだけ使い方を誤るとパフォーマンスを落としますし銀の弾丸じゃないよでも正しく使えばユーザビリティを上げられる可能性を秘めているし省メモリ(なはず) スポンサードリンク(adsbygoogle=window.adsbygoogle||[]).push({});タグ : C#, LINQ, リンク, 入門, 遅延評価, 集中評価関連記事Xamarin.FormsでYouTube Data APIとPlugin.YouTubeを使ってみるXamarin.FormsでYouTubeのデータを取得したいXamarin.FormsでTabbedPageを使ってみたXamarin.Formsを勉強するにあたっての参考資料・サイトをまとめてみたXamarin.Formsで学ぶMVVM入門 MVVMって何よ?編VS2017をインストールしてXamarinを使えるようにしてみる「Xamarin.FormsでTabbedPageを使ってみた」コメントをどうぞ返信をキャンセルする。名前 メールアドレス(公開されません) ウェブサイトコメント送信PV数ランキングVS2017をインストールしてXamarinを使えるようにしてみる (290pv)2017/03/08に待望のVisual Studio 2017 がリリースされました!この記事ではVS2017のCommunityをインストールしてXamar…Xamarinのプロジェクトの作り方 (201pv) 勝手にオザマリンの最初の記事として、Xamarinプロジェクトの作り方を説明していくよ!ワクワクするよねwkwk!ただ、色々…Xamarinでリストを表示できるコントロール(ListView)についてまとめてみる(プロパティ) (180pv)リストを表示する際に便利なListViewというコントロールがあります。今回はこのListViewの全てのPublicプロパティと実際の動作についてまと…JXUGC #22 最新事例&お前のアプリを説明してもらおうの会に参加したきたった (151pv)JXUGC #22 最新事例&お前のアプリを説明してもらおうの会に参加したきたので、どういうことが話されてたとか思ったこととかをつらつら書きます。 …Xamarinで日付を選択できるコントロール(DatePicker)についてまとめてみる (145pv)日付を選択する際に便利なDatePickerというコントロールがあります。今回はこのDatePickerについてまとめてみました。DateP… […]

Search/Archive

Category

Profile


Yoshifumi Kawai
Microsoft MVP for .NET(C#)

April 2011
|
March 2017

Twitter:@neuecc
GitHub:neuecc
ils@neue.cc