Microsoft MVP for .NETを受賞しました

msmvp320表題のとおり、2015.04期のMicrosoft Most Valuable Professionalを受賞しました。コミュニティでは様々な方に応援して頂いてありがとうございました!

MVPには受賞ジャンルがあり、私は「.NET」というカテゴリーで受賞しました(まだ、MVPの公式サイトからは検索できないようです)。


MVPのあれこれ

MVPは自薦で申し込みました。申し込み自体は去年の10月もしたのですが、落選 (;´Д`) ただ、引き続き活動の様子を見たいので、改めて申し込んで欲しいと書かれていたので、ヌケヌケと再応募して受賞、という流れです。

MVPの事を考え始めたのは去年の頭ぐらいからですが、周到に狙ったわけではありません。そもそも、MVPの受賞可否は非公開なので、どのような経緯で落選・受賞したのかは、最後まで分かりません。ただ、現在MVPとして活躍されている方のお話を聞く感じでは、ブログの情報発信よりもコミュニティ活動は重視される「らしい」と言う事を聞いていました。

10月と言えば、CenterCLRが発足ホカホカで、勉強会もまだ開いていなかったので、10月に応募しても落選するだろうなぁとは思っていました。

MVPへの自薦を考えている方は、年間4回募集があり、結果は次回の申し込み期間の直前に連絡がある事は覚えておきましょう。つまり、受賞するとしても結構長いスパンで時間がかかります。また、過去1年間のコミュニティ活動をまとめる必要があります(定型フォームがあるので、そこに記入します)。

MVPを受賞すると、様々な特典があるようです。何しろまだ受賞したてで詳細は良く判りませんが、分かり易い部分では、MSDN Ultimate Subscriptionのライセンスが付与されます(MVPの認定期間は1年間なので、ライセンスも1年間です)。他にもConnectの優先アクセスや、MVPメンバーだけのイベントへの参加権があるようです(NDA契約があるので、何かネタ情報を聞いてもバラせない、という制約はあります)。

個人的には、MSDNはCommunity Championshipでライセンスを貰っているし、会社でも使うので、それ以外の特典に期待しています。特に、MVP向けのイベントには都合が付けば参加したいです。


マイルストーンに「なった」

MVPの受賞は、それほど期待していたわけではありませんでした(期日が迫ってきたらそれなりに結果が気になりましたが)。実際、受賞したらしたで、嬉しいかな、ぐらいで考えていました。

しかし、実際に受賞してみて、突然、受賞したことがマイルストーンになった感が出てきました。

# それは「マイルストーン」じゃないというツッコミが飛んで来そうだw

コミュニティ活動を行うまでは、私の技術の視野はとても限定的でした。国内においても、技術にこれだけ熱意を持って取り組んでいる人が沢山いるという事実を殆ど知らず、自分の周りには皆無で諦めのような心境で、本を執筆したり大手サイトで記事を書くような人は、どこか遠い国の人達のように思えていました。

そういう自分だけの世界では、身に着けた技術を客観的に考える土壌は無く、果たして良いのか悪いのかもわからず、しかも業務でその技術を有効活用出来ていないと、自身に何の価値も見いだせないという、とっても辛い世界で悶々とするしかありませんでした。

今考えると、そういうフラストレーションは、MCPを次々と脈絡もなく取ってみるという行動に出ていたと思います(普通は、計画的にMCPを取得して上位MCP認定を狙うものです。お金もったいないww)。

で、色々あって、自分の会社を立ち上げてしばらくした頃、「どうもMSはWindows Azureに力を入れているらしい。自社でサービスを立ち上げるとしたら、こういう技術も吸収しておく必要があるだろう」、と考え、時間も割と自由に取れたので「セミナー」に参加してみるかと思いました。

一つ目のセミナーはマイクロソフト名古屋支部で行われましたが、Azureの概要に触れたぐらいで、もっと「濃い」話が聞けたら良いのに、というのが感想でした。それで、他にもセミナーが無いかと探したのです。中部圏でのセミナーは選択肢も限られてくるのですが、「Bar Windows Azure」とか言う「セミナー」があるらしい、と言うのを、どこからか見つけてきました。

adreana

ATND: Bar Windows Azure in Nagoya

今、上のATNDページを見てみても、ちゃんと「piano bar CLUB ADRIANA」と書いてあるわけですよ。まさか、本当に酒場的バーでこういうイベントをやるとは、鎖国人間には全く想像出来ていなかったのです。セミナーですよ、セミナー。だから、当日、店の前で「本当にここでやるのか?!」とウロウロした記憶がありますw

そして、その時のセッションの内容も衝撃的でした。「何でもかんでもブラウザベースはおかしいだろう」と思ってた口なので、「SignalRって何だよ?!」とか。更にさらっと高いレベルの解説が飛び交ってるこのバーの状況が理解できない… 結構ボー然としてました。

「こ、これは、この人たちは、一体何者なのか? ここは本当に自分の知っているITの世界なのか?!」 と。

# 今見たら全員MVPですね… 無知もイイ所です。感謝してます

コミュニティ活動なるものを知った後は、次々と参加するようになりました。が、早々に登壇する機会を頂いて(いきなり「その話、LTしませんか?」だったので、正直、かなりビビってましたが)、自分で技術を整理して発表する、という事を繰り返して、ようやく思い出しました。

IT技術とは、閉鎖的で鬱屈した、決まった作業を淡々とこなすような作業的なものではなく、クリエイティブな取り組みなんだと。ここに至るまでの20年近く、楽しくおもしろかったはずのコンピューター技術が、取るに足らない社会の量産的ネジとして刻み込まれた「何か」から脱しなければ、自分は本当にダメになってしまう、と。

それから今に至るのですが、MVP受賞の審査内容は分かりませんが、受賞したことでこういった経緯を思い出して、改めてこれは「マイルストーンだったんだ」なぁと感慨深く感じたのです。


これからどうするか

変わらず、です。MVPの受賞は結果的に自分にとってのマイルストーンだったのですが、どちらにしてもこれまでのペースを基本に、コミュニティ活動を長く続けていきたいと思います。

MVPの受賞カテゴリは.NETなのですが、引き続き注力したい分野です。.NET Coreがオープンソースとなり、大きな変化がありました。今後どうなっていくのか、とても楽しみです。

scrumalliance2また、個人的にはもう一つの大きな柱として、認定スクラムマスターを取得した事があります。スクラムは実践する事が重要なのですが、この点では全くと言って良いほど実践できていないのが辛い所です。スクラムの実践・アジャイルな活動は、人間的な仕事の在り方、人間的な成長に繋がると思っているので、何とかそこにリーチして実践したい・共有したいなぁと思っています。

これからも応援、よろしくお願いします!

第一回 三重合同懇親会 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への関わりとか、想像もしなかった盛りだくさんで面白かったです。次回も参加しますよ!

それでは、また。

さようなら、Windows 5。

とうとう近づいてきた

win20032
最近、良く目につくようになった、「Windows Server 2003移行」の文字。GoogleやBingで検索すると、技術情報よりも延命措置の有償サービスが上位に表示されます。私の業務環境では既にレガシーWindowsを扱っていない事もあり、現実的な問題がどんなものかは想像するしかない状態です。

公式がどこかいまいち良く判らないので、リンクを張っておきます:Windowsライフサイクルのファクトシート

Windows XPが2014年4月にサポート切れとなり、もうすぐWindows Server 2003のサポートも切れます(2015年7月15日)。つまり、Windowsとしてサポートが存在するバージョンはVista以降、という事になります。

Windows XPはともかくとして、Windows Server 2003は個人的にとても良い印象のWindowsでした。Windows XPと大体において同じカーネルを持ち、Windows XPのように色々ゴテゴテと付いていないOSと見た時、2003の扱いやすさが光るのです。

# ええと、Vistaや7が嫌いと言う訳ではなく、むしろ「見た目」で入る製品も好きなのですが、手の内でほぼコントロール出来る感も、それはそれで良いと思ってます。

また、Windows 5系列は、初めてx64のサポートが追加されたバージョンでもあります。初物だけあって、Windows XP x64 Editionは色々な制約があって、ユーザーサイドで必ずしも良いOSとは言えませんでしたが、Iteniumと違ってx86にやさしい(互換性が高い)事が良く、後続のWindows x64の基礎を作りました。また、同じx64カーネルを持つWindows Server 2003 x64はサーバー向けという事もあって、より良く改良されていました。

# Windows Server 2003 x64はWindows XP x64よりも、全ての面で安定性が高かった。
# デバイスドライバについて言えば、サードパーティはWindows XP x64を半ば無視ししていたのに対し、2003 x64での開発に注力していたことも大きいです。


カーネルモードの5

win20031Windows XPやWindows Server 2003は、Win32プログラミングを行う方なら知っているかもしれませんが、GetVersionEx APIで得られる「メジャーバージョン番号」番号は「5」です。この記事のタイトルはそこから来ている訳ですが、このバージョン5はWindows 2000からWindows Server 2003 R2までの系譜です。分かりつらいですね。Windows 5の一覧を載せましょう:

 

  • Version 5.0 – Windows 2000 x86 (Windows 2000 Professional/Server/Advanced server)
  • Version 5.1 – Windows XP x86 (Professional/Home/Starter)
  • Version 5.2 – Windows Server 2003 x86/x64/Itenium (無印/R2) / Windows XP Professional x64/Itenium

Windows XP x64は実は5.2なので、2003と同じ扱いである事が分かります。既にどうでもいいトリビアですが、Windows 2000、つまり5.0は、唯一のNEC PC-9800シリーズ向けの実装が存在しました。

# そういえば、NTの頃はMIPSやらAlphaやらもサポートされました(一時はSPARCの噂までありました)。

以前私はWDKを使って、Windows向けのストレージ系ツールを設計・開発していました。このブログのWDKのカテゴリでいくつか記事を書いているのは、その時得られた知見に基づいたものです。このツールは、Windowsのカーネルモードドライバ(フィルタードライバ)を含んでいて、ディスクI/Oを監視して色々操作するものでした。

テストの為に、ユーザーモードのアプリケーションで非常に高負荷なI/Oを発生させ、ntfs.sys・フィルタードライバ・物理ドライバ間のパフォーマンス測定を行っていました。その時に、Windows 5系列の各スタックは、全体的にパフォーマンスが高いなと思ったのです。

Windows 6(つまりVista以降)と5の間には、「乗り越える事が出来ない」程のパフォーマンス差がありました。残念ながら開発はWindows 7・2008R2までであったため、最新のWindows 8でどの程度改善されたのかは分かりませんが、それでもWindows 5系列の方が良いと思われます。その位の差がありました。

# VistaではSSE2への最適化も謳われていましたが、「そんなものは無かった」程の違いです。結局「Vista重い」というのは、エンドユーザーの正直な感想で大体合っていたなーと思います。

勿論、この例はシンセティックなベンチマークのようなものです。現実的なパフォーマンスの測定には、他のあらゆる要素が関係してきます。また、システムの利便性は、ベンチマーク結果だけで測る事は出来ません。ですが、ベンチマーク速度の話が出て来る度に、「あぁ、5のスタックだけ使えたらなぁ」という、不毛な妄想をしたものです。

実行環境について:Windows XP以上は、今の所HYPER-Vのゲスト拡張機能は問題なく動作するようです。Windows 2000については、リリースビルドは問題ありませんが、デバッグビルド(デバイスドライバデバッグ用の、アサーションが大量に挿入されたWindows)は動作しませんでした。


終焉

Windows XPの当初の印象は良くない物でした。また、Windows XPの終盤では、IE6の不具合と思しき症状も放置されたためにWindows Updateが出来ないという、最悪の状態でしたが、最後には修正されてほっとしました。

さて、現在、主に.NETの世界の住人として気になる事は、CLRとのバージョンの関係でしょう。Windows 5の系列は、大ざっぱにはCLR 2.0(.NET 2.0~3.5)のバージョンの扱いと重なります。つまりは、Windows 5が終焉するのに合わせて、CLR 2.0も終焉と考えても良いかなと言う点です。

Microsoft .NET Frameworkサポートライフサイクルポリシーを見ると、何だかややこしい事になっていますが、開発者の目線で見ると、もはやNuGetのサポートは避けられず、Visual StudioがWPFベースで実装されている事もあり、Visual Studio 2012での開発が最低レベル(少なくともCLR 4.0)という状況です。

NuGetは2010向けもありますが、パッケージの方が2010で検証していないものもあり、結局NuGetを使うなら、ほぼ最新のVisual Studioを使わなければならないため、古いバージョンでの開発は互換性問題を解決するよりも、問題を引き起こす方が大きいという、想像もしなかった状況になってきています。

また、NuGetのパッケージを使い、そのパッケージに脆弱性が見つかった場合、殆どのプロジェクトは最新版に対して修正を行うと思われます(そもそもNuGetにはバージョン分岐の概念が無い)。すると、脆弱性対策を行うには、パッケージを最新に更新すれば良い(=最新に更新しなければならない)、という事を意味します。

# 既にASP.NETのプロジェクトは、思いっきりNuGetに依存しているので、これを回避するのはかなり辛いです。

つまり、今後の開発は、同じプラットフォームバージョンが維持されることを前提に開発するのはほとんど不可能で、常に最新に追従し続けなければならない、事を意味します。これは既にスマートフォンベースの開発(iOS及びAndroid)では当たり前になってきており、好むと好まざるとにかかわらず、Windowsもその位の、素早く、変化に追従できる開発が求められる、という事です。

マイクロソフト周辺での事情の変化も大きい、IT全体での変化もとても大きなものでした。

そんな今日の状況を感じつつ、Windows 5を見送りたいと思います。

RIP Windows 5 versions.

win20033


※なお、文中で「GetVersionEx API」の話を持ち出しましたが、このAPIは非推奨です。「Version Helper functions」を使って下さい。

Final LINQ Extensions – 第二回 Center CLR 勉強会

第二回 Center CLR 勉強会にて、「Final LINQ Extensions」というタイトルで、登壇してきました。

Center CLR二回目で、一回目から参加された方も、今回初めて参加された方も、ありがとうございました。

FinalLINQExtensions
LINQ集大成ってことで、LINQ to Object限定ですが、入門的内容から発展ネタまで展開しようと思いました。が、最初にアジェンダを書いて、それを何とか詰め込もうとしたのですが、どうしても時間が足りない! orz という事で、次回に続編やります。

  • ループと分岐処理からの脱却
  • 構造的な値への適用
  • 初歩的な演算子
  • 実践的なforeachの置き換え
  • 列挙子とは
  • 演算子を作る
  • パフォーマンスの改善

次回もよろしくお願いします!

オリジナルスライドFinal LINQ Extensions.pptx

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

Madoben12

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

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

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

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

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

それではまた。

データバインディングと非同期 – C# Advent Calendar 2014

この記事は、「C# Advent Calendar 2014」の21日目の記事です。

WPFやWindows Phoneやストアアプリでは、Windows Formsのような「コードビハインド」による実装の他に、「データバインディング」を積極的に使う実装を行う事が出来ます。というよりも、データバインディングという手法を使わないと、複雑なUIを表現するのが困難です。この記事では、その辺りと非同期がどう関係するのかを書いてみたいと思います。

(なお、例はWPFですが、ストアアプリでもほぼ同様です)


データバインディングって何?

そもそもデータバインディングとは、どういう手法でしょうか?

asyncbinding1

Windows Formsの時代、データバインディングと言うと「DataSet」クラスの内容を、「DataGridView」コントロールを使って表示させるという用途が一般的でした。

詳しい解説は「Windowsフォームにおける「データ・バインディング」」を参考にして下さい。

この手法の問題点として:

  • DataSetやその周りのクラス(DataTableなど)と、List等の限定されたクラスがターゲット。
    実際には、IListインターフェイスやIBindingSourceインターフェイスを実装したクラスであれば受け入れる事が出来ますが、殆どの場合はこれらのインターフェイスを独自実装しないで、単にDataSetクラスを使用していたと思います。
  • DataGridViewのセル内に、更に別の(コレクション)要素を配置するようなリッチな表現を、統一された方法で実現するのが困難。
    「DataGridViewのセル内に「コンボボックス」を配置するのはどうしたら良いのか?」と言うのは、定期的に発生するFAQであったと思いますが、このような拡張がスムーズに行えないのは、グリッドの表現をDataGridViewが独自に制御していたため、応用性が無く、学習効果が得られないからと思われます。
  • バインディング対象のインスタンスがコンポーネントとしてフォームに設定される必要があり、柔軟性に欠ける。
    DataGridView以外のコントロールでも、バインディング機能を使う事は出来ました。但し、バインディングしたいインスタンス(モデルクラスやコレクションなど)は、コンポーネントとしてフォームに配置される必要があったため、フォームとデータが密結合する事になり、データバインディングする事の意義が見出しにくい状態でした。また、コントロールへの直接アクセスの手法があまりに一般に広まったため、そもそもこの手法が利用可能である事も周知されませんでした。

「DataSetを用意して、単純にDataGridViewに表形式で表示」させるのであれば十分なのですが、それ以上のUI表現に踏み込むと途端に難易度が高くなり、インターフェイスが複雑化してしまいます。

また、DataSet自体がデータベースのテーブルを模倣したものであったため、階層化されたデータを容易に扱えないという問題もあります。Visual Studioのデザイナー機能によって「型付きDataSet」を生成出来るようになりましたが、結局(生の)DataSetをラップしたものに過ぎず、本来コード上でモデル型として表現されている構造から乖離してしまう事は避けられませんでした。

asyncbinding2

.NET 3.0でWPFが発表され、ここの部分に大きなメスが入りました。DataGridViewのような、特殊なクラスの特殊な機能によってバインディングを実現するのではなく、そもそもWPFの枠組みでこれを実現し、どのようなコントロールでも統一されたバインディングを使えるようにし、UIとデータを完全に分離可能にした… それが今のデータバインディングです。

図のように、「DataGridコントロール(WPF)」「TextBoxコントロール(WPF)」は、両方ともモデルクラスとのデータの送受を「バインディング式(Binding expression)」で実現しています。この手法には全く違いは無く、更にDataGridコントロールの場合は、階層化されたモデルクラスのインスタンス群をどのようにDataGridで表現するのかまでを、全てバインディング式で記述する事が出来ます。

DataGridコントロールだけではなく、リスト形式で要素を表示する「ListBoxコントロール」や、コントロール要素の配置を制御する「Panelクラス」を継承したコントロール群「DockPanelStackPanelGrid等」も、ほぼ同様の手法でデータとの関連付けが可能です。

そして、これらのコントロールを階層化して組み合わせた複雑なUIは、データバインディングの規則に従っている限り、技術的な制限なく実装する事が出来ます。

WPFでも、従来のWindows Formsのようにコントロールに直接アクセスして、ほぼ全てを制御する事も出来ますが、データバインディングを使えるようになると、かなり面倒に感じられるはずです。また、ちょっとUIが複雑化するだけで実現難易度が高くなるため、単調なUIで実現する事しか選択の余地が無くなってしまいます。

(Windows Formsが楽と言う意味ではなく、むしろ複雑なUIを実現する場合、Windows Formsでは更に難易度が高い)


データバインディングのキモ

データバインディングのキモは、コントロールの公開するプロパティと、モデルクラスの公開するプロパティの値を自動転送するところです。以下にTextBoxの例を示します。

これはXAMLの定義です:

<Window x:Class="CenterCLR.AsyncBindings.MainWindow"
		xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
		xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
		Title="MainWindow"
		Width="525"
		Height="100">
	<TextBox x:Name="sampleTextBox" />
</Window>

対応するコードビハインド:

public partial class MainWindow : Window
{
	public MainWindow()
	{
		InitializeComponent();

		// TextBoxに初期文字列を設定
		sampleTextBox.Text = "ABC";
	}
}

asyncbinding3

TextBoxのTextプロパティは、テキストボックスに入力された文字列をやり取りするプロパティです。あらかじめこのプロパティに値を設定すれば、テキストボックスの初期表示文字列となり、ユーザーが文字列を入力したり編集したりすれば、それがこのプロパティから読み取れます。ここまではWindows Formsと同様です。

ここで、Textプロパティにバインディング式を記述します。バインディングするためには、相方のプロパティが必要です。とりあえず、MainWindowクラスに直接実装しましょう。

public partial class MainWindow : Window
{
	// バインディングされる値を保持するフィールド
	private string sampleText_ = "Bound!";

	public MainWindow()
	{
		InitializeComponent();

		// バインディング対象のインスタンスを、このクラス自身に設定
		this.DataContext = this;
	}

	// バインディングされるプロパティ
	public string SampleText
	{
		get
		{
			return sampleText_;
		}
		set
		{
			sampleText_ = value;
		}
	}
}

XAMLにはこのようにバインディング式を書きます:

<Window x:Class="CenterCLR.AsyncBindings.MainWindow"
		xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
		xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
		Title="MainWindow"
		Width="525"
		Height="100">
	<!-- TextプロパティとSampleTextプロパティをバインディングする式 -->
	<TextBox Text="{Binding SampleText}" />
</Window>

XAMLのコードについて、重要な部分が二カ所あります。一つはバインディング式を記述した事です。TextBoxにはTextプロパティがあるのですが、そのプロパティに対して「{Binding SampleText}」と記述します。この中括弧+Bindingで始まる指定がバインディング式です。「SampleText」と言うのは、バインディングさせたい対象のプロパティ名です。ここではMainWindowクラスに定義した「SampleText」プロパティを指定しています。

もう一つの重要な点は、もはや「x:Name」による、コントロールへの命名が不要になっている点です。この点についてはまた後で説明します。

MainWindowクラスへの変更も説明をしておきます。MainWindowクラスにも重要な二カ所の変更点があります。一つはバインディング対象のプロパティ「SampleText」を追加した事です。これに合わせて、値を保存しておくためのフィールドも追加してあります(熱心なC#信者なら、自動実装プロパティを使う事が出来ると思うかもしれません。デバッガで動きを追えるようにするためにわざと手動で実装しています)。

もう一つはコンストラクタに追加した、「DataContext」プロパティにthisを代入するという式です。このプロパティは、XAML上でバインディング式を実行する際に、デフォルトのバインディング対象のインスタンスを特定するために使われます。ここでthisを代入しているという事は、デフォルトのバインディング対象はMainWindowクラスのインスタンスとなる、と言う事です。そして、XAML上に記述したバインディング式が「{Binding SampleText}」となっているので、結局「MainWidnowクラスのインスタンス」の「SampleTextプロパティ」が、TextBoxのTextプロパティにバインディングされる、と言う事になります。


asyncbinding4

さて、このコードを実行すると、望みの通り、バインディングが実現し、初期状態としてフィールドの値が表示されます。しかし、テキストボックスの文字列をタイプして変更しても、SampleTextプロパティのsetterに遷移しません(ブレークポイントを設定して確かめて下さい)。

これは、バインディング粒度のデフォルトが、コントロールからフォーカスが外れた時、となっているためで、サンプルではTextBoxが一つしかないためにそのタイミングが無い事によります。ここでは単純に、「一文字でも変化があれば」、と変えておきます。この指定もバインディング式だけで実現出来ます。

<Window x:Class="CenterCLR.AsyncBindings.MainWindow"
		xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
		xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
		Title="MainWindow"
		Width="525"
		Height="100">
	<TextBox Text="{Binding SampleText, UpdateSourceTrigger=PropertyChanged}" />
</Window>

asyncbinding5

XAMLのバインディング式に「UpdateSourceTrigger=PropertyChanged」と付け加えます。すると、バインディング処理の実行が、Textプロパティに変化があった時という条件となります。これで、setterにブレークポイントを張っておいて、テキストボックスで「A」をタイプするとこのようになり、バインディング式によって自動的に値が転送されて来る事が分かります。

asyncbinding6

上記のデータバインディング例を図示すると、このようになります。従来のWindows Formsのように、イベントハンドラのフックやTextBoxのTextプロパティへの直接アクセスをする事無く、値の受け渡しが出来ている事になります。おっと、忘れる所でした。これで「x:Name」によるコントロールの命名が不要になっている理由も分かりますね。プロパティ同士がバインディング式で直接転送されるので、「どのコントロールに対する操作なのか」の明示は不要となっているのです。

(ここで解説しませんが、能動的にSampleTextの値が変化する場合は、MainWindowクラスにINotifyPropertyChangedインターフェイスを実装して、プロパティの値が変化したことをWPFに通知する必要があります)

ここまで、Windows Formsっぽい手法からスタートして説明するため、バインディング対象のインスタンスをDataContextプロパティにthisを代入する事で「MainWindow」そのものにしましたが、更にコードの責務を分離するために、バインディングするクラスを独自のクラスに完全に分離することも出来ます。例えば「MainWindowViewModel」クラスを作り、DataContextにこのクラスのインスタンスを代入する事で、バインディング対象をMainWindowから切り離す事が出来ます。

// MainWindowクラスから完全に独立したクラス(ビューモデル)
public sealed class MainWindowViewModel
{
	// バインディングされる値を保持するフィールド
	private string sampleText_ = "Bound!";

	// バインディング対象のプロパティ
	public string SampleText
	{
		get
		{
			return sampleText_;
		}
		set
		{
			sampleText_ = value;
		}
	}
}

// コードビハインドが殆ど存在しないMainWindowクラス(ビュー)
public partial class MainWindow : Window
{
	public MainWindow()
	{
		InitializeComponent();

		// バインディング対象のインスタンスを、MainWindowViewModelに設定
		this.DataContext = new MainWindowViewModel();
	}
}

これが「MVVM(Model-View-ViewModel)」パターンの入り口です。


データバインディングと非同期

長い前フリでしたが (;´Д`) 本題に入ります。ストアアプリに導入された新しいWinRT APIのセットは、原則として外部リソースアクセスは全て非同期で実現しなければならないようになっています。ブログの記事や勉強会でも説明した通り、これらはC#で言う所の「async/await」を使って実現します(より深くはTaskクラスやIAsyncInfoインターフェイスを使う事ですが、本題と関係ないので省略)。

「非同期処理」と「ユーザーインターフェイス」とはどう絡むのかが本題です。この問題は古典的でもあって、一般的なウェブブラウザのフォームでも同じような問題を抱えていました。例えば、「購入」ボタンを何度もクリックされると困るので、クリック後はボタンを無効化して押せなくする等の処置は、(ブラウザとは無関係に)ウェブサーバー側で購入処理が非同期で実行されるから発生する問題でした。

あるいはもっと良い方法もあります。何度「購入」ボタンが押されても問題ないように、同じセッションが維持されていると分かる場合は、単一の要求と見なすなどです。

どちらの手法を取るにしても、「非同期処理は何も対策しないで実現することは無理」と言う事です。async/awaitのサポートによって、非同期処理の実装難易度は格段に改善されたのですが、この部分をどうするのかは、仕様や実装を行う者に任されています。

二つのパターンの背景を説明しましたが、非同期処理をユーザーインターフェイスを工夫する事によって担保する方法について、データバインディングと絡めて考えてみます。


非同期処理中にユーザーインターフェイスをロックダウンする、と言うのは、もっとも単純で効果的な方法です。真っ先に思いつくのは、Windowsクラス全体のIsEnabledをfalseに設定する事でしょう。これをいつ行えばよいでしょうか?というよりも、非同期処理をどこで開始するのか?と言う事です。

例えば、UIにボタンが配置されており、ボタンのクリックと同時に非同期処理が開始されるとします。データバインディングを使うのであれば、「ICommandインターフェイス」を実装したクラスを使って、ボタンクリックのイベントを「バインディング」するでしょう。移譲されたイベントハンドラは、以下のような実装となります。

// (これは擬似コードのため動きません。INotifyPropertyChangedの実装やCommandクラスの実装が必要です)
public sealed class MainWindowViewModel
{
	public MainWindowViewModel()
	{
		// ICommandインターフェイスを実装したクラスを使って、バインディング可能なイベントハンドラを公開する
		this.FireStart = new Command(this.OnFireStart);
	}

	// バインディング可能なイベントハンドラ(ボタンのCommandプロパティにバインディングする)
	public ICommand FireStart
	{
		get;
		private set;
	}

	// ウインドウ全体の有効・無効を制御する(MainWindowのIsEnabledプロパティにバインディングする)
	public bool IsEnabled
	{
		get;
		private set;
	}

	// Commandクラスによって、転送されるイベントの実処理
	private async void OnFireStart(object parameter)
	{
		// ユーザーインターフェイスを無効化する
		this.IsEnabled = false;
		try
		{
			// 非同期処理...
			using (var stream = await httpClient.GetStreamAsync("..."))
			{ ... }
		}
		finally 
		{
			// ユーザーインターフェイスを有効化する
			this.IsEnabled = true;
		}
	}
}

この例では、ボタンのクリックと言う「アクション」を引き金に動作するので、比較的分かりやすいと言えます。非同期プログラミングのベストプラクティスによれば、シグネチャとして「async void」と定義するのはイベントハンドラの場合だけだと述べられています。この例は本物(CLR)のイベントハンドラではありませんが、ICommandによって間接的に呼び出されるコールバックとして、広義において同じと見なしても良いと思います。

asyncbinding7

では、値を転送するデータバインディングそのものが引き金になる場合はどうでしょうか?前フリのような、テキストボックスに対する編集操作で、非同期的に処理を実行し、結果を反映する場合です。例えば、Visual Studioのインテリセンスがシンボル名の絞り込みを行う過程や、ウェブで言うなら「googleサジェスト」のような機能です。

この場合、バインディングされたプロパティに、一文字変化がある度に値が転送されてきます。転送はsetterのコールによって実行されるため、非同期処理はsetterから始める必要があります。

<Window x:Class="CenterCLR.AsyncBindings.MainWindow"
		xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
		xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
		Title="MainWindow"
		Width="525"
		Height="200"
		IsEnabled="{Binding IsEnabled}">
	<DockPanel>
		<!-- Textプロパティ同士をバインディングし、テキストボックスへの文字の入力を検知可能にする -->
		<TextBox DockPanel.Dock="Top" Text="{Binding Text, UpdateSourceTrigger=PropertyChanged}" />

		<!-- 結果を表示するリストボックス -->
		<ListBox ItemsSource="{Binding ResultItems}">
			<ListBox.ItemTemplate>
				<DataTemplate>
					<!-- 結果を表示するテンプレート -->
				</DataTemplate>
			</ListBox.ItemTemplate>
		</ListBox>
	</DockPanel>
</Window>
// (これも同様に擬似コードです)
public sealed class MainWindowViewModel
{
	private string text_;

	public MainWindowViewModel()
	{
		this.ResultItems = new ObservableCollection<ResultModel>();
	}

	// TextBox.Textにバインディング(UpdateSourceTrigger=PropertyChangedとする)
	public string Text
	{
		get
		{
			return text_;
		}
		set
		{
			// ユーザーインターフェイスを無効化する
			this.IsEnabled = false;
			try
			{
				// 非同期処理...
				using (var stream = await httpClient.GetStreamAsync("...?q=" + text_))
				{ ... }
			}
			finally 
			{
				// ユーザーインターフェイスを有効化する
				this.IsEnabled = true;
			}
		}
	}

	public bool IsEnabled
	{
		get;
		private set;
	}

	public ObservableCollection<ResultModel> ResultItems
	{
		get;
		private set;
	}
}

このコードには仕様的にも実装的にも問題があります。仕様面では、一文字入力する度にWindowが無効化される(しかも非同期処理が完了するまで解除されない)ため、恐らくユーザー体験は最悪の物となる事です。実装的な問題としては、setter内ではawait出来ない事です。これはsetterにはasyncを指定出来ない事から来ています。

まず、実装的な問題は、実処理をメソッド抽出すれば解決します。

public sealed class MainWindowViewModel
{
	private string text_;

	public MainWindowViewModel()
	{
	}

	// TextBox.Textにバインディング(UpdateSourceTrigger=PropertyChangedとする)
	public string Text
	{
		get
		{
			return text_;
		}
		set
		{
			// 非同期メソッドを呼び出すが、待機は出来ない。
			// →Taskを返し、メソッド名に「Async」と付ける事で、setter呼び出し後は、非同期処理が継続する事がはっきりする
			var task = this.ProceedTextAsync(value);
		}
	}

	// 一般的な非同期処理のシグネチャを順守
	private async Task ProceedTextAsync(string value)
	{
		// ユーザーインターフェイスを無効化する
		this.IsEnabled = false;
		try
		{
			// 非同期処理...
			using (var stream = await httpClient.GetStreamAsync("...?q=" + value))
			{ ... }
		}
		finally
		{
			// ユーザーインターフェイスを有効化する
			this.IsEnabled = true;
		}
	}

	public bool IsEnabled
	{
		get;
		private set;
	}
}

実処理を「ProceedText」としてasync voidとする事も出来ますが、ここではより汎用的になるように(そしてベストプラクティスに従うように)、Taskクラスを返却するようにしました。setterで戻り値のTaskを無視すると、C#コンパイラが警告を発します。そのため、ダミーの変数で受けるように書いています。わざわざTaskクラスを返す事で、別の仕様要件でProceedTextAsyncを呼び出さなければならない場合にも、非同期処理の待機が可能になる余地を残す事が出来ます。

残る仕様的な問題ですが、ウインドウ全体を無効化する必要が無いのであれば、特定のコントロールに絞って無効化を実施するという方法が考えられます。例えば、検索結果に応じてリストボックスの内容を変化させる場合、テキストボックスは有効としたまま、リストボックスだけを無効化するようにすれば良いのです。その場合、ロジックの変更は必要ありません。XAMLのバインディング式をListBoxに適用します。

<Window x:Class="CenterCLR.AsyncBindings.MainWindow"
		xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
		xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
		Title="MainWindow"
		Width="525"
		Height="200">
	<DockPanel>
		<TextBox DockPanel.Dock="Top" Text="{Binding Text, UpdateSourceTrigger=PropertyChanged}" />
		
		<!-- IsEnabledをListBoxにバインディング -->
		<ListBox ItemsSource="{Binding ResultItems}" IsEnabled="{Binding IsEnabled}">
			<ListBox.ItemTemplate>
				<DataTemplate>
					<!-- 結果を表示するテンプレート -->
				</DataTemplate>
			</ListBox.ItemTemplate>
		</ListBox>
	</DockPanel>
</Window>

非同期処理のエラーの対処

ここまでで、データバインディングで値が転送された場合の非同期処理は対処可能になりました。では、非同期処理中に発生するエラーはどのように対処すれば良いでしょうか?

そもそも、仕様的な面で非同期処理中のエラーにどう対処するかを考える必要があります。最後の例では、ユーザーがテキストボックスに何かタイプすると、非同期で検索処理を行い、結果がリストボックスに(非同期的に)表示されます。一文字タイプし、その事が原因で何らかのエラーが発生したとしても、ユーザーはもう次の文字をタイプしているかも知れませんし、ウインドウ上の別の機能を実行しているかも知れませんし、ウインドウを閉じているかも知れません。

  • エラーが発生したら、その場でメッセージボックスを表示する。
    単純なユーザーインターフェイスであれば、これでも良いと思われます。但し、複数の非同期処理を実行している可能性があり、それらが一斉に失敗すると、メッセージボックスが大量にスクリーンに表示される可能性があります。また、忘れた頃にいきなり表示される可能性もあります。
  • ログ出力のような表示領域に表示させる。
    過去に発生した内容を表示できるので、より望ましいと言えます。但し、どのメッセージが何の操作に紐づいていたのかは分かりにくくなるかも知れません。

どのような方法でも、握りつぶすのでなければ、対処方法は同じでしょう。非同期処理の実質的なコードが例外をスローする場合は、単にtry-catchで例外を補足し、エラー処理を行います。

private async Task ProceedTextAsync(string value)
{
	// ユーザーインターフェイスを無効化する
	this.IsEnabled = false;
	try
	{
		try
		{
			// 非同期処理...
			using (var stream = await httpClient.GetStreamAsync("...?q=" + value))
			{ ... }
		}
		catch (Exception ex)
		{
			// メッセージボックスを表示する
			MessageBox.Show(ex.Message, "Error occurred!", MessageBoxButton.OK, MessageBoxImage.Error);
		}
	}
	finally
	{
		// ユーザーインターフェイスを有効化する
		this.IsEnabled = true;
	}
}

注意点としては、C# 5.0ではcatch-finallyブロック内でawait出来ない事です。WPFの場合はMessageBoxの内部でメッセージポンプが実行される(Win32メッセージループが実行される)ので良いのですが、ストアアプリではawaitで待機する必要があります。そのため、多少泥臭いコードを書く必要があります(エラーを保存して一旦catchブロックから抜けてから表示するなど)。

エラー処理のコード例をお見せしましたが、結局のところ、一般的なコードと何も変わらないことが分かると思います。


ビジネスロジックの分離

仕様がより高度になるにつれ、データバインディングを実装しているクラスを分割したくなります。特にユーザーインターフェイスに対応するための固有のコードと、いわゆるビジネスロジックの分離です。リファクタリングの作法に従って、責務によってクラスを分割して行くと、MVVMで言う所のビューモデルとモデルに分割されます(うまくやればね :)。この時、前述の例で示したHttpClientによる検索処理などのビジネスロジックは、ビューモデル(元のクラス)からモデルに移動する事になります。

実装を移動する際、HttpClient.GetStreamAsyncは非同期メソッドなので、await可能でなければなりません。と言う事は、移動先のメソッドにはasyncが適用されなければならず、ベストプラクティスに従うならTaskクラスを返却するメソッドでなければなりません。結局のところ、「ProceedTextAsync」メソッドはほぼそのままの形でモデルクラスに移動する事になります。

ProceedTextAsyncがビューモデルに依存している箇所は、それぞれを以下のように対処します。

  • 結果をどう反映するか : モデルはthis.ResultItemsにはアクセス出来ない。したがって、ProceedTextAsyncは結果をまとめて(コレクションに入れるなどして)返却する。
  • IsEnabledへのアクセス : モデルは直接UIとの関係を持たないと考えると、IsEnabledの操作はビューモデルに残しておく。
  • メッセージボックスへのアクセス : モデルは直接UIとの関係を持たないと考えると、メッセージボックスの操作(例外の処理)はビューモデルに残しておく。

以下の例では、モデルクラス自身をコレクション(List)とし、UpdateAsyncメソッドでここに結果を保持するようにします。モデルの存在意義を考えてこのようにしましたが、実際の所はメソッドを単なる移譲として分離(戻り値にコレクションを返すスタティックなメソッド)した方が良かったかも知れません。

// ビジネスロジックを含むモデル(コレクション)
public sealed class BusinessLogicModel : List<ResultItem>
{
	// コレクションを更新する
	public async Task UpdateAsync(string value)
	{
		// 非同期処理...
		using (var stream = await httpClient.GetStreamAsync("...?q=" + value).
			ConfigureAwait(false))  // 以降の処理をワーカースレッドで実行
		{
			var document = SgmlReader.Parse(stream);

			// ここではUIを直接操作出来ない(ワーカースレッドなので)
			this.Clear();
			this.AddRange(
				from html in document.Elements("html")
				from body in html.Elements("body")
				// ...
				select new ResultItem { ... });
		}
	}
}

このように分離すれば、あとはビューモデルから呼び出すだけです。

private async Task ProceedTextAsync(string value)
{
	// ユーザーインターフェイスを無効化する
	this.IsEnabled = false;
	try
	{
		try
		{
			// モデルを用意して結果をフィルする
			var model = new BusinessLogicModel();
			await model.UpdateAsync(value);

			// バインディングする
			// (awaitでメインスレッドにマーシャリングされているので、UIを操作しても問題ない)
			this.ResultItems = model;
		}
		catch (Exception ex)
		{
			MessageBox.Show(ex.Message, "Error occurred!",
				MessageBoxButton.OK, MessageBoxImage.Error);
		}
	}
	finally
	{
		// ユーザーインターフェイスを有効化する
		this.IsEnabled = true;
	}
}

更なる発展

コレクションは段階的に反映したいですね。結果が認識されるたびに、1件ずつリストボックスに反映されたりすると、ユーザーインターフェイス的にとても良いと思います。

このような実装にするには、結果が1件取得される度に、バインディングしているコレクションに反映して、それがUIにも反映される必要があります。コレクションへの反映はINotifyCollectionChangedインターフェイスで通知することが出来ます。このインターフェイスはObservableCollectionクラスが実装しているので、Listの代わりにこのクラスを使うと良いでしょう。

注意点として、コレクションへの追加処理は、UIスレッド(WPFでは恐らくメインスレッド・ストアアプリではビュー毎に割り当てられているスレッド)で実行する必要があるという事です。ここにトレードオフがあります。

CPU依存性処理をワーカースレッドで効率よく処理させるために、「ConfigureAwait」メソッドを使用したくなるかもしれません。これにより、awaitの度にUIスレッドにマーシャリングされなくなるため、パフォーマンスが向上します。しかし、当然の事ながらUIスレッドにマーシャリング出来ないので、(メソッドを抜けるまでは)UIに絡む処理は実行出来ません。

ObservableCollectionを使用した場合、要素の変更時に「CollectionChanged」イベントを発火します。このイベントの発火はUIスレッドで実行しなければならないため、ObservableCollectionの操作は暗黙にUIスレッドで実行する必要があります。

前述のコード例でConfigureAwaitを使用しているのは、結果を一旦Listに保存しているので、直接的にも間接的にもUIに依存していない事が分かっているためです。このように、呼び出し元のスレッドがどこまで有効に作用するのかを考えて、ロジックを分離すると良いでしょう。

この辺りを掘り下げた資料は、以前の勉強会で発表した資料があるので、参考にどうぞ。


今年もあと少し、今日は寒くて堪りませんでした。明日はmoririringさんです。よろしく!

列挙可能から完全なるモノまで – 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さん、お願いします!

抽象太郎ものがたり そして伝説へ – NGK2014B 名古屋合同懇親会

今年も残りわずか、NGK2014B 名古屋合同懇親会にて、「抽象太郎ものがたり そして伝説へ」というタイトルでLT登壇してきました。

WP_20141206_14_24_43_Pro

会場の様子: 今年は名工大が会場でした。会場はほぼ満席、いつもながら(去年しか知らないけど)凄いです。

NGK2014Bは大LT大会で、LT持ち時間は5分、5分を過ぎると強制終了、5分未満だと放置プレイですww


WP_20141206_16_12_42_Pro

今年の一番の驚きは、ふ”れいすさんの「FCell」でした。うーむ、EXCEL侮りがたし… F#/C#/VB.netのコードを直接EXCEL上で書けて、しかもセルの計算式に指定出来て、しかも再計算も勿論自動で、しかも非同期処理出来て結果も非同期で反映できるという… ええ、私もVBA「大嫌い」ですよ。妄想でこういうの考えた事はあった。これマジ欲しい!!!

「札束でEXCELを殴る」で、Ust見れます。今、ようやくタイトルの意味が分かったw ちなみに、その次の「Play emacs」もデモンストレーションとして凄い良かった・面白かったです。


で、私の登壇した内容ですが… (;´Д`) ええと、忘年会なので、完全にネタです。ただ、ILやりたい人には、サンプルコードは参考になるかと思います。例によってGitHubにアップするので、遊んで下さい。

オリジナルプレゼンはこちら:抽象太郎ものがたり_e
GitHub: CenterCLR.StaticMethodInInterface
Ust: [6/6] NGK2014B 名古屋合同懇親会 昼の部 19:52から

ちなみに、突っ込まれました、その通りです、ハイ。

Youtubeで動画が公開されています。20分ぐらいからです。

去年もネタで笑いが取れるかどうかが勝負だったんですが、今年も何とか笑いが取れたので満足です。
来年も頑張ります。


WP_20141206_18_00_44_Pro

昼の部のLT大会の後は、夜の部で、しゃぶしゃぶ&しーすー食べ放題&飲み放題!!! とっても楽しかったです。

それでは、また。

進捗ダメ orz とりあえず中間報告 – プロ生ちゃん Advent Calendar 2014

プロ生ちゃん AdventCalendar 2014にエントリーしたんですが、スケジュールミスった&技術的な問題が重なり、4日午前3時でとりあえず白旗状態です (;´Д`)

pronamaac

挽回する気はあるので、中間報告はしておきます。

  • 一週間読み間違えてた – 全然駄目ですね。すいません。2日間しか時間が取れなかった…
  • プロ生ちゃんサイトの構成が変更されていた – 先日の名古屋で発表したネタを、Windows Phone&ストアアプリのユニバーサル対応にしようとしたのですが、何と、壁紙ページの構成が大幅に変わってしまい、スクレイピングコードが機能しなくなっていることが発覚 orz

サイト構成の変更はかなり痛い状態で、まず、壁紙データはOneDriveに移行したようですが、OneDriveのスクレイピングはかなり難易度が高く、そのままの方法では実現が難しい事が判明。

仕方が無いので、OneDrive APIを使ってみようとしたのですが、自分のOneDriveストレージを読み取る例は色々あってすぐに分かったのですが、プロ生ちゃんのストレージをAPIで読み取る方法が分からず、時間も圧していたので、再度検討し直し。

OneDriveには、フォルダ内の画像を一括してZIPに固めてダウンロードする機能がある事に着目し、URLもどうやら認証不要で取得出来るので、オンザフライでZIPのデコードをしながら表示するという作戦に切り替え。

しかし、HttpClientのPOST要求を正しく発行するのに手間取り、成功したかに見えたのもつかの間、途中でストリームが切断されてしまう問題を解決する事が出来ず、手詰まりました。

25日までには何とか形にしたいと思っています(駄目ならネタを変えます)。出来たらまたポストします!