rider.お品書きサクッと書いて公開して、後でちまちま修正する事がママあります。なんじゃこりゃ?と思ったら、数日してから見て貰えると嬉しいです。「スクラムの実践方法を分かりやすくマンガも交えて解説。:-)もう一つ小ネタを行ってみよう。複数の文字列を連結出来たら良いのにと思う事がある。ハードコードするなら、+演算子で繋げれば良いのだが、配列や列挙子だとこうは行かない。レベルの低い現場でよく見るのが、forで回しながら+演算子で連結という、眩暈のするコードだ(しかもStringBuilderも使っていない)。拡張メソッドを定義してみる。StringBuilderを使ってもよい(その方がありがたみがあるかも?)が、忘れがちだが、System.StringにConcat()というメソッドがあり、簡単に実現出来る。string.Concat(words);これが忘れやすい理由の一つは、やはりStringクラスのスタティックメソッドであることではないだろうか。されていなければならない。プロジェクトで共通で使用するクラスライブラリに、LINQ向け拡張メソッドを含む名前空間を決めておくというのはどうだろうか?他にも、文字群を連結して文字列にするというのも考えられる。string(chars)}最初の例で示した文字列の連結は、たとえばカンマ区切りで取得したい場合もあるだろう。words);}小ネタばっかりだが、そろそろまとめておく。列挙子を受け取って何かをするメソッドなら、拡張メソッドを使って定義しておくと、LINQクエリで使いまわしやすい。同じ名前のオーバーロードを沢山定義すると、ニアミスが発生しやすい事は押さえておく必要がある。例で挙げたメソッドは、全て”Concat”メソッドで、引数が異なるのでオーバーロードとして成立する(LINQのConcat含めて)。これが原因で使う際にどのオーバーロードを使用すべきか迷うことがある。私は、”Contains”メソッドをいくつか定義してみたことがある。LINQのContainsは、追加引数で指定された値が列挙値内に含まれているかどうかをテストする。ここに、System.String.IndexOf()とよく似たContainsを定義してみた。列挙値が追加引数群の何れかに一致するかどうかをテストするContainsも作ってみた。すると、当然のことながら、Containsのどの実装が欲しい機能を持っているのか、良く分からなくなってしまう。実際、インテリセンスはこれらのContainsを全て掲示してくれるが、やはりいまいちピンとこない。問題は、似て異なる機能を持ったメソッドを、同じ名前で定義している事だろう。古典的な問題だが、拡張メソッドについても同じ事が発生するので注意した方が良い。”Concat”も、”ConcatWords”とか、”ConcatToString”のような、具体的な名前を使った方が良いこともある。この問題はどのように考えれば良いだろうか。LINQでは、拡張メソッドがあらゆる列挙子で使われることを想定している。rhs);というのは、この拡張メソッドだけでT型列挙子の全てに応用が可能だ。このConcatだけで様々な用途に使用出来る。それだけ既に抽象化されているわけで、これと似た、しかし異なるメソッドを定義しなければならないとすれば、そもそも元のConcatと用途が異なるかも知れない、という事に注意を払えば良いと思う。LINQ向けに上記のような「部品」を作っておく事の良さを、もうちょっと別の面で示そうと思う。今までの例は、拡張メソッド内で、直前の列挙子を評価してしまう。ToSortedDictionaryの場合、メソッド内でforeachで回した時点で列挙を開始する。1.0/1.1では、IEnumerableインターフェイスを実装後、GetEnumerator()メソッドを書かなければならなかった。GetEnumerator()は、列挙子の実体処理を行うクラスのインスタンスを返す。これはIEnumeratorインターフェイスを実装していれば、非公開でも構わない。IEnumeratorはステートマシンの実装を強要するため、はっきりってこれを書くのは面倒だ。2.0になって、画期的な構文「yield」が使えるようになった。詳細は省くが、つまりこれを使って実装すればGetEnumerator()の実装は難しくない。何もしない拡張メソッドNop()を実装してみる。}}NopEnumerable<T>クラスは、GetEnumerator()が呼び出されるまでは、元の列挙子を一切操作しない。GetEnumerator()が呼び出されると、最初のforeachで初めて列挙子のGetEnumerator(enumerable.GetEnumerator())が呼び出される。ここで、元の列挙子も動作を開始するわけだ。return構文で一つずつ返却する。このメソッドのソースコードとバイナリコードはまったく異なり、記述どおりに振る舞うステートマシンとしてコンパイルされる。そのお蔭で、GetEnumeratorの実装は非常に簡単になっている。このコードをスケルトンとして、GetEnumerator()の実装を肉付けすれば良いだろう。一例として、二重の構造を持った列挙子を、一重の列挙子(つまり普通の列挙子)に変換するメソッドを定義する。折を見て、MSDNライブラリにも目を通すと良い。#思いつくようなメソッドは標準で提供されている事がある。Objectの拡張メソッドは、Enumerableクラスに定義されている。“IEnumerable<IEnumerable<T>>”という型を見て、「えっ、これどういう構造?」と一瞬考えたことは無いだろうか?このUnloopを使えば、ループ構造を簡略化出来る。このメソッドは実際に列挙されるまで動作が遅延され、中間バッファは全く必要ない。LINQにはぴったりのメソッドだ。この例で言えば、実はLINQクエリだけでも解決できる。inner;#対応する拡張メソッドは「SelectMany」だ。selectの書き方次第では、outerとinnerを使った演算結果を返すとか、応用性も高い。Unloopは単純な例なので仕方がないが、逆に言えば、まずLINQクエリだけで解決出来るか考えて、それから拡張メソッドを実装する、という手順にした方が良いかもしれない。メソッドを作れば文字通り「何でもアリ」となるので、究極的にはメソッド実装すれば、難しい問題も切り抜けられるはず。Serverで、まずクエリだけでどうやって書けるか、クエリの書き方次第でどのように高速化出来るか、それがダメなら部分的にユーザー定義関数に逃がすか、と考えるのと同じ方法論であって、実際頭の中では同じ部位が働いている感じがする。クエリの連結と拡張メソッド小ネタだ(しかし、重要)。書いたクエリはすぐには実行されず、列挙を行った時点(おおよそ、IEnumerable<T>.GetEnumerator()を呼び出した時点)で、実際にクエリが評価される。LINQメソッドの中には、その場で評価が実行されるものもある。ToList()がそうだ。letはクエリ内の一時変数のようなもの。Object)はそこまでやらないので、letを使うと良い場合がある。query.ToList();ToList()の部分は、以下のような事をしている。leapYears.Add(value);}あるいは、もっとこう。List<int>(query);後者ならかなり短く記述出来るわけだが、ToList()を使う利点は、LINQのメソッド連結式の延長上で書けるからだ。メソッド連結式(Where()とOrderBy()はデリゲートで評価結果を得る。Studio上でこれを書けば、何を言っているのか分かると思う。要するに「すらすら書ける」ということだ。「すらすら書く」ためには、LINQの標準メソッド群でも、やや不足を感じることがある。U>に変換できるのだが、SortedDictionaryが欲しい場合には、ToSortedDictionary()なるものを期待しても、そういうメソッドはない。某サイトでは、一旦ToDictionary()で辞書を生成した後、SortedDictionaryのコンストラクタに渡して生成する例が掲載されていた。それではDictionaryは完全に無駄だ。Dictionaryは内部でハッシュコード取得と分散を行っているはずなので、そのコストはドブに捨てる事になる。そこで、無いものは作ってしまえ、と。staticメソッドの第一引数にthisを付けることで、「拡張メソッド」となる。2.0世代の知識では少し理解が厳しいかもしれない。ToDictionary()がどのように使われるのかを確認しておく。匿名クラスから値を取り出してDictionaryにする。文の後ろのブロックで読み取り専用フィールドを定義・生成出来る。上記ではArg1とArg2を宣言して、それぞれ値を代入した。匿名クラスには名前がないので、コード上で型名を書くことが出来ない。:-)そして、今やToSortedDictionary()という武器が手に入ったので、以下のように書けばよい。});晴れて、イメージ通りのToSortedDictionary()が得られた。私はToDictionary()にもやや納得が行かない部分があった。匿名クラスに値を入れ、その後セレクタを使って再度辞書に入れ直すという部分だ。無い道具は作ってしまえ、と。dictionary;}そうすると、クエリ側にはKeyValuePairを書かなければならなくなる。これをどう捉えるかだが、直後に辞書に入れると分かっているなら、KeyValuePairで生成した方がすっきりするのが私流。U>>を実装する、全てのクラスで使えるのだ。sorted.ToDictionary();もちろん、辞書じゃなくてもOKだ。長い間、.NET2.0から知識をアップグレードしていなかったのだが、先日一気に.NET4.0の知識を詰め込んだ。LINQに触る必要性から、匿名デリゲート・ラムダ式・匿名クラス・式ツリー・拡張メソッドなどを覚えたのだが、はっきり言って今まで勉強を放置してきた事に、激しく後悔している。「食わず嫌い」だったのは、矢継ぎ早に追加される新しい構文に対する抵抗だったような気がする。C++のテンプレート拡張がもめたらしいのも、気持ちはよくわかる。テンプレートもそうだが、LINQも言語思想の革命と言っても言い過ぎではないぐらいのインパクトがあった。LINQの何が良いかと言えば、比類なき拡張性だろう。「枯れた」技術になり始めてる気がするので、今更だが)IEnumerable<T>インターフェイスは、List<T>ジェネリッククラスや配列が実装している。いうよりも、「列挙」可能なクラスには、このインターフェイスが実装されている(ちなみに、非ジェネリックなIEnumerableは列挙可能だが、LINQで使うには難点があるので省略)。このインターフェイスが実装されていれば、以下のようにforeach出来る。2.0技術者向け)で、LINQでforeachの部分を以下のように書ける。このクエリの結果要素としてnameを返す。Console.WriteLine(name);}「仮」に、nameListの要素数が非常に多かったとする(100万件とか)。その場合に、以下のように「AsParallel()メソッドを呼び出す」だけで、”様”付きの判定をマルチスレッド化出来る。name;大量の要素の、しかしながら個々の要素の判定にはそれほどの判定コストを必要としない場合でも、この「PLINQ(パラレルLINQ)」は効率的に動作するはずだ。通常、マルチスレッドで同じことを行う場合、以下のようなコードとなる。kingNames;}まず、第一にわかることは、「マルチスレッド対応」にするだけで面倒で不安な同期処理を大量に記述しなければならない事だ。この処理の肝は「EndsWith(“様”)」だけであるのに、付随コードの何とも多い事か。これだけでもLINQで書く事がいかに強力であるかが分かる。LINQで書く場合、「AsParallel()」で列挙子を修飾するだけだ。AsParallelメソッドは、指定された列挙子(nameList)をParallelQuery<T>クラスでラップする。LINQの他のメソッド同様、これだけではまだクエリは実行されていない。最後にkingNamesをforeachで列挙するまで、全く処理は行われていない。中間バッファは一切不要という点も重要だ。元のデータが100万件、EndsWithで絞り込んでも50万件のデータがあるとすれば、判定しては新たなコレクション(中間バッファ)にデータを格納するのはためらわれる。列挙した時点で一気に処理を実行することで、CPUのキャッシュにコードが維持される可能性も高まる。おまけに.NETはJITコンパイラで動いているので尚更だ。次に、レガシーコードで示した方法には、マルチスレッドを効率的に実行できない罠がある。それは、kingNamesに対してロックを行っている部分だ。この実装は、一回のワークアイテム実行に占めるロック時間が、相対的に長すぎる。そのため、複数のスレッドで同時実行されると、ロック競合が多量に発生する。結局その間は「シングルスレッド」と変わらないのだ。おまけにスレッドコンテキストの遷移に時間がかかってしまうので、シングルスレッド性能より落ちてしまう。「既存コードのマルチスレッド化でパフォーマンス向上」なんて、生易しい事ではないのだ。それがもっとよく分かるように、このレガシーコードを改良してみる。キューから文字列を取り出す。(そんな訳で、コンパイルして検証はしていない)要するにこのコードは、ロックを行わなくて済むように、事前にスレッド毎にデータを分散し、スレッド毎に結果を格納し、その結果はメインスレッド側で収集する、という事をしている。これはマルチスレッドでパフォーマンスを向上させる定石のようなものだ(ロックを不要にする)。キューにデータを入れ直している時点でメモリを余分に使っているため、さらなる改良が必要だが、「もういいだろう」。それにこれ以上書きたくない。いう面倒なことを、ParallelQuery<T>クラスの内部でやってくれるという事だ。現在のCPU事情と言えば、シングルスレッド性能は頭打ちで、マルチコア・SMTを推進している。コードを高速化させるためには、マルチスレッドに対応させるコーディングを行う必要があるが、同期処理は面倒かつバグを生みやすい。上記のような短いコードでさえ、書いただけでは正しいかどうか分からない。この最適化手法を知っている人なら、これがAsParallelするだけで実現される、なおかつそれはコンパイラが何か怪しげなことをやっているのではなく、全てライブラリだけで実現されていると聞けば、カルチャーショックを受けるはずだ(受けなきゃおかしい)。#LINQのクエリ構文はコンパイラが解釈する。ParallelQuery<T>クラスは種も仕掛けもない、普通のライブラリだ。#その気になれば、だれでも同じものが作れる。安全に実行出来るようにするには高度な技術が必要なので、「俺ParallelQuery<T>」は作らない方がいい。どのようなコードでも、LINQで書かなければならない訳ではない。LINQで列挙しながら、列挙中のコレクションを更新するという操作はNGだ。それはLINQを使わない場合でも同じ事だ(LINQで記述すると、あまりに簡便になるため、出来ない事があると欠点に思えてくるが、それは多分違うよ?)。LINQは「常に構造が変化する要素」を扱うのが難しい。列挙する要素がintになったりstringになったりする状況では、クエリが簡便に記述出来ない(そういう状況が、IEnumerable非ジェネリックインターフェイスを使った場合だ。LINQのライブラリはIEnumerableに対応していない。擬似的にIEnumerable<object>で試せば分かる)。これを以って、やはりLINQは中途半端と思うなら、実用的なコードをLINQで書いてみるべきだ。Serverにアクセスする必要などない。IEnumerable<T>インターフェイスを実装していれば、あらゆる要素に応用が可能だ。「LINQが適している分野ではLINQで書け」これに尽きる。LINQの「後」でPLINQが生み出されたように、LINQの拡張性にも目を見張るものがある。CoInitializeExは初期化ではないさて、そろそろ架空のコードでは現実とどう違うのかわからなくなってくるので、現実のコードがどうなるのかを見ていくことにする。前回までの解説からなんとなくイメージを持ってもらえたかも知れないが、これらのAPIは、現在のスレッドにアパートメント属性を設定・解除する。CoInitializeはCoInitializeExのバリアントなので、CoInitializeExで説明する。COMに関する様々な処理...CoUninitialize();CoinitializeExの第一引数は使われていないので0(ないしNULL)を指定する。第二引数は、初期化フラグを指定し、COINIT列挙値の値を使用する。ほとんどの場合、ここにはCOINIT_MULTITHREADEDかCOINIT_APARTMENTTHREADEDのどちらかを指定する。COINIT_MULTITHREADEDにした場合、現在のスレッドのアパートメント属性として、MTA(マルチスレッドアパートメント)に属するように設定する。COINIT_APARTMENTTHREADEDにした場合、STA(シングルスレッドアパートメント)に属するように設定する(そして、CoInitializeを使用すると、STA固定となる)。マルチスレッドアパートメントに設定すると、そのスレッドは、COMコンポーネントとのやり取りで発生するすべてのスレッド同期作業を、自分で行うと宣言したことになる。シングルスレッドアパートメントに設定すると、そのスレッドは、COMコンポーネントとのやり取りで発生するすべてのスレッド同期作業を、COMが背後で面倒見てほしいと宣言したことになる。これらの違いについては次回に譲る。これでCOMに関する下地が出来た訳だが、巷のコードを見ているとアパートメント選択以前の勘違いが多いことに気が付く。このAPIの呼び出しは、呼び出し元のスレッド(カレントスレッド)に対してアパートメントの設定を行うのであるが、まるでプロセスワイドにCOMの初期化が行われているかのような使い方だ。DLLによるライブラリを作成していたとする。このDLLは外部に以下のような公開エントリポイントを持つとする。TerminateLibrary();このライブラリを初期化するには、InitializeLibraryを呼び出す。内部の実装(DoSpecialMakeMoney)では別のCOMコンポーネントを呼び出す予定なので、InitializeLibrary内でCoInitializeExを呼び出している。InitializeLibraryを呼び出したスレッドと、DoSpecialMakeMoneyを呼び出したスレッドは別のスレッドかも知れない。TerminateLibraryもまた、別のスレッドである可能性がある。すると、CoInitializeExやCoUninitializeを呼び出す事は何の意味をも持たなくなってしまう(CoInitializeExは、「現在のスレッド」にアパートメントを「設定」するのだから)。それどころか、InitializeLibraryを呼び出した時点で、すでにCOMの初期化が行われているかもしれない。その時点のスレッドは、STAであるべきか、MTAであるべきかは、呼び出し元のみぞ知るわけであり、ライブラリの中では知りようがない(無理やり変更を試みると、RPC_E_CHANGED_MODEが返される)。仮に、InitializeLibrary内でCoInitializeExのエラーコードを適切にハンドリングしたとする(RPC_E_CHANGED_MODEのほかにS_FALSEが返される可能性がある。これはすでに同じアパートメント属性で初期化されていることを示す)。これでエラーは回避できるかもしれないが、そもそも呼び出し元のスレッドはCOMの初期化を行っておらず、InitializeLibrary呼び出しの「後」で、自力でCoInitializeExを呼び出していたとしたらどうだろうか。その実装には関与できないかもしれず、従ってここで示したようなエラーの回避が行われているかどうかわからず、結局のところ、必要とされる正しいアパートメント属性に設定できたのかどうかもあやふやだ。このようなコードは完全な「お手付き」と言える。COMの初期化を行ってあげようという親切心は分からなくもないが...堅牢なライブラリに仕上げるためには、まず、独自にCoInitializeEx(とCoUninitialize)の呼び出す事を止めることだ。CoInitializeExが呼び出されていない場合は、DoSpecialMakeMoneyの呼び出しに対して、COMの初期化が行われていない事を示すCO_E_NOTINITIALIZEDを返す(あるいはカスタムのエラーコードや、C++かつ環境限定なら例外をスローしても良い)。CoInitializeExが呼び出されているかどうかは、COMの何らかのAPIを呼び出せばわかる。APIを使用した時点でCO_E_NOTINITIALIZEDを検出し、異常処理を行えばよい。呼び出し元のコードを記述する際は、スレッドを生成したら、速やかにCoInitializeExでアパートメント属性を確定しなければならない。生成したスレッドがどのアパートメントに属するのが適切なのかは、スレッドを生成した者にしかわからないからだ。#筆者は過去にこのような不適切な実装を行っている、サードパーティ製のコンポーネントを目撃した。#実際に業務に使用していたため、非常に困り、最終的にはサードパーティに「直させた」経験があるが、問題が解消するまでに、結局3か月近く掛かった...#(技術的な問題点をお客様に理解してもらい、さらにサードパーティベンダーにも周知してもらい、修正され、解消されたことを確認するまでに掛かった時間)#ベンダーは技術の内容をよく理解して使ってほしいと思う。.NETのメインエントリポイントが、[STAThread]や[MTAThread]属性を使ってアパートメントを簡単に指定できるようにしているのは、このような理由によるものと推測している。FormsやWPFアプリケーションをウィザードで生成すると、自動的に[STAThread]が適用される。属性を指定していなくても、Thread.CurrentThread.SetApartmentState()を呼び出せば設定できる(内部ではおそらくCoInitializeExを呼び出しているのであろう)が、アパートメント初期化のベストプラクティスは、スレッド生成と同時に設定してしまうというものだ。本格的にコードコントラクトがサポートされるようになったそうだ。いっても、実際に検証するのは別のツールであって、コードにコントラクトが書けるように、標準のインターフェイスが準備されたことだ。System.Diagnostics.Contracts名前空間以下のクラスを使用する。していたような所をこれに置き換えていく。