Win32 APIをてなずけよう – プログラミング生放送勉強会 第38回@名古屋

WP_20151212_17_51_06_Pro_LIプログラミング生放送勉強会 第38回@名古屋、今年もやってきました。「Win32 APIをてなずけよう」というお題で登壇してきました。

今回もギリギリまでかかりましたが、何とか形にできてよかったです。
普段は言語系に生息していますが、プロ生は総合力が試される良い機会だと思っています。きちんと形にするのは大変だ…

等身大のコレには、プロ生ちゃんの中の人のサインが!このサインには重大な意味があるらしいです…


.NETとWin32 APIの融合

Pronama.InteropDemoセッション中でも説明しましたが、WindowsのクライアントサイドはこれからUWPが環境の主役となっていくと思いますが、一方で制限された環境故に外部との連携にはどうしても大きなリソースを必要としてしまいます。

例えば、IoTデバイスであればRaspberry PiやBluetooth LEを持ったデバイス等であれば、UWPにもWiFiやBLE APIがあるので通信が可能ですが、生USBデバイスなどの、もっと軽量なデバイスとの通信のニーズもあるかと思います。また、C言語インターフェイスしか持たないサードパーティ製のライブラリなどもそうです。これらを.NET上で簡単に有効活用するための方法の一つとして、P/Invokeを紹介しました。

内容は、P/Invokeを使用してC#からWin32 APIを呼び出し、最終的にプロ生ちゃんをデスクトップのウインドウ上を走らせてジャンプさせる、というネタです。このセッションではWin32 APIを主眼に置いて、どうやってアプリを成立させるかと言う事を背景に解説しましたが、P/Invoke自体は定義さえ出来れば、Win32に限らず他のライブラリにも応用できるため、前述のターゲットも視野に入れることが出来ます。

ところで、セッション中は解説しませんでしたが、このビデオを見て気が付いた方は居るでしょうか? Visual Studioの上辺を走っている時だけ、ちょっと宙に浮いている気がしませんか?

これは、Visual Studioのウインドウ外周に光彩(ぼんやり光るヴェールのようなもの)のエフェクトがあり、このエフェクトを表示するために、外周を取り囲むように半透明のウインドウがあるからです(つまり、理論的にはバグではないです)。
詳しくはぐらばくさんの記事を見てもらうとして、これを無視するのもちょっと難しい課題ですね。究極的には、人間の目で見てそこに「上辺」があるかどうか、だと思いますが、それを言い始めると半透明だったらどうするんだとか、矩形じゃないウインドウはどうするんだとか、色々ありそうです。

さて、P/Invokeの説明を主体にしていますが、実際に作ったコードはほかの肉付けが大きかったです。ソースコードにはコメントを多数入れて、サンプルじゃなくて現実的なコードを作るにはどうしたらよいか、という事に注意しています。

ソースコードはGitHubに上げてあるので、スライドの最後で紹介した改良を試したりしてみてください。

オリジナルスライドはこちら:Win32 APIをてなずけよう.pptx

それではまた!

式の体を成して無い式を式の体を成すように式と式木で何とかする式 – NGK2015B 名古屋合同懇親会2015忘年会

WP_20151205_14_03_21_Pro_LI年末ラッシュの季節です。NGK2015B 名古屋合同懇親会2015忘年会で、「式の体を成して無い式を式の体を成すように式と式木で何とかする式」の題目で登壇してきました。

NGKは忘年会なので、ネタが多くて面白いです。全セッション完全5分LT形式です。

「高校生がプログラミングを三年間で学んだ軌跡」とか「リアル波動拳をつくってみました」とか、爆笑しました (*´Д`)

今年も気の利いたネタを披露したかったのですが、秋口から体調が優れず、普通ネタを高速展開するという、いつか見たアレになってしまった…

.NET 3.5環境で、.NET 4以降で使えるExpression Treeのシミュレートをしようという内容です。
パズルっぽい感じで何とかなった感あります。ただし、すべてのExpression Tree Nodeがシミュレートできるのかどうかはわかりません。

オリジナルスライドはこちら:式の体を成して無い式を式の体を成すように式と式木で何とかする式.pptx


この記事には、まとめなおした完全版があります。参考にどうぞ:「真・式の体を成して無い式を式の体を成すように式と式木で何とかする式」

ChalkTalk CLR – 動的コード生成技術(式木・IL等)

ChalkTalkCLR12122636_1505045119820235_1955812025993410087_n「ChalkTalk CLR – 動的コード生成技術(式木・IL等) 」というお題で、小規模で濃い話が出来る場を設けてディスカッションしてきました。

今回の会場は、「Maker Lab NAGOYA」さんをお借りすることが出来ました。モノづくり主題のワークスペースで、色んな加工装置とかあって、IoTネタとか常時面白そうなことをやっています。案内頂いたMatsuokaさんは、.NET Micro Framework (GR-PEACH) のバグフィックスとかやっていて、わぉーと言う感じです。

WP_20151017_12_58_31_Pro__highres


開催の背景

「ChalkTalk」という名称は、チョークと黒板を使って、密なディスカッションをする、のような意(のはず)です。普段のCenter CLR勉強会でもディスカッションと言う事は意識しているのですが、もっと密にやりたかったという動機があります。

  • 今回のテーマが、ILや式木なので、いきなり展開するにはディープ過ぎる。
  • でも、個人的にはやってみたい。なので、小規模でやってみて、このネタをどうやって本会でやるのかを練ってみたい。
  • そもそも、Center CLRに近しいメンバーが、この辺りの技術にどれだけ長けているか・追従できるか、と言う事を知りたい。
  • そして、ChalkTalkは面白い(知ったのはde:code 2015)ので、自分でもやりたい。

と言う事が背景にあって、オーガナイザーなんだから、やってみればいいよね、ダメなら今回で終わりにすればいいし、と思って開きました。

WP_20151017_17_49_59_Pro__highres告知通り、事前に資料の準備なし(以前にやった勉強会の資料とかはありますが)としたのですが、あまりにフリーダムだと議論が発散する可能性もあったので、KPTを使って、参加者がどのような課題を持っているのかを視覚化してみようと思いつき、このような感じで5分で書き出してもらいました。

一部の参加者が「COMが~COMが~」と言っていたのですが、COMの基礎をやり始めるとそれだけで潰れてしまうので、やむなく我慢してもらうことに (;´Д`) 何故COM?と聞いてみたところ、「WinRTのバックグラウンドにCOMがあるらしいけど、もうググっても情報が得られなくて云々…」との事。この、某学生MVPには、やたら熱くアピールされたので、COMについてはまたChalkTalkでリベンジすることにしました。

結局、KPTでは各参加者のレベル確認的な所となり、順当にILとかCLSの説明からスタートする事に。KPTの導入は、私のアジャイル方面への試みの一つでもあるんですが、全然生かせていないのは今後の課題だなと感じました。
題目が「IL・式木」と言うように、初めから技術にフォーカスしていて、これらの技術を使う動機というか、バックグラウンドと言うか、そういうところに焦点が当たりにくかったのも敗因かなと思っています(技術フォーカスだとわかりやすいし、もともとは自分がやりたかった話なので、仕方ないんですが)。


ILとCLSの話

makerlab1x86とかx64のような物理CPUアーキテクチャの話と、それをつなぐCLSやILのコードの関係・実行時に何が起こっているのかという概要の話をしました。参加者には経験が浅くてILとかそもそも中間言語や仮想CPUとかわからない方も居たので、概要レベルでの導入を試みました(どんなエキスパートでも、最初に通る道なので、知らなくても恥じる必要はないです)

特に、仮想的なCPUが「スタックマシン」であり、スタックの出し入れで処理がおこなわれる、という所が焦点だと思ったので、ILSpyで逆コンパイルした結果を見せ、仮想CPUのスタックにどのように値が出し入れされるのかを、詳しく説明しました。この部分は、以前にMGK2015(三重合同懇親会)で、LTでILを説明するという無謀を試みたことがあり :)、その話も交えながら。写真のように、白板にスタックの図を描いて、ILコードからどのようにスタックが操作されるのかを詳しく説明しました。

ILの仮想CPUがスタックマシンであることと、もう一つ重要な概念として「box化(boxing)」とその解除の話をしました。.NETにはValueType型があり、この型を継承した型が構造体(struct)となって、ILのレベルでは扱いが異なると言う事を話しました。マサカリ来ましたねー (;´Д`) ValueType型自身は構造体ではなく、クラスでしたね(今、ちゃんとリファレンスで再確認しました)。

構造体のインスタンスは、ローカル変数領域(.locals)や、フィールド変数などで、直接そこに値が格納されます(物理CPUでどう実装されるのかは、ひょっとしたら違いがあるかもしれません)。それに対して、「参照型(クラス)」は、不透明なポインタ値がそこに格納され、実際の値(インスタンス)は、ヒープ領域に格納されます。object型のフィールドや変数にインスタンスを格納する場合、構造体のインスタンスであれば「box命令」「unbox.any命令」で、ヒープ領域への格納と取り出しが行われる、というのを図示しました。この操作にコストがかかることについても理解してもらえたと思います。

トリビアとして、box化を忘れたり、やらなくてもよい所でbox化や解除したりすると、おかしな現象が起きたり起きなかったり、InvalidProgramExceptionで落ちるよ、みたいな話もしました。CLRがロード時にILに対してどこまでの検査をしている・していないのか、というのは興味深いところです。bleisさんからは、PeVerifyツールを紹介して頂きました。自分でEmitで生成したコードに問題がないかどうかを、静的に解析できます。ILSpyは、ILの使用方法が間違っていても、一見それっぽく逆コンパイルコードを表示してくる(クラッシュする場合もある)ので、初めのうちは特に注意した方が良いでしょう。

ILについては、あとは命令のバリエーションを理解すれば、少なくともILSpyで逆コンパイルしながらコードを調整していくことで、そこそこILが書けるようになると思います。と、ここで、「IL Support」という拡張機能の事についても教えて貰えました。これを導入しておくと、il用のソースを別に用意しておき、C#側からそれを参照する定義をexternで書いておくだけで、動的にEmitしまくる事無く静的に自分のコードにILを導入できます。更にILソースファイルは、pdbにデバッグ情報も出力されるので、デバッガでILにステップイン出来るという、正に神ツールです。すばらすぃ….

ILを書いたソースコードファイルをhoge.ilのようなファイルで保存し:

.namespace CenterCLR
{
	.class public ILSupportSampleClass
	{
		.method public static int32
			Compute(int32 a, int32 b) cil managed 
		{
			ldarg.0
			ldarg.1
			add
			ret        
		}
	}
}

C#側からはMethodImpl属性で参照可能にします。

namespace CenterCLR
{
	public class ILSupportSampleClass
	{
		[MethodImpl(MethodImplOptions.ForwardRef)]
		public static extern int Compute(int a, int b);
	}
}

式木の話

ILの話は低レベルでなじみが薄いため、一般的にはILの方が難しいような印象がありますが、上記の話を押さえておけば、結構誰でも掛けたります。間違ってると簡単にクラッシュもしますが。しかし、式木については概念からわかってないと扱いづらい(≒解説が長期化してしまう)と言う事もあって、どうやってディスカッションするか、考えあぐねていました。

第一回~第三回のCenterCLR勉強会でも度々取り上げてきたので、参加者には「ぼんやりと」式木の概念は伝わっていたようですが… ここでもbleisさんに、式木についての説明をしてもらいました。王道的に計算式的な定義から、ノードに分解される様、そしてノードの木構造があれば、プログラマブルに探索が可能・変形も可能、という感じでの解説でした。簡潔で分かりやすい。ちゃんと伝わったかな?

式木は:

  • (C#において)ラムダ式からExpressionクラスのインスタンスを取得して、式を動的に解釈する(Entity FrameworkのようなO/Rマッパーが行っている手法)
  • Expressionクラスのインスタンスを動的に組み立て、Compileメソッドを呼び出して、実際に実行可能なデリゲートを入手する
  • 式木ノードを探索して、メタデータ参照に役立てる

というような代表的な用途があることを説明しました。

最初の例では、Entity Frameowrkの話だけでは終わってしまうので :)、Final LINQ Extensions IIIで解説した、即席O/Rマッパーについて軽く復習して、式木の内容をどうやってサーバーサイドで実行するか、のような話をしました。

次に、がりっち=サンに話を振って、「某ronia」というツイッタークライアントのフィルタ条件を動的に構築するために、入力されたフィルタ式をパースして(ここは手動)、Expressionを生成して式木を組み立てて、フィルタ処理を実現した、という話をして貰いました。

最後に、EXCEL仕様書の駆逐ネタでやった、メタデータの参照利用としての式木の話をしました。

そのほか、貴重なお話として、bleisさんの業務での式木の利用経験として、F#の式木をどうやって解釈するのかを考えたという話をしてもらいました。F#の式木は、概念的にはC#(というより.NET FrameworkのExpression)と同じようなものですが実際は異なり、高速化のためにExpressionにマップしてCompileするか、またはF#式木を直接解析してILを生成するか、のような事で、いくつか障害があって頭を悩ましたという話を聞くことが出来ました。この話、以前に何かで見た記憶があるなーと思っていたのですが、今になって繋がった!みたいな、妙な感動がありました。

# 関数型言語にまつわるドタバタで徘徊していた時に見た気がする。
# (えーと、私は面白い技術であればおk的な立場です)

ちなみに、このF#の解析器、手伝ってくれる人募集中とのこと。私が手伝うには、F#をモノにする必要があるな….


総括

WP_20151017_17_54_15_Pro__highres結局、結構時間もおしてしまって、いつも通り(?)最後にバタバタしてしまったのは申し訳ないです。クロージングでKPTを再度書いてもらったのですが、振り返りの時間がまともに取れなかったのでダメですねー。でも、ディスカッションとしては楽しい時間を過ごせました。参加者の皆さんありがとうございます。

課題的にはやはりというか、参加者に想定する知識レベルの差をどうやって埋めるか、と言う事が課題だなと思います。長い目で見た場合、Center CLRへの参加者のレベルが全体的に底上げされて来れば、だんだん解消されるかなと思っています。気の長い話ですが、コミュニティベースなので、このぐらいが良いかなと。ChalkTalkについても、レベル差で細分化した方が良いのかもしれません。私のマンパワーにも限界があるので、中々難しい所ですが、あまり小難しく考えず、トライして方針を修正していく、という感じでやりたいと思っています。

それにしても、白板に書きまくったのに、写真全然撮ってなかったのも残念。だれかカメラ担当お願いすれば良かったです orz

不健康なIT戦士を健康的にするアレの話 – 第13回まどべんよっかいち勉強会

WP_20151010_13_18_22第13回まどべんよっかいち勉強会で、「不健康なIT戦士を健康的にするアレの話」というタイトルで登壇してきました。
今回の会場は、別の部屋でニコニコNTもやっていたので、ちょっとびっくりしました。

内容ですが、今回はIT技術の話からちょっとだけ離れて、健康の話をしてきました。たまにはこういう話も刺激になっていいんじゃないかな~的な感じです。こういう方面も、ちょっと掘り下げて調べると、色々なことが分かって面白いです。

今回のセッションはLTだったのを無理やりショートセッションぐらい使ってしまった(ごめんなさいごめんなさい)のですが、まだまだ盛り込んでいない要素があります。希望が多ければ、どこかで完全版をやろうかなと思っています。


オリジナルスライドはこちら: 不健康なIT戦士を健康的にするアレの話.pptx

SourceTreeで始めよう! Gitへの乗り換え指南 – Atlassian User Group NAGOYA 第3回 ユーザーミーティング

「SourceTreeで始めよう! Gitへの乗り換え指南」というタイトルで、AUG名古屋 第3回 ユーザーミーティングで登壇させて頂きました!

augn1Atlassianの製品では、JIRAを少しだけ、後はSourceTreeをどっぷりと使う、という経験値なのですが、以前登壇したGitの解説を元に、SourceTreeやBitbucketに絡めて話をさせて頂きました。

スライドの分量は多めなので、適当に端折って進行しました。
途中、解説が甘い部分にマサカリが (;´Д`) すいません、精進します。

あと、最終版がありましたので、ここで公開するスライドは最終版で公開しておきます。

ご静聴ありがとうございました。

オリジナルスライド:SourceTreeで始めよう! Gitへの乗り換え指南.pptx

augn2あと、登壇特典貰いました!Atlassianロゴ入りBluetoothスピーカーですよ!! やっほい!

会場は美味しい食べ物と酒が呑める(私は食べる専門ですが)場所で、あんなお店でプロジェクター完備だとは思いませんでした。Atlassian ExpertsのHSDさんの協賛で、会費もお値打ちでした。いつかうちでもやってみたいですね。

スライド中でも紹介しましたが、Windows系のSourceTree向けに、マージツールとして「TortoiseMerge」のポータブル版を作ったので、公開してあります。

GitHub: TortoiseMerge Portable

tortoisemergeportableSourceTreeは、サードパーティ製のマージツールをいくつかサポートしていて、TortoiseMergeも対応しています。しかし、公式パッケージはTortoiseSVN内に含まれた形で配布されており、TortoiseMergeだけを使いたい場合でも、TortoiseSVNをインストールしなければならず、インストールするとエクスプローラーがとてもアレになってしまうのが嫌だ… という向きに最適です。

ポータブルと銘打っていますが、純粋なポータブルとしてのバイナリファイルを固めただけの物と、MSIによるインストーラー版があります。インストーラー版は32ビット版しか用意していませんが、ぶっちゃけ、64ビット版が必要になるとも思えないので、64ビット版を作る気は多分起きないです(勝手にフォークして作ってください)。

注意点として、既にTortoiseSVNを使っている環境には入れないで下さい(64ビット版TortoiseSVN環境に、32ビットのインストーラー版を入れるとおかしくなるかも知れません)。

それではまた。

Final LINQ Extensions II – 第三回 Center CLR 勉強会

finallinqextensions2第三回Center CLR勉強会で、Final LINQ Extensions IIのタイトルで登壇してきました。

ご静聴ありがとうございます。
前回網羅できなかった、細かい話の落穂広いと、標準の演算子を一通り説明しました。
標準演算子が使いこなせるようになると、LINQでの実装がはかどる事間違いなしですよ。

にしても、あーあ、やっちまいました (;´Д`) 次回もよろしくお願いします!!


オリジナルスライドはこちら: Final LINQ Extensions II.pptx

第一回 三重合同懇親会 MGK2015

第一回 三重合同懇親会が、三重県津市で開催されたので行ってきました。

mgk2015
名古屋でクロスコミュニティな集いを「名古屋合同懇親会 NGK2014B」として開催していたのを、三重でもやりたい! というところから始まった企画で、Center CLRの紹介とLTの二本立てで参加しました。

参加人数は、26名と、会場に対して結構ぎっちりで、中部圏からちょっと離れた(失礼!)場所なのに、参加人数にびっくりしました。

この懇親会で分かったのは、「三重、IoTが熱い!」と言う事です。LTで伊勢方面のIT歴史を紹介していただいたのですが、伊勢発祥のデバイスメーカーって、結構多いんですね。前から伊勢方面は(理由がわからないけど)過熱しているというイメージがあって、実際に下地があるんだなという事が(良い意味で)意外でした。

で、いつもながら準備時間が十分取れなかったのですが、CenterCLRの紹介と、CenterCLR的ネタを三重にかましてきましたよ!!


Center CLRにおこしやす(誰

Center CLRの紹介です。特に何って事もないです… いや、興味ある方、ぜひぜひ参加どうぞ!!


Hello! Intermediate Language

これ、本当ならLTでやるネタでもないんですが、以前にがりっちさんがNawaTechで頑張ってLT枠でショートセッションネタをやってたのを見て、チャレンジしてみようと思ってたので、やってみました。

超絶まくってたので、理解のほどはどうかな?という感じですが、ちゃんとスタックマシンの話もしましたよー

セッションで説明したコードはGitHubにアップしてあるので、見たかった!という人は確認してみてください。

オリジナルスライド:Hello_Intermediate_Language
GitHub:CenterCLR.EmitLiveDemo


今日は.NET MicroFrameworkに積極的にかかわっている方との情報交換も出来て、IoT全般のコミュニティの取り組みとか、もっと大きな技術者的業界生き残り戦略がどーのこーのとか、物理演算シミュレーションがアレとか、主婦と子供のITへの関わりとか、想像もしなかった盛りだくさんで面白かったです。次回も参加しますよ!

それでは、また。

ポイントをおさえて移行しよう!Git乗り換え超初級 – 第11回まどべんよっかいち

Madoben12

第11回まどべんよっかいちにて、「ポイントをおさえて移行しよう!Git乗り換え超初級」というタイトルで、登壇してきました。
参加人数も11名と、徐々に増えて良い感じでした。

ここまで.NETがらみの登壇が多かったのですが、今回は少し毛色を変えて、Gitについての知識共有をしようかと思いました。スライドでは書いていませんが、約30名程度の組織で一からGit展開した際に得られた知見のうち、特に最初「わけがわからないよ!!」と思った、ひっかかりそうな部分について、一通り網羅できたかなーと思います。

Gitの操作事始め的なレベルはクリアしたんだけど、どうも仕組みが良く分からない、と思っている方は、一度読んでみて下さい。

なお、セッションのスライドからは若干加筆修正しています(特にアレな間違いはばっちり修正しておきました!!(;´Д`)
参加された方も、もう一度見直してもらえると嬉しいです。

オリジナルスライドはこちら:ポイントをおさえて移行しよう!Git乗り換え超初級.pptx

それではまた。

列挙可能から完全なるモノまで – IEnumerableの探索 – C# Advent Calendar 2014

この投稿は、C# Advent Calendar 2014 の14日目の記事です。

IEnumerableにまつわるアレやアレ

こんにちは! いつもはAsyncとLINQの事をしゃべったり書いたりしています。「列挙可能から完全なるモノまで – IEnumerableの探索」というお題で書いてみようと思います。

.NETでは、LINQにまつわるインターフェイスがかなり多く含まれています。その中でも、IEnumerableインターフェイスの継承グラフに存在する様々なインターフェイスを、どのように使い分けるべきか、と言うのが分かりにくいかも知れません。沢山のインターフェイスが定義されているのは、歴史的な事情もあります。

LINQ to ObjectやLINQ to Entitiesなど、一般的に使用されているLINQの背景には、「列挙可能である」という性質があります。この事は、以前にLINQの勉強会でも取り上げましたが、もっと分かりやすく掘り下げたいと思います。


Back to the IEnumerable – .NET 1.0

LINQにおいて「列挙可能」であるとは、つまり「IEnumerable<T>」インターフェイスを実装している事、という事です。例えば、次のような簡単なLINQを考えてみます。

// データを格納するintの配列
int[] sourceDatas = new int[] { 123, 456, 789 };

// データ配列から、偶数の値を抽出する
IEnumerable<int> evens = sourceDatas.Where(data => (data % 2) == 0);

.NETの配列は、System.Arrayクラスを暗黙に継承しています。このリファレンスを見ると、以下のようなインターフェイスを実装していることが分かります。

// (無関係なインターフェイスは除外)
public abstract class Array : IList, ICollection, IEnumerable

これを図示すると、こういう事です。

enumeration1

  • IEnumerableインターフェイスは、「列挙可能」である事を示します。「示す」と言っても、ただ表明しているわけではありません。列挙可能にするための「GetEnumerator」メソッドを実装しています。ここではその詳細に踏み込みませんが、C#の「foreach」を使用する時、C#のコンパイラは暗黙に「GetEnumerator」メソッドを呼び出して、要素の列挙を実行します。そのため、「IEnumerable」を実装している=列挙可能、と見なせるわけです。
    だから、配列に対して、直接foreachが使えます。
    (厳密には異なるのですが、このように解釈していて問題ありません)
  • ICollectionインターフェイスを実装しているクラスは、要素数を把握可能である事を示します(他にも機能はあるのですが、省略)。要素数は、皆さんおなじみの「Count」プロパティで取得できます。
    ICollectionはIEnumerableを継承しています。そのため、ICollectionにキャスト出来た場合は、勿論「列挙が可能」と言う事になります。
  • IList インターフェイスは、リスト形状のコレクションに対する、追加・変更・削除も含めた、全ての操作が可能である事を示します。このインターフェイスは、ICollectionを継承しているので、要素数の把握も可能(つまり、Countプロパティが使える)で、更にIEnumerableも継承していることになるので、列挙も可能です。

さて、これらのインターフェイスが登場したのは、.NET 1.0、つまり最初から存在するインターフェイスです。気づいたかもしれませんが、これらのインターフェイスには、要素の型が含まれていません。例えば、IEnumerableインターフェイスで列挙しようとすると、System.Objectが要素の型となります。これでは列挙した後、適切な型へのキャストを行わなければなりません。

実際、「foreach」を使用して列挙する場合、ループ変数の型を明示的に指定する事で、暗黙にキャストしていました。意味的には以下のようになります。

// .NET 1.0世代での列挙

// 明示的にIEnumerable型の変数に代入
IEnumerable sourceDatas = new int[] { 123, 456, 789 };

// foreachでループ(一般的な記述)
foreach (int value in sourceDatas)
{
  // ...
}

// 実際にはこういう事
foreach (object untypedValue in sourceDatas)
{
  int value = (int)untypedValue;

  // ...
}

ところで、配列の基底型であるSystem.Arrayが、IListインターフェイスを実装しているのはおかしいと思うかもしれません。配列の要素数は、一度生成されたら「不変」な筈です。そのため、ICollectionインターフェイスの実装までが妥当と思われますが、そうなっていません。実際、配列のインスタンスをIListインターフェイスにキャストし、Addメソッドを呼び出して要素を追加しようとしても、例外がスローされます。

このような設計になっている理由は公式には明らかになっていないと思いますが、IListインターフェイスには「インデクサ」が定義されています。このインデクサを使用可能にするため、敢えてIListインターフェイスを実装しているのではないかと推測しています。

(しかし、結局これは誤った設計だったと個人的には思っています。理由については後述)


ジェネリック引数で要素の型を伝搬 – .NET 2.0

上図では、Arrayを継承してintの配列を生成しています。Arrayは列挙可能であったり、要素数の把握が可能と言う事になっています。しかし、その要素の型はSystem.Objectであり、intへのキャストが必要である事は説明した通りです。では、配列を対象とした場合、IEnumerable<int>のような、要素型が明示されたインターフェイスでは使用出来ないという事でしょうか?

enumeration2

そんな事はありません。.NET 2.0でジェネリック型がサポートされ、このように配列の実装インターフェイスが拡張されました。「Array<T>」クラスが導入されたわけではなく、配列を定義すると自動的に「IEnumerable<T>」インターフェイス「ICollection<T>」インターフェイス「IList<T>」インターフェイスが、直接配列の型に実装されます。

「IEnumerable<T>」は「IEnumerable」も継承しているので、根本的な「列挙可能」という性質は受け継いでいると言えます。しかし、「ICollection<T>」は「ICollection」を継承しておらず、「IList<T>」も「IList」を継承していません。

この理由についても推測するしかありませんが、「ICollection<T>」は設計上、ICollectionのジェネリックバージョンではないと考えられます。ICollectionには存在しない「Add」等の要素数の変化を伴う操作メソッドが追加されています。「IList」があまりに多くの役割を担い過ぎている事への反省でしょうか。

しかし、これが原因で、ICollectionやIListの対比が分かりにくくなっています。IList<T>を実装するクラスを見た場合、暗黙的にIListも実装しているのではないか?と考えてしまいますが、そうなっていません(ここで示した配列や、List<T>クラスは、わざわざIList「も」実装する事で、この問題に対処しています)。

それにしても、普段コードを書いていると、「ICollection<T>」ほど「触らない」インターフェイスも珍しいです。一番の問題はやはり「インデクサ」が定義されていない事で、わざわざICollection<T>を操作する機会がほとんどありません。対照的に、「IList<T>」は良く使いました。


LINQの登場 – .NET 3.5

さて、このようなコレクション操作の抽象化を行う下積みがあり、とうとうLINQが登場します。LINQは、IList<T>のような高機能なインターフェイスではなく、List<T>クラスのような個々のコレクションクラスへの直接拡張でもなく、「IEnumerable<T>」インターフェイスだけを要求してきました。

ここに「拡張メソッド」のサポートが加わり、「列挙可能な要素群」を包括的に操作可能となったのです。LINQが.NETやC#と切り離せない関係にあるのは、ここで改めて説明するまでも無いでしょう。

IEnumerable<T>しか要求しないので、ここまでに解説したほとんどのインターフェイスは、実装さえしていれば即LINQで応用が可能と言う事になります。IEnumerable・ICollection・IListの「非ジェネリック」版インターフェイスについては、残念ながらLINQでそのまま使う事が出来ません。大きな問題は、列挙可能な要素の「型」が分からない事です。

しかし、型を明示的に与えれば、LINQ演算子を適用する事が出来ます。

// 明示的にIEnumerable型の変数に代入
IEnumerable sourceDatas1 = new int[] { 123, 456, 789 };

// LINQで使うには、Cast演算子を使って型を明示すればよい
IEnumerable<int> evens1 = sourceDatas1.Cast<int>().Where(data => (data % 2) == 0);

// 様々なインスタンスが入っている状態
IEnumerable sourceDatas2 = new object[] { 123, "ABC", 456, Guid.NewGuid(), 789 };

// 何が入っているか不明な場合は、OfType演算子を使って型で絞り込みを行う事も可能
// (指定された型以外のインスタンスが入っていた場合は、そのインスタンスは無視される)
IEnumerable<int> evens2 = sourceDatas2.OfType<int>().Where(data => (data % 2) == 0);

IEnumerableは、列挙される要素の型が分からないため、必ず想定されるインスタンスが取得されるとは限りません。OfType演算子はそのような場合に使用することが出来ます。Cast演算子を使用しながら、異なる型が取得された場合は、例外がスローされます。

今でも、古いWindows Formsクラスのコレクションで、IEnumerableやIListだけが実装されているクラスがあります。それらのインスタンスに対しても、この演算子を使えばLINQで操作可能となります。

.NET 3.5の世代では、一連のインターフェイスに新たに付け加えられたインターフェイスはありませんでした。次の拡張は.NET 4.5を待つ事になります。


読み取り専用とインデクサ – .NET 4.5

enumeration3

.NET 4.5がリリースされ、突如として「列挙可能」インターフェイスに新顔が追加されました。それが「IReadOnlyCollection<T>」インターフェイス「IReadOnlyList<T>」インターフェイスです。

名前から想像出来るように、これらのインターフェイスは、要素の読み取りのためのインターフェイスで、IReadOnlyCollection<T>は要素数「だけ」が取得可能、IReadOnlyList<T>は読み取り専用インデクサが定義されています。

このインターフェイスを初見した時の率直な感想としては、「遅すぎた」でした。.NET 1.0世代で書きましたが、すでにこの時「読み取りしか行わないけど、インデクサが欲しい」という状態は露呈していました。世代が進んで初めて必要となったのであれば分かるのですが、.NET 1.0においても既に分かっていた事のように思います。理由として、「IsReadOnly」プロパティの存在が挙げられます。このプロパティを追加する際に、当然IReadOnlyCollectionやIReadOnlyListを検討する事も可能だった、と思うと残念なのです。

残念な出自もさることながら、残念な現実もあります(だから余計に残念)。今更、ICollection<T>やIList<T>インターフェイスの継承階層の適切な位置に、これらの新しいインターフェイスを潜り込ませる事は「出来ません」。そんな事をすれば、既存のICollection<T>やIList<T>等のインターフェイスを実装しているクラスが破綻してしまいます。インターフェイスは契約の一種です。後から変更されるとバイナリ互換性が失われます。

.NET内部ではこのインターフェイスをどう扱っているのでしょうか? 図に示したように、新しいインターフェイスは配列の具象型が「直接」実装する事になります。これまでのインターフェイスとは全く融合せず、唯一IEnumerable<T>を継承する事で「型明示で列挙可能」である事は表明しています。同様に、List<T>クラスも、クラスが直接新しいインターフェイスを実装する事で、互換性を失わないようにしています。


メモ:先人を擁護するなら、.NET 1.0の頃はまだJavaとの対比が強く、「マルチ言語」に強くこだわっていました。インデクサという機能は、様々な言語で再現出来ない可能性があり、C#やVB.net特有の機能とみなされており、IL上でも特別な扱いを受けていました。そのような機能を、mscorlib内のスタンダードなインターフェイスとして定義するのははばかられたのかも知れません。
ぶっちゃけ、素でインデクサを表現できないなら、「Item」メソッドで良いじゃん?と思わなくもないんですがw


インターフェイスバリエーションの物理的な側面

IEnumerableにまつわるインターフェイスの歴史的な側面を挙げてきました。物理的な側面についても触れておきます。

多くのインターフェイス定義の、どれを選択するのか?

単に列挙するだけならIEnumerable<T>、リストの参照や操作を行う場合はIList<T>型を指定させ、意図の違いをコード上に表現します。

// 与える引数は列挙しかしないので、int[]ではなく、IList<int>ではなく、IEnumerable<int>として意図を推測しやすくする
public static int SumEvens(IEnumerable<int> datas)
{
  // (わざとベタアルゴリズムです)
  int result = 0;
  foreach (var data in datas)
  {
    if ((data % 2) == 0)
    {
      result += data;
    }
  }
  return result;
}

// 配列の指定はOK
int[] sourceDatas = new int[] { 123, 456, 789 };
SumEvents(sourceDatas);

// List<int>の指定もOK
List<int> sourceDatas = new List<int> { 123, 456, 789 };
SumEvents(sourceDatas);

基底となるインターフェイスで引数を定義する、と言うのは意図の明確化でもあり、応用性を高める点でも重要です。上記の例では、引数にはあらゆる「列挙可能」なインスタンスを指定する事が出来ます。しかし、仮に具象クラスや具象クラスに近いインターフェイスを指定した場合はどうなるでしょうか。

// IList<int>を指定した場合
public static int SumEventsFromIList(IList<int> datas) { ... }

// 配列の指定はOK
int[] sourceDatas = new int[] { 123, 456, 789 };
SumEventsFromIList(sourceDatas);

// List<int>の指定もOK
List<int> sourceDatas = new List<int> { 123, 456, 789 };
SumEventsFromIList(sourceDatas);

// しかし、内部では列挙しているだけなのだから、IList<T>である必要はない。
// そして、IList<T>で渡していると、リストが書き換えられる可能性がある、ように見える。

//////////////////////////////////////

// 配列を指定した場合
public static int SumEventsFromArray(int[] datas) { ... }

// 配列の指定はOK
int[] sourceDatas = new int[] { 123, 456, 789 };
SumEventsFromArray(sourceDatas);

// List<int>の指定はNG
List<int> sourceDatas = new List<int> { 123, 456, 789 };
SumEventsFromArray(sourceDatas);   // 型不一致エラーでコンパイル出来ない

//////////////////////////////////////

// List<int>を指定した場合
public static int SumEventsFromList(List<int> datas) { ... }

// 配列の指定はNG
int[] sourceDatas = new int[] { 123, 456, 789 };
SumEventsFromList(sourceDatas);   // 型不一致エラーでコンパイル出来ない

// List<int>の指定はOK
List<int> sourceDatas = new List<int> { 123, 456, 789 };
SumEventsFromList(sourceDatas);

大したことではない、と思うかもしれません。一年後、コードのメンテナンスをしなければならない時に、内部実装を読む事無く、すばやくメソッドの「意図」を把握出来るのはどちらでしょうか? IEnumerable<T>のように、より制限された型を指定しておくと、そのような負担から解放され、少しでも楽になります。

.NET 4.5以上であれば、IReadOnlyList<T>も使い分けの対象に入れると良いでしょう。このインターフェイスはまだまだ実装クラスが少ないですが、見ると安心感が違います(少なくともIList<T>を引数に渡す時には、書き換えの可能性が頭をよぎります)。

共変性と読み取り専用インターフェイス

読み取り専用として定義されたインターフェイスが残念な事になっているのは説明した通りです。このインターフェイスがわざわざ遅れて導入された理由と思われるものとして、要素型の「共変性」があります。共変性については「ジェネリクスの共変性・反変性 – 未確認飛行 – ++C++;// 未確認飛行 C」が詳しいので参照してください。

読み取り専用であれば(或は、読み取り専用として定義する事により)、インスタンスの代入互換性が高まるという事が趣旨にあります。ちなみに、この共変性の性質は.NET 1.0からありました。配列についてだけは、要素型の暗黙的なキャストが成立する場合において、代入互換があることになっていました。

// stringの配列
string[] values = new string[] { "ABC", "DEF", "GHI" };

// objectの配列は、要素型がstring→objectへの暗黙のキャストが成立するので代入可能(暗黙の共変性・但し、値型は不可)
object[] objectValues = values;

実行時判定

今までの話は、全てコンパイル時に型を特定する「型安全」についてフォーカスしたものです。LINQのパフォーマンスについての疑念の話をしましたが、実際にはより最適なアルゴリズムを採用するために、実行時のインスタンスの型を調査しています。

去年書いた、Advent LINQ:Countや、Advent LINQ:ElementAtのように、実行時に様々なインターフェイスにキャスト可能かどうかを判定して、より効率の高いアルゴリズムを採用する、という手法です。

Countの例はIReadOnlyCollection<T>に対応していなかったので、書き直してみます。

public static int Count<T>(this IEnumerable<T> enumerable)
{
  // 列挙子がIReadOnlyCollection<T>を実装していれば
  var rocollection = enumerable as IReadOnlyCollection<T>;
  if (rocollection != null)
  {
    // 直接Countプロパティを参照する
    return rocollection.Count;
  }
 
  // 列挙子がICollectionを実装していれば
  var collection = enumerable as ICollection;
  if (collection != null)
  {
    // 直接Countプロパティを参照する
    return collection.Count;
  }
 
  // 仕方が無いので、列挙してカウントする
  var count = 0;
  foreach (var value in enumerable)
  {
    count++;
  }
  return count;
}

残念なインターフェイスの通り、IReadOnlyCollection<T>とICollectionは、互いに全く関係がありません。そのため、キャストが成立するかどうかを別々に確かめる必要があります。


あとがき

ちょっと、頭の中を整理したい事もあって、詳しく書き出してみたつもりだったのですが、要所でコード書いて確認してみないと自身が無い部分とかあり、良いリハビリになったなと思いました。

今年もあと少し、実際にはC# Advent Calendarは次週も担当です。進捗はダメです (;´Д`)
それではまた。

次は「a_taihei」さんです。よろしく!

WinRTベースのWindows Phone内でスクレイピング – Windows Phone Advent Calendar 2014

この投稿は、Windows Phone Advent Calendar 2014 の 8日目です。

WinRTベースのWindows Phone内で、HTMLのスクレイピングを試みるネタです。とはいえ、実はこれは約1年前の年始早々に取り組んだネタの続きでもあります。

Windows PhoneもWinRTベースの環境と、続々と出荷されるWindows Phone 8ベースの端末(何故か国内では発売されていない。「何故か」w)のおかげで、すっかりWinRTも浸透してきたようです。

pronama_wp_wp_800

(写真に登場するLumia 1520とプロ生ちゃんはフィクションであり、この記事とは無関係ですw)

SgmlReader for Portable Class Library

以前に作ったスクレイピングのライブラリは、
「SgmlReader」と呼ばれるものをPortable Class Library化したものです。このライブラリは、「SGML」をパースしてXmlReader化するものですが、HTMLのDTDが添付されており、要するに「普通のHTMLパーサー」として使えるところがミソです。

移植した際に、Profile1ベース(つまり、最初期のPCLで、環境的に何でもOK)という縛りをつけたので、ストアアプリ前までの環境では、あらゆる環境で使用可能です。

pcl1

この柔軟性はかなり美味しくて、他に依存しているパッケージも存在しないため、どのようなプロジェクト(Windows Phoneに限らず)にでも安心して追加する事が出来ます。

が、WinRT環境では、PCLの設定が排他的になっており、この例のように「全部入り」にしようとしても出来ません。

そのため、WinRT用に別のPCLプロファイルを組み合わせたライブラリを追加し、SgmlReaderをバージョンアップさせようと思っていたのですが… ズルズルと今の今まで放置プレイ(困っていなかったと言うのが大きい)。

Windows Phone Advent Calendarがあるとの事だったので、ようやく重い腰を上げて取り組むことにしました。


夜明け前(前のバージョン)

Portable Class Libraryへの移植は「最大公約数」的な対応が必要です。SgmlReaderの最初の移植では、WebClientクラスを使用して直接HTTP通信を行う部分がネックとなりました。

今なら「Microsoft HTTP client libraries」という、これまたPCLで非同期な優秀なパッケージがあり、これを使えばかなりの広範囲で移植が可能なパッケージを作ることも出来たのですが、元々SgmlReader自体がシンセティック(パーサーなので、理論上、計算しかしない)なので、何にも依存しないパッケージに仕上げたかったのです。

取り組んだこと:

  • WebClientを使っている箇所を削除
  • エラーが発生する部分を修正
  • NuGetパッケージ化

ストラテジとして、WebClientの代わりにデリゲートを導入し、HTTP通信に関わる部分をDI的に挿入できるようにしようと考えていました。ところが、SgmlReaderは「SGML」であるために、DTDの外部参照を自力で解決しようとします。そこで再帰的にWebClientを使ってダウンロードを試みようとするため、この部分まで含めた対応が必要でした。

結局、初期のストリーム(ストリームと言いながらTextReaderなんですが)を与える以外にも、任意のURLからのダウンロードを可能にするデリゲートを挿入可能とし、WebClientへの依存を排除しました。これが前のバージョンです。

また、NuGetパッケージ化は「NuGet Packager」というAddinを使って行いました。NuGetのパッケージ化は面倒です。今のところ、サクッとやる方法が無く、nuspecファイルを手動で編集する必要があります。NuGet Packagerはビルド部分を自動化出来ますが、結局NuGetの構造を知らないと書けないと思います。そして、NuGet Packagerはどうも、その、MSBuildの流儀から外れているのか、ゴミファイルが散乱したりして、落ち着いて保存できないのもモヤモヤしていました。


本番

sgmlreaderstoreapp1png

そして、今回のバージョンアップです。まず、ストアアプリ向けのPCLプロジェクトを追加します。前のバージョン(Profile1)と併存させるため、わざとVisual Studio 2012で作ります。

pcl2

PCLの設定は、グレードを上げて、こんな感じに。

ソースコードは、前のプロジェクト内のソースファイルに対して「リンクで追加」を使って、参照として追加しておきます。

SgmlReaderの最初のバージョンでは、HtmlのDTD参照をフックして、組み込みリソースとしてありました。DTDの指定がHtmlとなっている場合は、Assembly.GetManifestResourceStream()を使って、これを読み取る事で、リソースメンテナンスフリーにしていたのです。

ところが、WinRT環境では、Type.Assemblyが未定義です。これはどうしたものかと思っていたら、Resharperがヒントをくれました。Typeクラスへの拡張メソッドとして「IntrospectionExtensions.GetTypeInfo()」があります。名前が意味深ですね。とにかくこれで、TypeInfoクラス経由でAssemblyクラスのインスタンスが手に入り、GetManifestResourceStream()を使う事が出来ました。

(Resharperが無かったら短時間で探せなかったかも。今見たら、TypeInfoには面白そうなアレコレがありそうだ…)

あとは、WinRT版との切り分けに、「#if NETFX_CORE」を使うだけです。こんな感じ:

#if NETFX_CORE
using (var stm = type.GetTypeInfo().Assembly.GetManifestResourceStream(name))
#else
using (var stm = type.Assembly.GetManifestResourceStream(name))
#endif

さて、ビルド出来たらNuGetへの対応です。先ほど紹介したNuGet Packagerは、どうも完成度がいまいちだったので、別の何かが無いかどうかを探してみました。何しろあれから1年です。

で、見つけたのが以下の二つ:

  • NuGet Package Project – MSの人が(個人的に)作っている拡張機能で評判は良さそうですが、いかんせんVS2013のみの対応。
  • NuBuild project system – VS2010~2013まで対応していて、素朴ではあるものの、妙な挙動が無い。

今回は2012でやっているので、どちらにしてもNuBuild project systemを選択です。

nubuild

これはNuBuildプロジェクトのダイアログですが、「Add Binaries To SubFolder」だけTrueに変えておきます。今回のパッケージには、Profile1のPCLとWinRT向けのPCLの2つのバージョンを入れ込むため、サブフォルダに配置するように指示します。あとはデフォルトのままです。

nuspecファイルはビジュアルに編集するエディタは無く、単純にxmlとして編集します。もちろん、ひな形は提供されるので、埋めていく感じです。

<?xml version="1.0" encoding="utf-16"?>
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
	<metadata>
		<!-- Required metadata -->
		<id>CenterCLR.SgmlReader</id>
		<version>2014.12.7.3</version>
		<title>SgmlReader for Portable Class Library</title>
		<summary>SgmlReader for Portable Class Library. SgmlReader most popular usage the "HTML" parser. (It's scraper!!)</summary>
		<authors>Kouji Matsui</authors>
		<description>SgmlReader for Portable Class Library.
SgmlReader is "SGML" markup language parser, and derived from System.Xml.XmlReader in .NET CLR.
But, most popular usage the "HTML" parser. (It's scraper!!)

/* Use SgmlReader in Html parse mode. */
XDocument document = SgmlReader.Parse(stream);

Done!</description>
		<releaseNotes>2014.12.7.3:
 Add 1 line parse method.
2014.12.7.2:
 Direct handling the Stream class.
 Initial parameter is set of Html parse mode.
2014.12.7.1:
 Namespace changed "CenterCLR.Sgml".
 More easy usage, HTML parse is default mode.
 Native store app library included.
1.8.11.2014:
 Initial release.</releaseNotes>
		<projectUrl>https://github.com/kekyo/CenterCLR.SgmlReader.git</projectUrl>
		<iconUrl>https://raw.githubusercontent.com/kekyo/CenterCLR.SgmlReader/master/CenterCLR.SgmlReader.100.png</iconUrl>
		<requireLicenseAcceptance>false</requireLicenseAcceptance>
		<licenseUrl>http://opensource.org/licenses/Apache-2.0</licenseUrl>
		<copyright>Copyright (c) 2002, Microsoft Corporation; Copyright (c) 2007-2013, MindTouch; Copyright (c) 2014, Kouji Matsui</copyright>
		<tags>SgmlReader Parser Portable HtmlReader Html Scraping</tags>
		<!-- Optional metadata
		<owners></owners>
		<dependencies>
		</dependencies>
		<references></references>
		-->
	</metadata>
</package>

nubuild1

そして、NuBuildプロジェクトのReferencesにプロジェクト参照を追加します。あとはビルドすれば、Bin配下に*.nupkgが出来ているはずです。プロジェクト参照すると、きちんと依存関係も設定されます。

上記のnuspecにはfilesタグが含まれません。通常、nuspecファイルを手で作る場合は、パッケージに含める配布物をfilesタグに含めますが、そこはこのプロジェクト参照で自動的にNuBuildがやってくれます。しかも、いつもながら面倒なプラットフォーム指定文字列(「portable-net403+sl40+win+wp+Xbox40」のような)も、自動的に判定してくれます!これは本当に嬉しい。

(プラットフォーム指定文字列の一覧は、何故か物凄く探しにくいです。備忘録としてリンクを張っておきます

ビルドして生成されたnupkgをNuGetにアップロードします。アップロードの方法は、NuGetでユーザー登録するかMSアカウントでサインインしてアップロードするだけ。nuspecに書かれた定義を元に解析を行うので、特にこれと言って追加の作業はありません。

一点、アイコン参照(nuspec内のIconUrl)は、正しいURLを示していないとアイコンが表示されません。今回はGitHubで公開する事もあり、GitHub上にアイコンファイル(100x100px)を上げておいて、アイコンファイルへのRaw参照URLを指定するようにしました。


実際に使ってみる

wpad

これでNuGet出来るようになったので、使ってみましょう。パッケージIDは「CenterCLR.SgmlReader」です。

やっていることは、プロ生で登壇した時のスクレイピングネタと殆ど同じです。但し、プロ生Advent Calendar進捗ダメで書いた通り、そのままではスクレイピング出来ないので (;´Д`)、代わりにいつものネタに使う「郵便番号データ」のページを使っています。

/// <summary>
/// 指定されたURLのHTMLを読み取って解析します。
/// </summary>
/// <param name="url">URL</param>
/// <returns>コンテンツ群のURL</returns>
private static async Task<IReadOnlyList<KeyValuePair<Uri, string>>> LoadFromAsync(Uri url)
{
	using (var client = new HttpClient())
	{
		using (var stream = await client.GetStreamAsync(url).ConfigureAwait(false))
		{
			// SgmlReaderを使う
			var sgmlReader = new SgmlReader(stream);

			var document = XDocument.Load(sgmlReader);

			// 郵便番号データダウンロードのサイトをスクレイピングする
			// ターゲットは、html/body/div[id=wrap-inner]/div[id=main-box]/div[class=pad]/table/tbody/tr/td/a
			// にあるhrefとなる。
			// パースとトラバースまでワーカースレッドで実行しておく。
			return
				(from html in document.Elements("html")
					from body in html.Elements("body")
					from divWrapOuter in body.Elements("div")
					let wrapOuter = GetAttribute(divWrapOuter, "id")
					where wrapOuter == "wrap-outer"
					from divWrapInner in divWrapOuter.Elements("div")
					let wrapInner = GetAttribute(divWrapInner, "id")
					where wrapInner == "wrap-inner"
					from divMainBox in divWrapInner.Elements("div")
					let mainBox = GetAttribute(divMainBox, "id")
					where mainBox == "main-box"
					from divPad in divMainBox.Elements("div")
					let pad = GetAttribute(divPad, "class")
					where pad == "pad"
					from table in divPad.Elements("table")
					from tbody in table.Elements("tbody")
					from tr in tbody.Elements("tr")
					from td in tr.Elements("td")
					from a in td.Elements("a")
					let href = GetAttribute(a, "href")
					where href.EndsWith(".zip", StringComparison.OrdinalIgnoreCase) == true
					let zipUrl = ParseUrl(url, href)
					where zipUrl != null
					let text = a.Value
					where string.IsNullOrWhiteSpace(text) == false
					select new KeyValuePair<Uri, string>(zipUrl, text)).
				ToList();
		}
	}
}

wpadresult

ZIPファイルへのリンクをトラバースするだけで、中身は展開していません。スクレイピングが出来る事は確認できます。


気になっていた点の改良

身も蓋もない事を言います:

「どうせ、今更SGMLなんて使わないんですw」

と言う訳で、SGMLサポートを削除… まではしませんが、Htmlのパースをもっとサクッと出来るようにしておきました。

何しろ、これ以上簡単には出来ません。1行で行けます。

// ストリームを与えてHtmlをパースし、XDocumentを返す。
XDocument document = SgmlReader.Parse(stream);

では、みなさん、楽しんでください!


SgmlReader for Portable Class Library

GitHub:Repository: CenterCLR.SgmlReader
NuGet:Packaged ID:CenterCLR.SgmlReader

AdventCalendar.StoreAppDemo1(デモ用Windows Phoneプロジェクト)

GitHub:Repository: CenterCLR.AdventCalendar.StoreAppDemo1

つぎは、icchuさん、お願いします!