INETA Japan リ ーダーズミーティング
INETA Japan リ ーダーズミーティング に出席。
小田急サザンタワーのマイクロソフト 本社 (新宿オフィス) へ。
内容は,
- 活動報告
- 『Tech・Ed 2004』のイベントについて
- .NET 本について
等。
初参加で初対面の人だらけ.取り敢えず名刺交換。
INETA Japan リ ーダーズミーティング に出席。
小田急サザンタワーのマイクロソフト 本社 (新宿オフィス) へ。
内容は,
等。
初参加で初対面の人だらけ.取り敢えず名刺交換。
Tech・Ed 2004 Yokohama に参加予定 (9/7~10).
目的は,
四日間に渡った "Tech Ed 2004 Yokohama" から、新幹線と北陸本線で夜中に帰って来た。ひんやりとした夜風に吹かれながら帰り道を歩いた。虫の声しか聞こえない。こっちはなんて静かなんだろうと思った。
今回は、なんと言っても「アジャイル開発ライブ!」に参加したのが、一番の体験だった。
INETA Japan の人達やその他多くの技術者の人と毎晩のように飲んだり、昔の同僚とばったり会って久々に昼食を共にしたり、NIFTY のフォーラムで以前よくお話をした ひどり さん と初対面したり、同じくNIFTY のフォーラムや XP祭り2002 でお話した επιστημη さんに STL.NET のお話を伺ったり、コミュニケーションが沢山とれた。
# いずれレポートを書きたい。

今更ながら、Tech・Ed 2004 Yokohama の参加レポートを書いてみた。
参加直後は、なんだか慌ただしくてレポートを書く気にならなかったが、昨日漸く社内報告会をおこなったので、そのついでに書いた。
# ちなみに現在社内では C# + ASP.NET の研修を実施中である。
# 今日は、研修用に以下の本を買った。
「『こんな .NET はいやだ』と思うものを自由にあげていくテスト。」
VB.NET JAPAN TOUR と INETA Japan リーダーズ ミーティングに参加。
http://www.ineta.jp/activity/calender/
会場は、マイクロソフト株式会社 笹塚 NA オフィス。

VB.NET JAPAN TOUR では、実際に VB の開発をしているジョーさんとアマンダさんから最新の VB の情報を二時間半に渡ってお話頂いた。
○ 内容
・VB 2003
・Visual Basic Power Pack
・Visual Basic .NET Resource Kit
・Tablet PC
・Vusial Studio Tools for Office
・.NET Compact Framework
・VB 2005
ClickOnce、My クラス、Edit&Continue 等
・Q&A
○ お土産
・Net Compact Framework Guide (Pocket Reference (O'Reilly))
・Visual Basic .NET Resource Kit CD
・Microsoft Mobile DevCon 2004 Conference DVD
・VB.NET World Tour Tシャツ
ジョーさんもアマンダさんも笑顔が魅力的な方だった。
通常の講演と異なり、気軽に随時質問も O.K. とのこと。講演者との距離が近い感じで、リラックスして聴けた。
私は VB でなく C# を主に使っているが、今の仕事で役に立つ知識が結構得られた。来週にでも調査してみたいことが幾つか。

※ 通訳付き、軽食と飲み物付きだった。
その後、INETA Japan リーダーズ ミーティングは一時間程度。
主に、INETAJ の今後について話し合われた。
終了後は、新宿で飲み会 (一次会・二次会)。
INETA 参加者の方達と五名で。
楽しいお話が沢山聞けて、とてもおいしいお酒だった。
少し焼酎を飲みすぎ。
Microsoft Tech・Ed 2004 Yokohama の 「Post Conference DVD」が送られてきた。
内容は、
Visual Studio 2005 Team System は楽しみにしていたもの。
早速個人的にも仕事上も評価して行きたい。
また、参加できなかったセッションについてもこれで内容を把握することができる。
続きを読む "Microsoft Tech・Ed 2004 Yokohama 「Post Conference DVD」" »
今日から、.NET/C# の社内研修を始めた。
ASP.NET 研修は、ずっと前から続行中だが、今度のは、Windows フォーム中心。
C++/C# での Windows 上での CAD の作り方の研修も数回実施。
自分の学習の方が全然進まない。
読みたい本もあるが、それより、もっとプログラミングを試したい気分。
今度、.NET Windows フォーム用のフレームワークと、その上にのる CAD のフレームワークのモデリング&実装をしてみたい。これはいずれにしても一度試す必要がある。
他にも .NET 上で実装を試したいものが幾つかある。開発用のツールとある種の ASP.NET アプリケーション。

近年マイクロソフトが力を入れているらしいパターン関連の頁は、今後益々要チェックのようだ。
日本語の書籍も今月頭に発売になっている。
最近 ASP.NET の社内研修をやっているのだが、見ていると、或る程度 ASP.NET が判ってきて実用的なプログラムを書こうとしたときに、よく戸惑うのが、次のような点らしい。度々質問を受けた。
ASP.NET でアプリケーションを書き進めていくと、上のようなことが解決できず、直ぐにソースがぐちゃぐちゃになってしまう、と言うのだ。
そういう人には、先の Microsoft patterns & practices から、先ず以下を試してみるように言ってみる。
かなり手取り足取り説明してあるので、パターンに不案内な人にも分かりやすい。
また、テストのしやすさについても言及されているのが良いと思う (*1)。
後は追加で「『3 層分散』周辺は読んどいてね」とか、Singleton パターンくらいなら知っているという人には「『C# でのシングルトンの実装』が面白いよ」とか。
とにかく、効果が実感しやすそうなところから紹介していく。
この辺に少しずつ慣れていって、それから少しずつ、ドメイン モデルを作るべきかどうかや、ユーザー インタフェイスのテスト方法などについても考えていったら良いと思う。
そして、色々とパターンを試していく中で、プログラムの複雑さをどこにどんな風に吸収させていけば良いのか、そのコツを帰納的に体得していくのが良い。
パターンは教育ツールとしても強力なのだ。
(*1) これからは、デザイン パターンやアーキテクチャ パターンの記述のテンプレートに「テストのしやすさ」という項も追加するのが良いのではないだろうか。
パターンを適用することでどう設計が良くなるのかを言うのに、「テストがやりやすくなる」というのは判りやすい指標になると思う。
2004 Japan Community Open Day に参加予定。
| 2004 Japan Community Open Day | |
|---|---|
| 日時 | 2004/12/18(土) 14:30-21:30 |
| 場所 | マイクロソフト 新宿オフィス17階会議室 |
| 概要 | コミュニティ活動や Visual Studio 2005 Team System などに関するワークショップ |
沢山のワークショップが行われる。
Visual Studio 2005 Team System 関連のワークショップへの参加を希望した。
# 翌日は、INETA Japan のリーダーズ ミーティングに出席予定。
INETA Japan の関連で上記二つのイベントに参加してきた。

・両方の会場となった小田急サザンタワー
■ 12/18(土)
この日は、『2004 Japan Community Open Day』に参加。
| 2004 Japan Community Open Day | |
|---|---|
| 時間 | 14:30-21:30 |
| 場所 | マイクロソフト 新宿オフィス17階会議室 |
| 概要 | コミュニティ活動や Visual Studio 2005 Team System などに関するワークショップ |

・Community Open Day の様子
の二つのワークショップに参加した。
Team System について、無責任に言いたいことを沢山言ってしまった。やれ導入しづらいだの、やれ高すぎるだの。
MS の人は熱心に聴いてくださったが、果たしてフィードバックになっただろうか。
その後、中華レストランで懇親会。
INETA の皆さんや MVP の方々、MS の方々など、色々な人とお話をさせて頂いた。
# 途中「○×ゲーム」があったが、商品ゲットならず。
そして二次会。
小井土 さん、福井 さん、中西 さんとご一緒させていただいた。
レゴの話などで盛り上がった。
コミュニティ関連の飲み会でいつも感じることだが、こういう素敵な方達と自分が一緒にいる状況というのは、五年前の自分には想像することもできなかった。
コミュニティならではの体験だと思う。
その後、12時過ぎにホテルにチェックイン。
ジャグリングの練習をした (謎)。
■ 12/19(日)
翌日は、『INETA Japan リーダーズ ミーティング』に出席。
| 第8回 INETA Japan リーダーズミーティング | |
|---|---|
| 時間 | 10:30-14:00 |
| 場所 | マイクロソフト 新宿オフィス17階会議室 |
帰りはPAPA'n VB の杉下 さんとたまたま飛行機が同じだったため、新宿~福井県丸岡町まで、話をしながら帰ってきた。
バスで帰る予定のところ、車で送っていただいた。
杉下 さんに感謝。
今回やっと羽田空港第二ターミナルを初利用。

・羽田空港第二ターミナル
続きを読む "『2004 Japan Community Open Day』 & 『INETA Japan リーダーズ ミーティング』" »

楽しみにしていた 中西 さん の記事が @IT に載った。
私の偏見かも知れないが、.NET 開発者は、Java の人たちと比較して、オブジェクト指向だのデザインパターンだのに慣れていないような気がする。
だから、今回のような .NET 開発者向けの記事は、これからとても重要だ。
これから .NET 開発者の前には、テスティング フレームワークだとかリファクタリングだとかエンタープライズ パターンだとか DI (DependencyInjection) コンテナだとかアスペクト指向だとか、Java の人たちが取り組んできた様々な技術の可能性が待っている。
そしてこれらは、オブジェクト指向やパターン技術に関する知識を前提としているのだ。
ところで、中西さんは、ドットネッターでアジャイラーで TDD Player (謎) だ。
なので、デザインパターンの有用性を .NET 開発者に教えるのにもなんだかとてもアジャイルな感じだ。
これまでのデザインパターンの解説に、次のような言葉が使ってあるものがあっただろうか。
- 「モチベーション指数」
- 「ホワイト・ボードにクラス図」
- 「NUnit用テスト・コードを記述してみよう」
例題も、HowMuchGreenbarLover メソッドだとか、HowHappy メソッドだとか。
福井 さん も書いてたが、とても中西さんらしい。
※ この記事は、Ichikawa さん のところでも紹介されている。
さて、今回の記事では、次のような問いに答えている。
問い: 「デザインパターン? 何それ? 何が良いの? .NET な我々にメリットあるの?」
でこれに関する有り勝ちな答え:
- 「デザインパターンというのは、こうこうこういうものだ」
- 「例えば、State パターンというのがあって、それはこういうもので・・・」
- 「使い方は、こんな感じ」
- 「VB.NET や C# でサンプル コードを書いてみると、こんな感じだよ」
- 「ねぇ、わかった? 良さそうでしょ。使ってみてよ」
これは、演繹的な説明だが、なんだかウォーターフォールな感じだ。
「うーん、どういうものかはなんとなく判ったけど… まあいいや、また今度改めて勉強するよ」
とか言われてしまいそうだ。
# 私経験有りますから!! 残念!!! 切腹!
で、中西 さん の答えは一味違う。少し真似してみるとこんな感じだろうか。
中西さん風の答え:
※ フィクションです。実在の中西さんとは関係ありません。
- 「例えば、VB.NET や C# でこんなコードがあるとするやん」
- 「ありがちなコードやけど、これってどうなんかなー 自分どう思う?」
- 「こんな風にしてみたらどうかなー」
- 「な? 自分知ってるやろ? ポリモーフィズム。どう、これ」
- 「これが State パターンや。で、今やったのがリファクタリング」
- 「な? 今度から State パターンやらいうたら、こういうのを指すんやで」
「デザインパターン23個覚えたぞ。さあ何かに役立てよう」でなくて、リファクタリングの結果として、使うべきところに使う分だけのデザインパターン。
こういきたいものだ。
さあ、今後の連載にも期待だ。

デブサミで INETA Japan として以下をやります。
# 私は C# 側。
是非ふるってご参加ください。
| Developers Summit 2005 (デブサミ2005 ― VB.NET vs C# 『.NET 言語合戦』) | |
|---|---|
| 日時 | 2005/02/03(木) 17:30~19:00 |
| 会場 | 青山ダイヤモンドホール B1F 会場 C |
| 主催 | 株式会社 翔泳社 |
| 詳細 | イベント告知サイト (Developers Summit 2005) にて |
関連リンク:
上記に参加してきたので、レポートしてみたい。
■ 詳細
| Developers Summit 2005 | |
|---|---|
| 日時 | 2005/02/03(木)~04(金) |
| 会場 | 青山ダイヤモンドホール |
| 主催 | 株式会社 翔泳社 |
| 詳細 | Developers Summit 2005 |
■ はじめに
デベロッパーズ サミットの開催期間には丁度、寒気団が日本上空に来ていた。
北陸地方は、今年一番の大雪で、出発前日の午前中には羽田行きが飛ばなかったこともあり、無事会場に行けるかどうかが先ず心配だった。
当日の朝は、五時半に自宅を出発した。道には新雪が積もり、ちょっと油断するとすぐスタックしてしまう。
高速道路もすっかり凍っている。凸凹の路をゆっくりと走った。
それでも小松空港には随分早くついた。INETAJ のイベントに一緒に出る杉下さんも、たまたま飛行機が同じで、同じ頃に空港にいらした。
心配した飛行機は定刻通りに出発し、定刻通りに羽田に到着。予定通りに会場につくことができた。ほっと一安心。

さて会場に入ると、すごい人の行列。
二日間会場の青山ダイヤモンドホールは大変な人で、歩くのに苦労した。
デベロッパーズ サミットには、最初の年から毎年参加しているが、参加するたびに良くなっている。
特に今回は、楽しみなセッションが盛り沢山で、選ぶのに迷うほどだった。
ただ、昨年も感じたことだが、あの Web からの申し込みのユーザー インタフェイスはちょっと「いけてない」気がする。
あれは本当にユーザーの方を向いた UI と言えるのだろうか。
ユーザー側の関心事に対してではなく、主催者側の関心事に対して UI が設計されているように感じた。
今回の参加者、即ちデベロッパーにとっては、ユーザーの視点・ユーザーの関心事に合わせるのがデフォルトだから、多分違和感有りまくり。
でもまあ、その他の面では大満足。とっても良いイベントだった。
デベロッパーが中心というのが実に良い感じ。
このイベントのサブタイトルに「デベロッパーの復権」というのがある。
この趣旨には大賛成。
日本のデベロッパーも、もっと楽しく自信に満ちて物作りをして良い。
■ 参加内容
○ XP事例カタログ
大熊 知栄 氏、猪狩 錦光 氏、関 将俊 氏、小倉 唯克 氏
四年の歴史を誇る XPJUG (日本 XP ユーザグループ) で紹介されてきた XP プロジェクトの中から三つの例が紹介された。
すごい人で立ち見がでている。
大熊 さんの司会に始まり、三人の発表者が順に XP 事例の紹介を行った。
関 さん の発表は、XP 祭り 2004 で 関 さん が発表された内容「三年目の報告」(サブタイトル「XPが良いか悪いかなんて話は もうしないよ。」) だが、マイナー バージョンアップしていて「その後」が少し語られた。題して「3.5年目の報告」。「忍者式テスト」など、独自に XP をカスタマイズされている。
二人目の方はゲーム業界での XP。単純作業などのときに使う「ペアプロ解除」というプラクティスが興味深かった。
お二人とも開発者として、XP をやっていく中で現実的な解をいくつも見つけている。
また、回顧をよくやっていて、次にフィードバックしている。
今回特に良かったのは、顧客側からの XP プロジェクトの発表があったことだ。
以前見たことのあるバーンダウン チャートが顧客側視点で語られた。
これは結構目から鱗で、視点が変わると随分違って見えるものだと思った。
○ 失敗から学ぶプロジェクトマネジメント
伊藤 健太郎 氏

XP だけでなく、プロジェクト マネジメントについても学ぶ必要があるだろう、ということでこのセッションを聴きにいった。
という内容のセッション。
プロジェクトの失敗原因で、
というのが印象的だった。
ソフトウェア開発の場合、QCD (Quality: 品質・Cost: コスト・Delivery: 納期) のマネジメントだけでは、中々プロジェクトの成功に結び付かない、とよく言われているようだが、プロジェクト マネジメントも進化しているようだ。
講演中、プロジェクト マネジメントによるプロジェクト成功のための様々なキーワードが使われたが、「いけてる」と思ったものを私の独断であげてみる。
ところで、はじめの方で「是非このセッションが思い出に残るように」とのことで、「プロジェクトの失敗について隣の人と話してみよう」という時間があった。
アイス ブレーキングなんだろうが、これはちょっといけてない。余りにも唐突で中途半端な感じ。
○ .NETでアジャイル ペアプロ ライブ! ~VB.NETはテスト ファーストで行こう!
中西 庸文 氏、福井 厚 氏

「ドットネッターでアジャイラーで TDDer (謎)」になるための方法について。
最近は、Java の開発者に比べてアジャイルに馴染みが薄い .NET の開発者の人たちにもアジャイルな開発を知ってもらいたいという趣旨の講演や記事が漸く増えてきたようだ。
嬉しい限りだ。
さてこのセッションだが、なんと終始関西弁。
ペア プレゼンになっていて、会話形式で説明が進められていく。
圧巻は途中二回の「ペアプロ笑劇場」と題したペアプロ ライブ。
若手アジャイラーとアジャイル未経験なベテラン先輩開発者という設定で、テスト ファーストなペアプロを行う。
で、このペアプロが TDD で且つ TDD (謎) なのだ。
前者の TDD は、勿論テスト駆動開発 (Test Driven Development) のこと。アジャイルではお馴染みの手法だ。
後者の TDD (謎) は、ツッコミ駆動開発 (Tsukkomi Driven Development)。
テンポの良いボケとツッコミによって、開発が進められていった。このテンポは関西弁ならではかも。
※ ちなみにツッコミ駆動開発にもペアプロは必須。
※ 一人でぶつぶつとボケとツッコミをしながらプログラミングされるのはかなり嫌だ。
面白いのは、ペアプロなので、ドライバー役 (キーボードでプログラムを書く方: リアルタイム コード レビューア) とパートナー役 (ドライバーのコーディングを見ていてリアルタイムにフィードバックを行う) があるのだが、パートナーがボケてドライバーがツッコんでいたりする。
二回目の「ペアプロ笑劇場」では、Mock (擬似オブジェクト) によるテストも紹介され、技術的にも興味深かった。
このセッションを聴き終わって考えたこと:
これは私の経験則だが、アジャイルな人というのはアジャイルな講演をする。
見ていて感動が有る。
プレゼンテーションが濃いのだ。
| 薄いセッション → | 濃いセッション → | もっと濃いセッション |
|---|---|---|
| パワーポイントに文章を書いておいて、それを読みながら解説 → | 実際にやってみせる → | 参加者に体験してもらう |
| 言葉で説明する → | 図や写真で説明する → | 寸劇で表現・動かして説明 |
| 抽象的な新しいアイディアを述べる → | 具体的な例を交えて新しいアイディアを述べる → | 新しいアイディアを試してもらう |
コミュニケーションの帯域が違うのだ。時間当たりに伝わる情報量が違う。
これは、沢山話して言葉数を増やす、ということではない。沢山のパワーポイントを用意する、ということでもない。
発信側でなく受信側の情報量を増やすのだ。
新しいアイディアというものは、いくら言葉数を増やしたって伝わらないものは伝わらない。
見せる工夫、伝える工夫をしなければ。
私はこれを Broadband Communication と呼びたい。

仕事でも多分同じだ。
例えば、
という問題提起があるとする。先のプロジェクト マネジメントのセッションでもこれはあげられていた。
でそれに対する解。プロジェクト マネジメントのセッションでは、
ということだった。
でも「プロジェクトの失敗の主な要因はコミュニケーション エラー」という問題提起に対して、しっかりやるべきなのは自明であって、それだけでは不十分なのだ。
そこには「見える化」など実践するための工夫がなければならない。アジャイルな人たちはそこが上手だと思う。
○ INETA Japan Presents VB.NET vs C# 『.NET 言語合戦』
河端 善博 氏、東海林 秀晃 氏、小野 修司 氏、石野 光仁 氏、菊池 和彦 氏、小島 富治雄 氏、福王寺 聡明 氏、杉下 朋年 氏、中西 庸文 氏、片岡 真二 氏、樋口 忠洋 氏、Hollytown 氏
Visual Studio .NET 2003 になってからは、VB.NET と C# はそれほど使い勝手に違いがなくなりつつある。2005 では更に優劣がなくなる。
その中で、VB.NET と C# のそれぞれの長所をあげて、双方の使いどころについて考えてみよう、という趣旨のイベント。
別にどっちを使っている方が偉いかを競う訳ではない。
INETA Japanは、.NET のコミュニティのコミュニティだ。
私は、パネラーの一人として参加。C# 側。
ライブで VB.NET と C# のそれぞれで七並べの戦略部分をプログラミングし、その場で対決した。
お客さんは勝つと思う方に投票し、勝負の結果により、アマゾン ギフト券や PSP などが当たる。全員に参加賞もあたる。
パネル ディスカッションは、どうやら C# 側が劣勢な儘終わってしまった。折角 C# 側のパネル リーダーの小野 さん が頑張ってくれてたのに。残念。
C# を応援してくれてた参加者の方はさぞやきもきしたことだろう。
というか、VB.NET 側のパネル リーダーの杉下 さん のプレゼンが良過ぎた。
反対にプログラミング ライブの結果の七並べの対戦では、C# 側が終始優勢だった。
商品の効果かも知れないが、全体としては結構ほんわかと盛り上がっていたようで、コミュニティ色が出ていて良かったのではないだろうか。
○ My Framework作成の勧め:アプリケーションを30個作る時に何を用意するか
arton 氏
或る業務に関して、沢山アプリケーションを書くのであれば、その業務に特化した良いマイ フレームワークを自分で書いた方が良い、というお話とその作り方のお話。
arton さん のお話は、理論的で且つとても判り易い。
arton さん の独特の語り口は、とても説得力がある。
フレームワークの作り方の話は特に技術的に興味深かった。
パラメータ、プッシュ モデル (Tell) /プル モデル (Ask)、依存性注入 (Dependency Injection) などの実際の実装方法を、C# のソースをデバッグ実行しながらデモで見せてくれた。
リフレクションで仮引数名で検索してみせたり、実行時にコンパイラを呼び出してパラメータを評価させたり、自作の DI コンテナを使って依存性注入を実際にやってみせたり、ととても楽しめた。
※ ちなみに、このソース コードは公開されている。
○ フレームワークの効能と、.NET導入事例紹介
三部 雅法 氏
.NET Framework 上でのフレームワークの紹介とその導入事例の紹介。
自社製フレームワークの説明という感じ。
○ セッション参加者のパーティー (懇親会)

一日目の夜は会場でパーティーがあった。
デベロッパーズ サミットの場合は、他のイベントと比較して広い分野から参加者が集まっている。
で、層としては技術者が中心。
なんか縦割りでなく横割な感じで新鮮だった。
例えば、ドットネッターとアジャイラーは日頃それ程イベントでかぶらない。
今回はあちこちでドットネッターとアジャイラーが名刺交換する風景が見られた。
私も、多彩な人とお話ができて実に楽しかった。
○ 二次会
懇親会を途中で抜け出して、ドットネットな方々 (INETAJ・MVP) と原宿辺りで二次会。
○ その他
二日目の午前中は、INETAJ のリーダーズ ミーティングに参加した。
マイクロソフト 新宿オフィス。
その後 INETAJ の方々と食事会。
■ 人とのつながり
今回も多くの方々と話すことができた。
イベント参加の一番の収穫。
※ お会いしたのにお名前のもれてる方、すみません。ご指摘頂けると幸いです。
■ 関連リンク:
2/3 に行われた Developers Summit 2005 ― INETA Japan Presents VB.NET vs C# 『.NET 言語合戦』 のために集めたネタ (とイベント中で使われたネタ)。
# 久々に技術ネタを書いてみる。
# と言っても、某掲示板で使ったネタの使い回し。
例.interface ICloneable { object Clone(); }
interface では公開されているメソッドとプロパティの外見 (名前、パラメータ、戻り値) だけが宣言されていて、実装部分が定義されていない。実装部分は、その interface を実装するクラスによって定義される。
例.class Employee : ICloneable { private string name;public string Name
{get { return name; }
set { name = value; }
}public Employee(string name)
{ Name = name; }public object Clone()
{
return new Employee(Name);
}
}
interface は 抽象クラス (abstract class) と機能的には似ている。
抽象クラスも、中身のないメソッド (abstract method) の宣言を持つことができ、そこから派生したクラスで、そのメソッドの実装を定義する。
例.abstract class Person { private string name;public string Name
{
get { return name; }
set { name = value; }
}public Person(string name)
{ Name = name; }public abstract void DoSomething();
}class Employee : Person
{
public Employee(string name) : base(name)
{}public override void DoSomething()
{ /* Do good job. */ }
}
機能上の大きな違いとしては、
というものがある。
しかし、機能上の違いでなく、使い分けるときの一般的な指針はないだろうか。
デザインパターンの一つである、State/Strategy パターンの場合で考えてみよう。
先ずは、抽象クラスを使った場合と、interface を使った場合の両方について書いてみる。
・抽象クラスを使った場合の例:![]()
class Context { State state = null;public State State
{
set { state = value; }
}public void DoSomething()
{
if (state != null)
state.DoSomething();
}
}abstract class State
{
public abstract void DoSomething();
}class ConcreteState1 : State
{
public override void DoSomething()
{ /* 省略 */ }
}class ConcreteState2 : State
{
public override void DoSomething()
{ /* 省略 */ }
}
・interface を使った場合の例:![]()
class Context { State state = null;public State State
{
set { state = value; }
}public void DoSomething()
{
if (state != null)
state.DoSomething();
}
}interface State
{
void DoSomething();
}class ConcreteState1 : State
{
public void DoSomething()
{ /* 省略 */ }
}class ConcreteState2 : State
{
public void DoSomething()
{ /* 省略 */ }
}
さて、State/Strategy パターンを使う場合、はたしてどちらが標準的なのだろうか。
「State/Strategy パターンを構成している部分だけ」を見ると、或る振る舞いに関する制約が付けられれば十分なので、interface が適しているような気がする。
但し、実際の設計では、
「State/Strategy パターン」を使おう → じゃ interface を使おう
とはならない。
それは、通常 State/Strategy パターンが適用される場合というのは、先ず (リファクタリングなりの結果としての) クラス設計があって、そこにおける抽象化すべきものがクラスなのか、振る舞いに関する制約なのかの判断があり、結果として「State/Strategy パターン」の形になるだけであるからだ。
つまり、抽象化したいのがクラスであれば抽象クラス、単なる振る舞いに関する制約であれば interface、という感じで使い分けることになる。
それから、もう一つ。
実装上の問題として、State/Strategy パターンなどが対象としている関心事が、既存のクラス階層と直交する関心事であった場合は、抽象クラスでなく interface を使う。多重継承ができないからだ。
例えば、始めの方の例でいうと、
等の継承によって形成される継承ツリー或るいはその他の継承ツリーが対象としている関心事と、ICloneable が対象としている関心事は直交している。
従って、ICloneable の方の関心事の実装には interface を使う。
これは、「アスペクトの実装を便宜上 (言語の都合上) interface で行う」というイディオムといえるかと思う。
尚、余談になるが、Strategy パターンの場合は、抽象クラスも interface も使わずに delegate を使う、というイディオムも有ると思う。
例.class Context { public delegate void DoSomethingMethod();DoSomethingMethod doSomething = null;
public DoSomethingMethod OnDoSomething
{
set { doSomething = value; }
}public void DoSomething()
{
if (doSomething != null)
doSomething();
}
}
今まで新人向けのオブジェクト指向の研修で、「継承」というのは、
class Sub : Super { }
こういうのだ、とか説明してきたんだけど…
ごめん。この説明はちょっとごまかしなんだ。
説明が面倒だったから…
本当は、これは C# 言語の「派生」なんだ。オブジェクト指向でいう継承と同義じゃない。
これは、「『派生』という機能を使って継承を実装する」っていう C# のイディオムに過ぎないんだ。
他にも、オブジェクト指向の用語を C# にマッピングして説明するときに結構ごまかしてるんだ。
Super s = new Super();
オブジェクト指向っぽくこれを表現してみると、
「Super というクラスに new というメッセージを送ると s というインスタンスが作られた」
という感じかな。
s.Foo();
これも、「s という名前のインスタンスに Foo というメッセージを送ると s はそれに応じた振る舞いを行う」という感じに表現できる。
でも、実は C# 的には、s はインスタンスじゃない。参照変数に過ぎない。インスタンスを参照してるだけ。インスタンスは別に在る。
これも、「クラスのインスタンスを参照変数で表現」っていう C# でオブジェクト指向をやる場合のイディオムを前提に説明しちゃってる。
用語の曖昧さを使って誤魔化していたんだ。説明すると長くなっちゃうからね。
本当は、どのレイヤの話をしているのか、明示すべきなんだ。
ごめんよ。

■ VC++ から来た人向けの資料
VC++ でウィンドウズ アプリケーションを書いていた人が、.NET 開発を始めるときに重宝するドキュメント。
※ 特に C + Win32API で書いてた人向け

■ Visual Studio の HTML ヘルプ作成機能
C# では XML コメントを書くことで、HTML ヘルプをコードから自動生成することが出来る。
※ 尚、VB.NET では次期バージョン (2005) から、この機能に対応する。
Visual Studio では、メニューから「Web ページのビルド コメント」を選ぶことで、コードの XML コメントからHTML ヘルプ作られる。
但し、この機能で作られた HTML ヘルプは、Windows XP SP2では見ることができない。
参考:
■ NDoc の紹介
"NDoc" というツールを使うことで、MSDN のような HTMLヘルプ形式 (.chm ファイル) を含む様々なヘルプ形式を出力できる。
※ プロジェクトのプロパティで「XML ドキュメント ファイル」を指定しておく必要がある。
NDoc の使い方はこちらを参考に。

■ .NET 開発における名前付けのガイドライン
.NET 開発でクラス名や変数名などのシンボル名を付けるときのガイドライン。
参考:

■ .NET 開発用ツール
.NET 開発用の様々なツールが存在している。
※ 森屋 英治 氏 により N* (エヌアスタ) と命名されている。

■ ASP.NET Web アプリケーションの設計パターン
ASP.NET でアプリケーションを作っていく場合、よく入門書などにあるように、Visual Studio 上、でコントロールやデータ クラスをドラッグ&ドロップで作っていくと、
となり、或る程度以上の規模のアプリケーションでは、複雑なコードとなることが多い。
そのような場合にどのように設計を行うのが良いか。その代表的な設計のパターンを幾つか紹介する。
参考:
参考書籍:

■ Web アプリケーションにおける 危険な HTML の入力
先ず Web アプリケーション一般における、危険な HTML の入力とはどのようなものか、以下を参照のこと。
■ ASP.NET での対策
今の (.NET Framework 1.1 以降の) ASP.NET では、デフォルトでは、タグ入りの入力などは、自動でチェックされ、危険なリクエストを受けると例外がスローされる。
これにより、アプリケーションがサニタイズを怠った場合でも、最低限のセキュリティ対策はとられることになる。
これをオフにするにし、アプリケーション側でサニタイズを行う場合には、
Web.config で、
のようにする。
※ 参照:
アプリケーション側で HTML サニタイズを行うには、以下のメソッドが便利。
データバインド時に使う場合はこんな感じ:
<%# HttpUtility.HtmlEncode(DataBinder.Eval(Container, "XXXXX").ToString()) %>
※ 関連サイト:

■ Web アプリケーション一般における、 SQL インジェクションの脅威
先ず Web アプリケーション一般における、 SQL インジェクションの脅威とはどのようなものか、以下を参照のこと。
■ .NET の Web アプリケーションにおける対策
対策としては以下を参照:

C#言語がJIS (Japanese Industrial Standard: 日本工業規格) に制定された。JIS X 3015。
C# 好きの私としては喜ばしい限り。
昨年の夏前に Beta を見てその強力な機能強化に魅入られて以来、待ち遠しい Visual Studio 2005 だが、果たして何時頃出るのだろうか。
先ずは近々出る予定の Beta2 に期待が掛かる。
下の記事を見る限り、4/25 (3/56) 辺りに Beta2 が出るようだ。
ゴールデン ウィーク前後には試せそうか。
The Beta Experience will be available in your region from the 25th of April.

4/17(日)は、新宿で INETA Japan の総会に出席。
小井土 さん と私がジャグリングのボールを持ってきていて、休憩時間にジャグリングの啓蒙活動を行った。
# N* (エヌアスタ) の一つに NJuggler というのも追加されることになった (嘘)。
終了後は、飲み会へ。
沖縄そばの『ヤンバル』など、三軒ばかり梯子。
# 日曜だというのに、新宿には居酒屋の客引きが沢山居て ぺがらぼ さん と一緒に吃驚。
INETA の方々と .NET 系のお話ばかり延々とできて、嬉しかった。
いやしかし。皆さん、本当によく勉強されてる。
セミナーに参加したり、本を読んだり、英語勉強したり。
もうお会いするたびにどんどん知識が増えていってるし。
素晴らしいことだ。
改めてすごい人たちとご一緒させて頂いてるんだな、ということを実感した。
NAgile を推進されている afukui さん が新しいサイトをオープンされた。
NAgile (エヌ・アジャイル) というのは、.NET でアジャイル開発を行うこと。
NUnit をはじめとした .NET 用のアジャイル開発ツール (N*: エヌ・アスタと呼ばれている) を使う。
.NET 開発者にも、是非アジャイル手法が広まっていってほしいと願っている。
※ ちなみに、.NET でアジャイル開発を行う人のことは、NAgiler と呼んでいる。
その他の NAgile のサイト:
導入記事はこちら:
待ちに待った Visual Studio 2005 のBeta 2 日本語版が出た。
先程インストール完了。
これから色々試してみるつもり。
わくわくする。
以下のイベントに参加予定。
大変に濃いイベントとのこと。去年は仕事の都合で参加出来なかった。
興味の有る講演が目白押し。楽しみだ。
| 『MD3 (エムディーキューブ)』 | ||
|---|---|---|
| マイクロソフト社のエバンジェリストやアーキテクトが技術者を対象に広範囲で奥の深い技術情報をお届けするイベント 昨年「Microsoft Developer Deep Dive(MD3)」として開催されたもの 参考: aspxの日記(ASP++ブログ) - MD3(エムディーキューブ)が気になる。 |
||
| 主催 | マイクロソフト社 | |
| 開催日時 | Day1 | 2005年6月16日(木) 13:00-17:45 (受付開始 12:30-) ※終了後にパーティを開催予定 |
| Day2 | 2005年6月17日(金) 11:00-17:45 (受付開始 10:30-) | |
| 開催場所 | 野村コンファレンスプラザ | |
| 参加費 | 無料 | |
| お申込方法 | クローズドなイベントで一般向けには参加者を募集していないのですけれど、INETA Japan 参加コミュニティのメンバー向けに参加募集しても良いそうなので、ここを見ている方でご希望の方は、5月30日までにメールをください | |
NAgile ネタ。
日本XPユーザグループ (XPJUG) の第13回ユーザー会で、NAgiler の福井さんが『NAgileのご紹介』というネタ (『ネタ』という表現でいいのだろうか) を発表されたそうだ。
資料 (PDF) が、ここで公開されている。とても楽しい内容だ。
http://plone.xpjug.org/event/20050526meeting/NAgile_intro.pdf/file_view
・MSDN ― Launch 2005 での発表 (英語)
<http://msdn.microsoft.com/launch2005/> より
Welcome to the Visual Studio 2005, SQL Server 2005, and BizTalk Server 2006 launch portal. On this site you'll find links to technical content designed to help you learn about the new products. In addition, this site serves as the home for our online launch day activities. Launch is the week of November 7, 2005...and on launch day we’ll have:
・米MSがSQL Server、Visual Studio、BizTalk Serverの出荷日を正式発表
MYCOM PC WEB <http://pcweb.mycom.co.jp/news/2005/06/08/100.html> より
米Microsoftは6月7日(現地時間)、現在米フロリダ州オーランドで開催されている同社主催の開発者会議「Tech・Ed 2005」において、データベースの「SQL Server 2005」、開発ツールスイートの「Visual Studio 2005」、Webサービス構築や異なるアプリケーション間の連携を行う「BizTalk Server 2006」の3製品の出荷日が、11月7日になると正式に発表した。また同日、SQL Server 2005向けの開発者プレビュー版(June CTP)が提供されたことも発表した。同CTP版はMicrosoftの SQL Serverサイト <http://www.microsoft.com/sql/downloads/>からダウンロード可能だ。
・米Microsoft,「SQL Server 2005」「Visual Studio 2005」「BizTalk Server 2006」は11月第2週リリース
IT Pro <http://itpro.nikkeibp.co.jp/free/ITPro/USNEWS/20050608/162309/> より
米Microsoftは,データベース・ソフトウエア「SQL Server 2005」,アプリケーション開発環境「Visual Studio 2005」,電子商取引向けサーバー・ソフトウエア「BizTalk Server 2006」の最終版を11月の第2週にリリースする。Microsoft社が米国時間6月7日に明らかにしたもの。
・SQL Server新版、11月に正式リリース
IT Media エンタープライズ <http://www.itmedia.co.jp/enterprise/articles/0506/08/news008.html> より
米Microsoftはフロリダ州で開催のTech・Ed 2005で6月7日、「SQL Server 2005」「Visual Studio 2005」「BizTalk Server 2006」を11月7日の週に正式リリースすると発表した。
英語版の話です。
日本語版は未定です。
※ 英語版より早いことはないでしょう。多分。
以下に参加予定。
| Microsoft Tech・Ed 2005 Yokohama | |
|---|---|
| 日時 | 2005年8月2日(火)~5日(金) |
| 場所 | パシフィコ横浜 |
INETA Japan の方で以下の参加費用が無料になる招待枠があるそうです。
参加費用: 一般通常参加費用 4日間の場合で ¥125,000円 (7月8日までの早期申込ので4日間の場合 ¥87,000円)
この無料招待を希望される方は、6月13日(月)までにメールください。
MD3 (エムディーキューブ: Microsoft Developers Deep Dive) に参加してきた。
| 『MD3 (エムディーキューブ)』 | |
|---|---|
| マイクロソフト社のエバンジェリストやアーキテクトが技術者を対象に Deep な技術情報を話すクローズド イベント | |
| 主催 | マイクロソフト社 |
| 開催日時 | 2005/06/16・17 ※一日目終了後にパーティ |
| 開催場所 | 野村コンファレンスプラザ |
| 参加費 | 無料 |
会場は新宿野村ビルの上から三つ目の階。
![]() | ![]() |
会場の雰囲気は下のような感じ。
![]() | ![]() | ![]() |
流石に深い内容のセッションが多かった。
これを聴けただけでも来た甲斐があった、と思えるセッションも一つあった。
また、Tech・Ed などでは聴けないようなセッションもあって楽しめた。
一日目終了後のパーティでは、福井さん、萩原さん、一色さん、市川さんや、他にも多くの方たちと色々な話をさせて頂いた。
二次会では、小井土さん、福井さん、市川さん、小野さん、森屋さんと。
そして、三次会では、小井戸さんと MD3 のスピーカーの MS のエバンジェリストの方たちと。
なんとも感動的なメンバーだ。
こういう素晴らしい方たちとお酒を飲みながら、技術の話を延々と、というのは、技術者として本当に幸せを感じるひと時である。
本当になんて楽しそうに技術について語る人たちなんだろう!

二日目終了後、小井土さんがジャグリングのリング (!) を持っていらしてたので、トイレ脇の目立たないところでちょっと触らせてもらった。
私は偶然 (謎) ジャグリングのボールを持っていたので、小井土さんと福井さんと私とでジャグリングとアジャグラーという言葉を MS のエバンジェリストの方々に少しだけ披露。萩原さんはじめ MS の方々にも結構面白がってもらえたようだ (多分)。
# その後は INETA の方々と河豚会へ。
6/18 (土) は、INETA リーダーズミーティングに参加。

# これにて三日連続の新宿滞在も終了。
『C#で学ぶオブジェクト指向プログラミング』という本のブックレビューを書きました。
Tech・Ed Yokohama において、INETA Japan は、昨年 (*1) に引き続き今年も セッションをやります。
(*1) 昨年のイベント:
Micosoft MVP (Most Valuable Professional) Visual Developer - Visual C# のアワードを受けることになりました。
C# 大好きな私としては、大変光栄でありがたいことだと感じています。
関係者の皆様ありがとうございます。そして、今後とも宜しくお願い致します。
※ 同時に受賞した INETA Japan のリーダーの方々。
※ Tech・Ed 2005 Yokohama ― Welcome to INETA Japan Event 2005! の続き
Tech・Ed 2005 Yokohama で、INETA Japanイベント『デスマーチからの脱出 ~徹夜からの帰還~』 (8/4 18:30~20:00) をやります。
※ INETA Japan ― Tech・Ed 2005 Yokohama スペシャルプログラム「Welcome to INETA Japan Event 2005!」の特設頁
豪華賞品が当たる抽選会 (*) がありますので、是非奮ってご参加ください。
(*) INETA関係者 (リーダー、代理者、INETAJ枠参加者) とTechEd 運営側の方は対象外
Tech・Ed 2005 Yokohama で考えたこと ― ソフトウェア開発の問題点の解決とエンジニアの幸せについて。
.NET や Visual Studio、Team System など、ツールの進化は素晴らしい。かなり良くなって来ている。
効率的に開発が行えるようになってきた。
また、.NET の世界でも AOP や DI コンテナ、Generics などが新しいパラダイムとともに、使われていくようになりつつあるようだ。
一方で、.NET にアジャイル開発を積極的に取り入れようという動きもある。
そんなふうに技術が変化していく中、エンジニアとしてどのように幸せになっていったら良いのか、考えた。
※ 下記マインドマップは、Jude Professional 2.3 で作成。クリックすると大きいマインドマップがポップアップ。
工学的に解決を図ろうとするのとアジャイルに解決しようとするのは、態度の違い。
どちらの態度も必要。
「或る現場で工学的手法がうまくいかなかった → 工学の所為」とは言えない。単にその現場のやり方が「実は工学的にまずかった」だけかも。
また、たとえ或る問題が現状の工学で解決できないからといって、工学的解決をあきらめるべきではない。
但し、工学の守備範囲外の人間系の問題が現場でボトルネックとなっている場合も多い筈。そうした現場では、工学的に解決は図れない。
反面、再利用性など工学的手法が必要な面もある。
どうも、一人で考えても拙い結論にしか到らないようだ。
ツッコミ募集中。
※ 「Tech・Ed 2005 Yokohama で考えたこと ― ソフトウェア開発の問題点の解決とエンジニアの幸せについて」の続き。
忘れないうちに、Tech・Ed 2005 Yokohama に関することをメモ。
2005/08/02(火) 出発
6:30 自宅発
えちぜん鉄道 → JR北陸本線 → JR新幹線 → JR横浜線 → みなとみらい線
| Microsoft Tech・Ed 2005 Yokohama | |
|---|---|
| 期間 | 2005/08/02(火) - 05(金) |
| 場所 | パシフィコ横浜 (横浜市西区みなとみらい) |
| 8月2日(火) |
| 時刻 | Room | Session ID | Title | Speaker | |||||
|
|
MVP Roundtable 「MVPプログラムについて」 | |||||||
|
|
|
|
マイクロソフト株式会社 荒井 省三、INETA Japan 市川 龍太 |
|||||
|
|
|
|
マイクロソフト株式会社 荒井 省三、INETA Japan 市川 龍太 | |||||
| 18:30 - 20:00 |
|
|
|||||||
| 夜 |
|
|
|||||||
| 夜 |
|
|
| 8月3日(水) |
| 時刻 | Room | Session ID | Title | Speaker | |||||
|
|
|
|
マイクロソフト株式会社 近藤 和彦 | |||||
|
|
|
|
NRI ラーニング ネットワーク株式会社 矢嶋 聡 | |||||
|
|
|
|||||||
|
|
|
|
マイクロソフト株式会社 中原 幹雄 | |||||
|
|
|
|||||||
|
|
|
|
マイクロソフト株式会社 高橋 忍 | |||||
|
|
|
|||||||
|
|
|
|||||||
| 夜 |
|
|
| 8月4日(木) |
| 時刻 | Room | Session ID | Title | Speaker | |||||
|
|
|
|
NRI ラーニング ネットワーク 株式会社 矢嶋 聡 | |||||
|
|
|
|
マイクロソフト株式会社 荒井 省三 | |||||
|
|
|
|||||||
|
|
|
マイクロソフト コーポレーション 波村 大悟 | ||||||
|
|
|
|||||||
|
|
|
|
株式会社 豆蔵 岡村 敦彦 | |||||
|
|
|
|||||||
|
|
|
INETA Japan | ||||||
|
|
|
|||||||
| 夜 |
|
|
| 8月5日(金) |
| 時刻 | Room | Session ID | Title | Speaker | |||||
|
|
|
|
マイクロソフト株式会社 萩原 正義 | |||||
|
|
|
|
マイクロソフト株式会社 中原 幹雄 | |||||
| 11:50 - 13:20 |
|
|
|||||||
|
|
|
|
マイクロソフト株式会社 近藤 和彦 | |||||
|
|
|
|
マイクロソフト株式会社 安藤 浩二、有限会社アークウェイ 森屋 英治 |
![]() ・会場となったパシフィコ横浜 |
||||||
|
||||||
|
||||||
|
||||||
|
||||||
|
||||||
|
||||||
|
||||||
|
||||||
2005/08/05(金) 帰宅
みなとみらい線 → JR横浜線 → JR新幹線 → JR北陸本線 → えちぜん鉄道
23:30 自宅着
今年もアツイ四日間だった。沢山の刺激を受けた。
朝から晩まで、のんびり息をついている間もない程だった。セッションの合間も夜も含めて技術漬けの毎日だ。
エンジニアとしての自らの現状を見つめ直す機会になった。
ツールの進化は素晴らしい。
でもツールだけ進化しても幸せにはなれない。
エンジニアとして、次のパラダイムに自らをシフトさせていかねば。
日々学んでいくのがエンジニアだ。
さあ変化を受け入れよう。
ここで一句詠んでみる。
「行く末の道の途中に吾亦紅」
― Fujiwo
※ 吾亦紅の花言葉は「変化」。

今日からシアトルへ。
| Microsoft MVP Global Summit 2005 | |
|---|---|
| 期間 | 2005/09/28~10/01 (10/04 帰宅) |
| 場所 | ワシントン州レドモンド マイクロソフト コーポレーション |
荷造り完了。

今回、英会話の本やジャケット等、色々と散財。
電子辞書と無線LANアダプタも新たに購入。

電子辞書は、SHARP の PW-M800。
¥10,800 (メーカー希望小売価格:¥35,000) だった。
電子辞書の中でも特に小さく軽く (電池を入れて 113g と平均的なものの半分位) 値段も手頃で、収録辞書は22。
シアトルに向け朝6:40に自宅を出発。
車で小松空港へ。
乗るのは、一日一便しかない 8:00 発 ANA 成田行き。
普通の 767 だとばかり思っていたら、なんとも小さい飛行機で吃驚。

成田へ移動中。

成田空港到着。まだまだ時間があるので、タイのグリーン カレーを食べたり、ぼんやりと飛行機を眺めたりした。

MVP が集合して、いざ出発の乾杯。

ノースウェスト航空の直行便でシアトル空港に到着。
時差ぼけ防止のため、前夜は一時間の睡眠に抑え、飛行機の中で睡眠をとった。

"COMP USA" というコンピュータ専門店へ。ここではプレゼン用のツールを購入。

ボーイング社の敷地内にある航空博物館へ。SR-71偵察機の本物が展示してあって感激。

コンコルドの中も見た。

昼食はシアトルの東の町べレビューで中華。

その後、"Microsoft MVP Global Summit" にレジストレーション。

Microsoft のカンパニー ショップへ。最新のマウスだとか Windows Vista のシャツとか色々と購入。
![]()
そしてコロンビア ワイナリーへ。5種類くらいのワインを試飲させてもらった。とても美味しい。一本購入。

"Doubletree Hotel Bellevue" にチェックイン。部屋がとても広い。

日本の MVP でディナーへ。

その後ホテルのバーで暫く話をした。途中はあまりの眠さに、しょっちゅう意識を失う。
そして就寝。長い一日だった。
セミナーの内容は NDA (Non-Disclosure Agreement: 秘密保持契約) の関係で詳しく書けないので、差し障りのない範囲で。
マイクロソフト キャンパスは三万人もの社員がいるそうで、とても広い。木々が茂る中に沢山の建物が建っている。そしてバスが巡回している。
とても良い環境だ。
一人一部屋ずつあるという仕事場のある建物にも入った。
三日間は、英語漬けの日々。
英語でセミナーが続く。
途中で、Don Box 氏と写真を撮った。それから Gregor Hohpe さん を紹介していただいた。
途中の夕食会では、マイクロソフト コーポレーションの社員や外国の MVP に積極的に英語で話し掛けるようにしてみた。
十~二十人位の人と話したと思う。矢張り余り通じない。
以降、一度話した人と再会したときには、目を合わせて "Hi." と笑いかけるようにしてみた。これは必ず答えてもらえるので良い。
MVP Party というのもあった。場所は、シアトル EMP (Experience Music Project)。

ここでも、なるべく外国の人と話すようにしてみた。
こんなに英語を使ったのは初めてだ。
特にヒアリングが大切だと思った。
MVP Summit 中はとにかく「時差ぼけ」がひどかった。
セミナー中に何の前触れもなく突然意識がなくなることがしょっちゅうだった。
Microsoft MVP Global Summit 2005 が終わって再び少しだけのシアトル観光へ。
先ずは、"Bellevue Square" へ。


次は、シアトルのダウンタウンにも少しだけ。

最後は、シーフードのレストランで打ち上げ。

寝不足が続いていたので、この晩は大人しく就寝。
シアトル最終日。
まずは、"Doubletree Hotel Bellevue" をチェックアウト。
飛行機まで若干時間があるので、アメリカのテレビ ドラマ "Twin Peaks" で有名だというシアトル近くのスノコルミーの滝 (Snoqualmie Falls) へ。
スノコルミーは、とても美しい滝だ。

シアトルとその近郊は、都会と自然が調和した美しいところだ。
美しい山と木々と湖がある。
もうすっかり秋で、紅葉も綺麗だ。
東京辺りと比べても随分気温が低い。

そしてシアトル空港へ。

シアトル最後の食事は Burger King のステーキ バーガーだった。

ノースウェスト航空で成田へ。
入国手続きを済ませて、成田エキスプレスに乗った。
今は品川プリンスホテル。
明日は福井へ帰る。そのまま仕事の予定。
早く妻子の顔を見たい。
10/15(土) は、VSUG (Visual Studio Users Group) のミーティングに参加すべく、新宿小田急サザンタワーへ。

一次会、二次会と多くの素晴らしい方達と技術やコミュニティの話ができて楽しかった。
調子に乗って焼酎を飲みすぎた。
大崎に一泊して翌朝帰宅。
VSUG の関連記事:

今年の 11月17日 に、あのソフトウェアが発表になる。
そう。
…いや違った。まあ、『ポケモン不思議のダンジョン 青の救助隊』も 11月17日 に発表になるんだけれども。
待ちに待った 『Visual Studio 2005』や『SQL Server 2005』が発表になるのだ。
そして、この日はイベントが開催される。
そう!
…でなくて。まあ、『産官学連携フェスティバル2005』も 11月17日 に開催されるけども。
この日は、
が開催される。
マイクロソフト株式会社社長のダレン ヒューストン氏やマイクロソフト コーポレーション CEO のスティーブ バルマー氏が話す。
私も参加予定。
| 『the Microsoft Conference 2005』 (東京会場) | ||
|---|---|---|
| 開催日時 | 11月17日(木) 9:30~18:30 (受付開始 8:30) ※ スペシャルプログラムは 11:00~18:30 |
|
| 開催会場 | 名称 | 東京プリンスホテルパークタワー |
| 住所 | 東京都港区芝公園 4-8-1 | |
そして、この日は記念すべき日でもあるのだ。
そう。それは、
でなくて。 確かに、この日 TOKIO の城島は 35歳になるけども。
この日は実は、
でもなくて。 確かに、この日は第十二回「蓮根の日」には違いないけど。
なんと、この日 Visual Studio User Group (VSUG) も発足予定なのだ。
まあ、めでたい日だ。
# またこの二日後に、 『NAgile合宿2005秋』というのも予定されている。
# というわけで、11/17~18は東京に滞在予定。

マイクロソフト株式会社「スタート・マイ・ドメイン」キャンペーンを開始
~新規ドメイン取得無料、月額換算利用料1,000円以下などの特典を提供~
例えば、KAGOYA の DOSABA は 1GB で一年目6,300円(月額525円)、二年目以降10,080円(月額840円)。
うーん、中々魅力的な価格。Windows のホスティング サービスは今まで様子見してたけど、そろそろ手を出しても良いかな。
今はロリポップで 200MB で月あたり300円だけど、Apache で Perl と PHP で、MySQL、MovableType、XOOPS という環境。
DOSABA なら IIS で ASP.NET で、SQL Server、DotNetNuke、CommunityServer という環境。
取り敢えずロリポップの方とは別に、DOSABA か cervi.jp 辺りを利用してみようかな。
ニュース:
※ Enterprise Watch の記事に比較表が載っている。

# 11月17日の続き。
Visual Studio ユーザーグループ (VSUG) が、本日オープンしました。
私は、小井土さんと一緒に、この中の「開発プロセス」フォーラムのボードリーダーを務めます。
いよいよ本日、待望の Visual Studio 2005 が発表になり、
も開催されます。
# 私もこれから東京に向かいます。
Visual Studio 2005 は、素晴らしく開発効率をあげるツールに仕上がってきています。
無料の Visual Studio 2005 Express Edition も出ますし、Team System にも期待が高まります。
Visual Studio に関心の有る方は、是非お気軽に Visual Studio ユーザーグループ (VSUG) にご参加ください。
昨日は、the Microsoft Conference 2005 へ。
| the Microsoft Conference 2005 (東京会場) | ||
|---|---|---|
| 開催日時 | 11月17日(木) 9:30~18:30 | |
| 開催会場 | 名称 | 東京プリンスホテルパークタワー |
| 住所 | 東京都港区芝公園 4-8-1 | |




Visual Studio 2005 と SQL Server 2005、 BizTalk Server 2006 のローンチ記念イベント。
夜は、「Visual Studio / SQL Server 2005 ローンチ記念コミュニティーパーティー」へ。
場所は、キハチ・銀座店。
マイクロソフトや MVP、INETA Japan、VSUG (Visual Studio User Group) の方々と再会して色々な話をした。
ジム グレイ (Jim Gray) 氏とインスタンス カメラで写真を撮って、それにサインをもらったりして、楽しかった。
その後軽く二次会へ。
# 今日も東京泊。
を書きました。
どちらの製品もとてもお薦めです。

これからVSUG (Visual Studio User Group) 設立記念セミナーに出発。
| イベント名 | VSUG設立記念セミナー |
|---|---|
| 日時 | 2005年12月3日(土) 13:00開始 18:30終了 |
| 主催 | VSUG (Visual Studio User Group) |
以下のイベントに参加した。
| イベント名 | VSUG設立記念セミナー |
|---|---|
| 日時 | 2005年12月3日(土) 13:00開始 18:30終了 |
| 主催 | VSUG (Visual Studio User Group) |
■ 内容



■ 感想など
○ 講演について
荻原 さん と 萩原 さん の講演があった。
荻原 さん の講演は、Visual Studio 2005 の新機能部分に関する Tips。
楽しい Tips が多かった。
「Ctrl キーで DataTip が透ける!」など、知らなかった Tips が結構あった。
早速今日会社で披露。
萩原 さん の講演は、ソフトウェア工学の視点からソフトウェア開発の諸問題をみる、というもの。
とても良かった。
マインドマップでメモをとりながら聴いていたのだが、ものすごい勢いで、マインドマップの枝が伸びていった。
自分の発想がどんどん広がっていくのが分かる。
問題をどう捉えればよいのか、問題に対してどういう見方ができるのか。
それについて、萩原 さん の講演ではいつも、「かくあるべき」という視点を突きつけられる。
それと対比することで、自分のものの見方について再考させられたり、新たな視点が芽生えたりするのだ。
それは、とても貴重な体験だ。
○ Lightning Talk について
私は、VSUGフォーラムリーダーとして Lightning Talk をやった。
内容は、アンケート。参加者の皆さんに、手で大きな○か×を作って答えていただいた。100人程度の参加者だったので、1/100 アンケートもやってみた。
Lightning Talk はコミュニティ イベントらしさを出すのに最適なツールの一つだと思った。一般参加者の方にもやってもらえれば更に良いと思う。
○ 懇親会などについて
コミュニティ のオフライン イベントは一方通行で終わってはつまらない。
それでは、本当につまらない。
コミュニケーションは双方向でないと。そして、フェイス トゥ フェイスが重要だ。
今回、多くの人とコミュニケーションがとれた。
イベント後の飲み会では、大きな感動を味わいさえした。
こういう体験が、私にとってとても大切だ、ということを再認識した次第だ。
下記に参加。
| 名称 | Microsoft Developers Conference 2006 |
|---|---|
| 会期 | 2006年2月2日(木) - 3日(金) |
| 会場 | パシフィコ横浜 会議センター 1F-3F |
| 主催 | マイクロソフト株式会社 |
| 参加費 | 一般価格: ¥49,800、特別価格: ¥40,000 (事前登録制) |
参加内容は以下の通り。
| 2/2 | 9:00-10:40 | 基調講演 | マイクロソフト コーポレーション デベロッパー & プラットフォーム エバンジェリズム グループ コーポレート バイス プレジデント サンジェイ パラササラシー、マイクロソフト コーポレーション Windows クライアント プラットフォーム & ドキュメント ゼネラル マネージャ マイケルウォーレント、マイクロソフ トコーポレーション インフォーメーション ワーカー ビジネス グループ ゼネラル マネージャ 沼本 健 | |
| 2/2 | 10:55-11:55 | ゼネラル セッション | マイクロソフト コーポレーション Developer Division のテクニカル フェロー アンダース ヘルスバーグ | |
| 2/2 | 13:15-14:25 | T3-206 | Windows Communication Foundation ("Indigo") 概要 | マイクロソフト株式会社 伊藤 英豪 |
| 2/2 | 14:40-15:50 | T2-324 | 次世代開発基盤技術 "Software Factories" Part 1 ~ その概念の中心 | マイクロソフト株式会社 成本 正史 |
| 2/2 | 16:05-17:30 | MVP Private Roundtable | Sharing info & Discussion about Windows Presentation Foundation (Avalon) | マイクロソフト コーポレーション Windows クライアント プラットフォーム & ドキュメント ゼネラル マネージャ マイケルウォーレント |
| 2/2 | 17:30-18:40 | T1-306 | Windows Presentation Foundation ("Avalon") | マイクロソフト株式会社 高橋 忍 |
| 2/2 | 19:00-21:30 | コミュニティ スペシャル セッション with Anders Hejlsberg in Microsoft Developers Conference 2006 | マイクロソフト コーポレーション Developer Division のテクニカル フェロー アンダース ヘルスバーグ | |
| 2/3 | 9:00-10:30 | 基調講演 | マイクロソフト コーポレーション デベロッパー & プラットフォーム エバンジェリズム グループ コーポレート バイス プレジデント サンジェイ パラササラシー | |
| 2/3 | 10:50~11:55 | T1-301 | Windows Presentation Foundation ("Avalon") による新しいアプリケーション ユーザー インターフェースの実現 | マイクロソフト株式会社 高橋 忍 |
| 2/3 | 13:15-14:25 | T2-501 | 次世代開発基盤技術 "Software Factories" Part 2 ~ 開発手順の実践 | マイクロソフト株式会社 萩原 正義 |
| 2/3 | 14:40~15:50 | T2-314 | リレーショナル データのための LINQ | マイクロソフト株式会社 荒井 省三 |
| 2/3 | 16:05~17:15 | T1-307 | Windows Presentation Foundation ("Avalon") : 先進的なドキュメント ワークフロー コンテンツのセキュリティ、ビュー、印刷 | マイクロソフト プロダクト ディベロップメント リミテッド 佐藤 大樹 |
| 2/3 | 17:20-18:00 | Hands-On Lab | Windows Vista: Windows Presentation Foundation Hands-On Lab |
2/2 夜は、Turbo Pascal と Delphi、そして C# の父である Anders Hejlsberg を囲んだスペシャル セッションに参加。

参加者からの沢山の C# 関連の質問に答えていただいた。
とてもフレンドリーな方で、どんな質問にも笑顔で真剣に答えてくださった。
サイン入りの C# 本まで頂いてしまった。
Turbo Pascal と C# のファンである私としては、かなり嬉しくて舞い上がってしまった。

※ この様子はいずれレポートしたい。

マイクロソフトから、Visual Studio 2005 ACE の置物 (謎) が届いた。

※ 「Special Session with Anders Hejlsberg in Microsoft Developers Conference 2006」の続き。
MSDN に、
「レポート:コミュニティスペシャルセッション with Anders Hejlsberg in Microsoft Developers Conference 2006」
を書いた。
一色さんの記事と合わせてお読みいただけると幸いだ。

■ NDoc の紹介
Visual Studio 2005 と "NDoc" というツールを使うことで、MSDN のような HTMLヘルプ形式 (.chm ファイル) を含む様々なヘルプ形式を出力できる。
NDoc は以下で入手できる。
■ NDoc 日本語版の導入方法
NDoc 日本語版を使うには、以下のような設定を行うと良い。
※ "NDocGui exe.config" の内容
<?xml version="1.0" ?>
<configuration>
<startup>
<supportedRuntime version="v2.0.50727" />
<supportedRuntime version="v1.1.4322" />
<requiredRuntime version="v1.1.4322" />
</startup>
</configuration>
参考:
![]()
Visual Studio Team System を試している。チーム プロジェクトを試しに作ってみたのだが、消し方が分からない。不要なチーム プロジェクトが増えて困っている。
Visual Studio 2005 Team System の「チーム エクスプローラ」から「削除」を選ぶと、「チーム エクスプローラ」の一覧から消えるだけで、本当に消えてはいないようだ。 再度「Team Foundation Server に接続」しようとすると、一覧に残っている。
不要なチーム プロジェクトを本当に 消してしまうことはできないのか?
もちろん、できる。
Team Foundation Server をインストールした PC に、"TFSDeleteTeamProject.exe" というコマンド ラインから使用するツールがある。これを使って不要なチーム プロジェクトを削除することができる。
このツールは、Team Foundation Server をインストールしたフォルダを、例えば、
C:\Program Files\Microsoft Visual Studio 8\
とすると、
C:\Program Files\Microsoft Visual Studio 8\Common7\IDE\PrivateAssemblies
にあるはずだ。
この "TFSDeleteTeamProject.exe" を試しにコマンドラインから実行してみると、以下のように、このツールの使い方が表示される:
C:\Program Files\Microsoft Visual Studio 8\Common7\IDE\PrivateAssemblies>TFSDeleteTeamProject
使い方: DeleteTeamProject [/q] [/force] </TeamFoundationServer:<サーバー名>> <チーム プロジェクト名>
Team Foundation からチーム プロジェクトを削除します。このコマンドを使用するには、Team Foundation Server 管理者グループのメンバであるか、または削除するプロジェクトのプロジェクト管理者グループのメンバでなければなりません。チーム プロジェクトを削除した後、復元することはできないため、注意してこのコマンドを使用してください。
[/q] - 確認用のメッセージを表示しません。
</TeamFoundationServer:<サーバー名>> - Team Foundation Server の名前です。
[/force] - 削除できないデータがある場合でも続行します。
<チーム プロジェクト名> - プロジェクトの名前です。名前に空白が含まれる場合は引用符を使用します。
C:\Program Files\Microsoft Visual Studio 8\Common7\IDE\PrivateAssemblies>
次に、実際に "tfsserver" というサーバーの "HelloProject" というチーム プロジェクトを削除してみた例を以下に示す:
C:\Program Files\Microsoft Visual Studio 8\Common7\IDE\PrivateAssemblies>TFSDeleteTeamProject /TeamFoundationServer:tfsserver /force HelloProject
警告: チーム プロジェクトの削除操作は元に戻すことができません。チーム プロジェクトを削除しますか (Y/N)?y
Build から削除しています
完了
作業項目トラッキング から削除しています
完了
バージョン管理 から削除しています
完了
レポート サーバー ファイルを削除しています
完了
SharePoint サイトを削除しています
完了
Team Foundation コア から削除しています
完了
C:\Program Files\Microsoft Visual Studio 8\Common7\IDE\PrivateAssemblies>
これで、チーム プロジェクトを削除することができた。
ちなみに、同じことをもう一度やってみると以下のようになる:
C:\Program Files\Microsoft Visual Studio 8\Common7\IDE\PrivateAssemblies>TFSDeleteTeamProject /TeamFoundationServer:tfsserver /force HelloProject
警告: チーム プロジェクトの削除操作は元に戻すことができません。チーム プロジェクトを削除しますか (Y/N)?y
プロジェクト 'HelloProject' は、TF Server で見つかりませんでした。
C:\Program Files\Microsoft Visual Studio 8\Common7\IDE\PrivateAssemblies>
このツールを使用する場合の注意点は以下の通り:

以下に参加した。
| Japan MVP Summit 2006 | |
|---|---|
| 日時 | 2006/04/22(土) 10:00~18:30 |
| 場所 | マイクロソフト新宿本社 |
始発の飛行機に乗っていったのだが、会場へは10分遅れで到着。
参加内容は、
とても沢山の MVP の方々が参加されていた。聞いたところでは、8割を超える参加率だとか。
その後、MVP 主催の懇親会にも参加。こちらも、80名近い人が参加されていた。場所は、新宿ワシントンホテル2F「三十三間堂」。
その後、二次会まで参加。
知人の方々と久しぶりに直にお話ができたり、何人かの新たな知人ができた。
また、オンラインでお話したことがある何人かの方と、初めて対面できたこともとても嬉しかった。
しかし、この週は月曜日からずっと体調が悪かったので、この後また体調不良に。
もう復活したつもりで参加していたのだが、まだ体調が戻っていなかったらしく、自宅へ着いてから今朝まで寝込む羽目に。反省するとともに色々と凹んでいる。
# 今日は朝から病院へ行った。
VSUG DAY 2006 Summer
| |
|---|---|
| 主催 | VSUG (Visual Studio® User Group) |
| 日時 | 2006年6月13日(火)10:00~17:00 (受付 9:30) |
| 場所 | 秋葉原コンベンションホール (東京 秋葉原ダイビル 2F) |
| 定員 | 400名 |
| 参加料 | 無料 |
| 申し込み方法 | Visual Studio User Group > イベント > VSUG Day 2006 Summer より (VSUGへの会員登録が必要) |
下記に参加予定。
| Microsoft Office Developers Conference & MD3 2006 | ||
|---|---|---|
| 会期 | 2006年6月26日(月) ~ 27日(火) | |
| 会場 | 名称 | 住友スカイルーム (新宿住友ビル 47F) |
| 住所 | 東京都 新宿区 西新宿 2-6-1 新宿住友ビル | |
| Tech・Ed Yokohama 2006 | |
|---|---|
| 会期 | 2006年8月29日(火) ~ 9月1日(金) |
| 会場 | パシフィコ横浜 |
IT Media News より ― 『Microsoftの「Web2.0」と「.NET 3.0」』
上記より引用:
Microsoftは、これまでWinFXと呼んでいたデベロッパーブランドを.NET Frameworkに一本化させ、.NET Framework 3.0と改名した。また、開発者向けコミュニティーとして「MSDN Wiki」のβを開始した。
『Visual Studio 2005 Team Foundation Server 評価版』をインストールしてみた。



詳細はこれから確認だが、"β3 Refresh" では、エラーになって表示されなかったチーム ポータルのレポートがきちんと表示されているようだ。また、英語だった MSF のドキュメントが日本語化されている。その他、「セキュリティ」の権限の種類も変更されていたり、色々と変わったようだ。
関連情報:
この七月に、Micosoft MVP (Most Valuable Professional) Visual Developer - Visual C# のアワードを再受賞することができました。
ありがたいことです。
思えばこの数年、色々な方に多くの機会を与えていただきました。技術者を中心として多くの人に出会い、様々なお話を伺うことが出来ました。講演を聴かせて頂いたり、飲み会でご一緒させて頂いたり。
素晴らしい技術者の方々に接し、「この人達には、とても適わない」と感じながらも、何とか生き方・考え方を見習おう、少しでも近づこうと考えつつ送ってきた日々。そうしてきた過程こそが、私にとってかけがえのない Engineering Life であったような気がします。
皆様に感謝です。そして、今後とも宜しくお願い致します。
MVP バッグが届いた。
ベースは、"Victorinox Louvre Computer Briefcase" っぽい。
Microsoft On - 出張ワークショップ
8/2 Microsoft On - 出張ワークショップ を会社で開催。
栄えある第一回とのこと。
社内からは、予定を大幅に超える 50 人の技術者が参加して、社内の研修室は超満員。大成功だった。
わざわざ福井まで来ていただいて、きっちりとセミナーを二コマやっていただいた。ありがたいことだ。
関連記事:
『第2回 VSUG アカデミー』が開催されます。
日時: 2006年9月27日(水) 19:00~21:10 (※18:30受付)
場所: ニューオータニガーデンコート 17階 セミナールーム1
定員: 80名
参加料: 500円
福井情報技術者協会(FITEA) からのお知らせ。
10月20日(金) に福井で「Microsoft On」が開催される。
これはマイクロソフトが福井に来て出張ワークショップをやってくれるというイベントだ。
マイクロソフトの最新の開発環境や次世代の Windows である Vista における開発のセミナーで参加は無料。
| 『Microsoft On - 出張ワークショップ - 最新技術を学ぶ in 福井』 | |
|---|---|
| 日時 | 2006年10月20日(金) 13:00~17:15 |
| 場所 | ふくい産業支援センター 会議室A・B |
| 主催 | マイクロソフト株式会社 (共催: 福井情報技術者協会 |
| 内容 | 福井で「Microsoft On」による無料セミナーを開催。
|
| 講師 | マイクロソフト株式会社 デベロッパー エバンジェリスト 松崎 剛 氏 |


無事終了。
当初の予定以上に人が集まり、会場はいっぱいになった。
講師の松崎さんの話がとてもよかった。
開始前に「単なる Visual Studio 2005 の使い方の話より、.NET Framework の話を聴きたくて来られてる方が多いでしょう」とのご提案があり、前半のメニューでは、CLR や IL、アセンブリなどについての「中々 Web サイトや書籍では目に出来ないテクニカルな情報」について話していただいた。
後半は、Vista に関してデモを中心に話していただいた。
単に技術を紹介するにとどまらず、そのテクノロジーがどうして出てきて、今後どのような展望があるのかについて、松崎さん自身の考え方を聴けたのが良かった。「Web サイトや書籍で目に出来ない」生の声を聴くことができた。
コマンドライン上でのツールを使って基本的なことを説明した後で、Visual Studio 上で同じことをやってみせるやり方は分かりやすくて良かった。C# や、ASP.NET、XAML の説明のときにもメモ帳でソースを書くところから説明をしていた。共感できる。単なるドラッグ&ドロップの連続での説明だと見た目は派手だが内容に欠けるのだ。
聴衆を飽きさせずに、常に興味を引き付ける話ぶりは流石だ。参加者の方はその名調子に引き込まれていたようだ。
chikura さんと一緒に松崎さんを福井駅に送る車中で、三人でこれからの新人技術者についてなどの様々な話をした。
その後も懇親会でみっちりと話し合った。久しぶりに少し飲みすぎたが、とても楽しい一日だった。
![]() |
||
|---|---|---|
| 日時 | 日 | 2006年11月25日(土) |
| 時 | 11:00~20:00 (懇親会: 18:20~) | |
| 会場 | ベルサール神田 | |
| 主催 | VSUG (Visual Studio User Group) | |
| 受講料 | 無料 (懇親会のみ 1,000円) | |
| 詳細/申し込み | http://vsug.jp/tabid/135/Default.aspx | |
![]() |
||
|---|---|---|
| 日時 | 日 | 2006年11月10日(金) |
| 時 | 19:00~21:10 (18:30受付) | |
| 会場 | 株式会社翔泳社1Fスペース | |
| 主催 | VSUG (Visual Studio User Group) | |
| 受講料 | 無料 | |
| 詳細/申し込み | http://vsug.jp/tabid/134/Default.aspx | |
11月は以下の二つのイベントに参加予定。
| the Microsoft Conference 2006 東京会場 | |
|---|---|
| 日時 | 2006年11月15日(水) 10:15~17:20、16日(木) 10:00~18:10 |
| 会場 | 東京国際フォーラム |
| 主催 | マイクロソフト株式会社 |
| 参加費 | 無料 |
| 詳細/申し込み | http://www.event-registration.jp/events/msc06/ |
![]() |
||
|---|---|---|
| 日時 | 日 | 2006年11月25日(土) |
| 時 | 11:00~20:00 (懇親会: 18:20~) | |
| 会場 | ベルサール神田 | |
| 主催 | VSUG (Visual Studio User Group) | |
| 受講料 | 無料 (懇親会のみ 1,000円) | |
| 詳細/申し込み | http://vsug.jp/tabid/135/Default.aspx | |
先週、以下に参加した。
| 『the Microsoft Conference 2006』 東京会場 | |
|---|---|
| 日時 | 2006年11月15日(水) 10:15~17:20、16日(木) 10:00~18:10 |
| 会場 | 東京国際フォーラム |
| 主催 | マイクロソフト株式会社 |
| 参加費 | 無料 |
| 詳細 | http://www.event-registration.jp/events/msc06/ |
以下のセッションと「コミュニティ オープン ステージ」や「Windows Vista & 2007 Office system タッチ & トライ」、「パートナー ソリューション ショーケース」などに参加。
■ 所感
会場は「東京国際フォーラム」で、広過ぎてセッションからセッションへの移動が大変だった。
今回は特に SharePointServer 2007、 周りの情報収集を行った。SharePointServer と Exchange Server、InfoPath、と Office のクライアント製品の連携の部分だ。マイクロソフト松崎さんの Visual Studio を使った SharePointServer 2007 のカスタマイズの話は面白かった。
2007 への移行にあたって当面重要な鍵となりそうな、旧バージョンとの連携の話、特に SharePointService2.0 を使ったシステム (Team Foundation Server) などとの連携の話は余り聞けなかった。
一番楽しめたのは「コミュニティ オープン ステージ」だった。
以下は、会場の様子。





昨日 MSDN サブスクリプション ダウンロード サイトで、Windows Vista (x86用日本語版) の提供が開始された。
早速昨日から今日にかけて、ノートPCにインストールしてみた。
※ PC のスペックは以下の通り。
| ノートPC | DELL XPS M1210 |
|---|---|
| CPU | インテル Core Duo 2GHz |
| Memory | DDR2-SDRAM 2GB |
| HDD | SATA HDD 100GB |
| グラフィック | NVIDIA GeForce Go 7400 256MB DDR TurboCache (最大192MBをメインメモリより使用) |
| ディスプレイ | 液晶 12.1インチ 1280x800 |
ざっと触ってみた感じでは、アプリケーションはほぼ全部問題なく動くようだ。互換性に問題がありそうなアプリケーションは起動時に警告が出る。また、動作速度にも特に問題は無さそうだ。
これから、Office 2007 や Visual Studio 2005 の動作やクリーン インストールの場合など、色々と試してみるつもり。

本日、コミュプラス (COMU+) というコミュニティがスタート。
こみゅぷらす (COMU+) サイトより:
こみゅぷらす (COMU+) とは、INETA の加盟コミュニティのリーダーや Microsoft MVP などこれまでもアクティブに情報を発信してきたメンバーが集まって結成した団体です。
Windows 技術を中心に、さまざまな情報をオンライン、オフライン勉強会を通じて配信していきます。
メンバーのブログより:
先週土曜日は以下のイベントに参加。
![]() | ||
|---|---|---|
| 日時 | 日 | 2006年11月25日(土) |
| 時 | 11:00~20:00 (懇親会: 18:20~) | |
| 会場 | ベルサール神田 | |
| 主催 | VSUG (Visual Studio User Group) | |
| 受講料 | 無料 (懇親会のみ 1,000円) | |
| 詳細/申し込み | http://vsug.jp/tabid/135/Default.aspx | |


ライトニング トークスも素晴らしく、直ぐにでも役立つ知識が目白押しだった。それを5分で容赦なく切ってしまうのだから、とても贅沢だ。
昼は、何人かで「まんてん」のカレーを食べに行った。

夜は、懇親会に参加。その後スタッフの皆さんと飲みにいった。
有意義な一日だった。
前回に続き MVP Global Summit に参加予定。
| 2007 Microsoft Most Valuable Professional (MVP) Global Summit | |
|---|---|
| 期間 | 2007/03/12~15 (03/17 帰宅) |
| 場所 | ワシントン州レドモンド マイクロソフト コーポレーション |
今日届いたジャケット。

今日届いたノイズキャンセリング ヘッドホン。

※ 「3月のイベント」の続き。
これから MVP Global Summit に向けて出発。
| 2007 Microsoft Most Valuable Professional (MVP) Global Summit | |
|---|---|
| 期間 | 2007/03/12~15 (03/17 帰宅) |
| 場所 | ワシントン州レドモンド マイクロソフト コーポレーション |
MVP Global Summit 2007 を写真で振り返ってみる。
■ 一日目 (3/12)

小松空港を出発して成田へ

飛行機の窓から富士山が

Northwest航空で成田を出発

Seattle-Tacoma国際空港 (Sea-Tac) に到着

Seattle-Tacoma空港に集う日本のMVP達

Safeco Fieldへ

Safeco Field 記者会見が行われる部屋

Safeco Field グランドへの通路

Safeco Field グラウンドより

Bellevue SquareのWorld Wrappsでランチ

Bellevue SquareのCOACHで妻のバッグを購入

MVP Global Summit のレジストレーション時に受け取ったシャツ

SeattleダウンタウンのCrowne Plaza Hotelに到着

Seattleダウンタウンで見掛けた建物

Seattleダウンタウンの町並みを見ながら、日本のMVPだけのパーティの会場へ向かう

夜、翌日の会場となるSeattleダウンタウンの Washington State Convention and Trade Center の前を通る

初日の夜にビールやスナックを買った店
■ 二日目 (3/13)

Bill Gates氏のキーノート

Don Box 氏のセッション

夕方一時間だけ時間が空いたので、独りでPike Place Marketまで散歩

Pike Place Marketにて

Pike Place MarketにあるStarbucks Coffee一号店

マグカップを購入 (この後ボーイング社の航空博物館でMVPパーティ)
■ 三日目 (3/14)

三日目からの会場となるRedmondのマイクロソフトのキャンパスへ行き、先ずは朝食

昼食では、隣のテーブルに (Turbo PascalとDelphiとC#の父である) Anders Hejlsberg氏が

会場にはスナックやフルーツが沢山おいてあって自由に食べることができる

会場のスナックはどれもカロリーが高そう

飲み物は、コーヒーや紅茶の他、冷蔵庫には ROOT BEER などが用意されている

Vistaドリンクは、無糖の炭酸飲料

キャンパスでは桜が身頃だった

この日の夕方に買ったマイクロソフトのデバイス

セッションでもらったC#シャツと夜のプロダクト グループ毎のパーティでもらったTシャツとVisual Studio Team Systemのポロシャツ
■ 四日目 (3/15)

全てのセッションが終わり、昼からは最後のランチ

その日の夜、日本のMVPがシーフードレストランSalty'sに集まって行われた最後のパーティで出たSeattle名物のクラム・チャウダー

同じく蟹

Salty'sは海沿いにあって夜景が美しい
■ 四日目 (3/15)

最終日にSeattle近郊のSnoqualmie Fallsへ

NikeやEddie Bauerなどがある巨大なショッピング モールへ

Seattle-Tacoma国際空港にてBurger Kingで昼食

成田まで九時間近くと成田から一時間余りのフライトの上、無事小松空港に帰還
昨日の夕方は、『MVP Road Show 2007 金沢』に参加。
マイクロソフトと北陸三県 (福井、石川、富山) から一名ずつ計四名が参加。
全員シアトルでの MVP Global Summit 2007 に参加していた。
色々と北陸の話ができたし、新しい情報も手に入った。
# LifeCam も貰った。
以下は、久々の金沢駅前の写真 (一番下は会場となった金沢都ホテル)。




こみゅぷらすで、来月半ばに以下のイベントを開催します。
雰囲気の良い素敵な店で、食事や酒等を楽しみながら参加いただけるという画期的な技術系イベントです。
どうぞ奮ってご参加ください。
![]() |
|
|---|---|
| 主催 | VSUG (Visual Studio User Group) |
| 日付 | 2007年6月1日(金) |
| 会場 | コクヨホール (東京都港区) |
| 会費 | 無料 (VSUG 会員登録が必要) |
| テーマ | 「次期VSがやってくる」 |
| 詳細 | http://vsug.jp/tabid/151/Default.aspx |
今週の土曜日は以下の二つのイベントに参加予定。
# どちらも少ししゃべる予定。
まだ申し込みを受け付けているので、興味のある方はお早めに。
| 第16回 codeseek 勉強会 | |
|---|---|
| 日時 | 2007年5月19日(土)13:00~15:00 こみゅぷらす Community Launch イベント直前 |
| 場所 | 渋谷貸し会議室を予定 |
| 主催 | codeseek |
| 協力 | tk-engineer |
| テーマ | オブジェクト指向初心者脱出次の |
| 内容 | プログラミングのための |
| 参加費用 | ¥1,000 飲み物込 |
| 申込期限 | 2007年5月18日(金) 18:00 |
| 詳細/申込先 | こちら |
>
こみゅぷらす (COMU+) & eパウダー Vista & 2007 Office Community Launch
|
|
|---|---|
| 日時 | 2007年5月19日(土) 16:30 ~ |
| 場所 | ピンクカウ (渋谷・表参道から徒歩7分) |
| 主催/共催 | こみゅぷらす (COMU+)、eパウダ~ |
| 会費 | ¥3,500 (おつまみ ¥1,000 + ビュッフェ ¥2,500) |
| プログラム |
|
| 詳細/申し込み | 特設ページ |
この七月に、Micosoft MVP (Most Valuable Professional) Visual Developer - Visual C# のアワードを再受賞することができました。三年目になります。
お世話になっている多くの皆さんに感謝です。IT業界で知り合いうことのできた多くの皆さんとの会話が、私にとっての糧です。
今後とも宜しくお願い致します。
# よりよいIT業界を目指しましょう。
| Tech・Ed 2007 Yokohama | |
|---|---|
| 開催日 | 2007/08/21(火)~24(金) |
| 場所 | パシフィコ横浜 |
■ Birds of a Feather in Yokohama
尚、こみゅぷらすの運営スタッフが登壇するその他のセッションは以下の通りです。
| Microsoft Tech・Ed 2007 in Yokohama | ||
|---|---|---|
| 期間 | 8月21日(火)~8月24日(金) | |
| 会場 | 名称 | パシフィコ横浜 |
| 住所 | 〒220-0012 横浜市西区みなとみらい1-1-1 | |
| 電話 | 045-221-2155 | |
| TIME | ROOM | セッション ID | セッション タイトル |
| 10:30 - 12:10 | A | オープニング & キーノート | 「Dynamic IT for the People-Ready Business」 | マイクロソフト株式会社 ダレン ヒューストン マイクロソフト コーポレーション イアン マクドナルド |
| 13:50 - 15:05 | A | T3-302 | Visual Studio 2008 概要 | 近藤 和彦 |
| 15:05 - 15:25 | 会議センター 3F | Street Live! | VB6 on Vista + Vista 時代のモバイル開発環境構築 | codeseek 衣川 朋宏 (tkinugaw) / 岡本 貴浩 (こた) / 原 敬一 |
| 15:25 - 16:40 | 会議センター 3F | BOF09 | 今改めて語り合いたい。オブジェクト指向プログラミングをマスタするコツ | こみゅぷらす 小島 富治雄 / 尾崎 義尚 / 原 敬一 |
| 16:40 - 17:00 | 会議センター 3F | Street Live! | わかっていますか? 女性のキモチ。~ 事例に見る “こんな時どうする” ~ | eパウダ~ 村岡 淳子 / 山田 真由美 / 藤城 さつき |
| 17:00 - 18:15 | 会議センター 3F | BOF06 | ソフトウェア開発の問題解決のための開発者コミュニティの活用方法について | INETA Japan 小井土 亨 / 小野 修司 / 奥津 和真 |
| 18:15 - 18:35 | 会議センター 3F | Street Live! | マイクロソフト テクノロジの歴史を知ろう | こみゅぷらす 尾崎 義尚 (om (takanao)) / 小島 富治雄 (Fujiwo) |
| 18:35 - 19:50 | C | T3-402 | チーム開発のベスト プラクティス ~ Team System の実践的活用法 | 岩出 智行 |
| TIME | ROOM | セッション ID | セッション タイトル |
| 9:30 - 10:45 | C | T1-403 | Windows Communication Foundation のアーキテクチャと基本的な拡張手法 | アバナード株式会社 福井 厚 アバナード株式会社 木下 秀義 |
| 10:45 - 11:05 | 会議センター 3F | Street Live! | CTP ってなに? どう使うとうれしいの? CTP の魅力について語ろう | codeseek 大橋 宏章 (NAL-6295) / 浜津 信弘 (deep) / 原 敬一 |
| 11:05 - 12:20 | B | T3-405 | AJAX に対応した ASP.NET アプリケーション開発 | NRIラーニングネットワーク株式会社 矢嶋 聡 |
| 12:20 - 12:40 | 会議センター 3F | Street Live! | CTP ってなに? どう使うとうれしいの? CTP の魅力について語ろう | こみゅぷらす 杉山 洋一 (かるあ) / 亀川 和史 (めさいあ (kkmaegawa)) |
| 13:50 - 15:05 | E | T4-403 | Visual Studio extensions for SharePoint - 3 分でできる SharePoint 開発 | 小松 真也 |
| 15:05 - 15:25 | 会議センター 3F | Street Live! | Visual Basic 変数名 100 連発 | Tips of VB.NET 吉原 優一 (Elfaria) / 杉山 洋一 (かるあ) |
| 15:25 - 16:40 | 会議センター 3F | BOF04 | DI と AOP による業務アプリケーション開発 - Seasar.NET | 杉本 和也 / 青木 淳夫 / 福井 厚 |
| 17:00 - 18:15 | 会議センター 3F | BOF02 | ソフトウェア ファクトリ BOF ~ソフトウェア開発の明日はどっちだ!? | 酒井 達明 / 森屋 英治 / 福井 厚 / マイクロソフト株式会社 DPE/エバンジェリスト 野村 一行 |
| 18:35 - 20:15 | パシフィコ横浜 展示ホール B | Community & Networking | Tech・Ed 2007 Attendee Party INETA Japan ブース |
| TIME | ROOM | セッション ID | セッション タイトル |
| 9:30 - 10:45 | A | オープニング & キーノート | アプリケーション アーキテクトを目指すあなたに贈るスペシャル セッション 「LINQ to the Future ~ LINQ が創る次世代型データ処理技術 ~」 | 赤間 信幸 |
| 11:00 - 12:00 | INETA Japan リーダー・ミーティング |
| 12:20 - 12:40 | 会議センター 3F | Street Live! | C++、C++ / CLI、C# 適材適所の BOF の紹介 | えムナウのプログラミングのページ 児玉 宏之 (えムナウ) / 高萩 俊行 (とっちゃん) / 福田 文紀 (επιστημη) |
| 12:20 - 13:50 | ヨコハマ グランド インターコンチネンタルホテル 3F ボールルーム / パシフィック / ベイビュー | Community & Networking | PeerTalk Lunch |
| 13:50 - 15:05 | Hands-on2 | H-311 | ASP.NET AJAX による Web アプリケーション開発概要 |
| 13:30 - 13:50 | 会議センター 3F | Street Live! | ダイナミック言語のおもしろさ | わんくま同盟 中 博俊 / 大沼 安正 (usay) / 松下 和貴 (恣意の) |
| 15:25 - 16:40 | 会議センター 3F | BOF14 | .NET Framework 3.0 をこう使いたい | わんくま同盟 中 博俊 / 露木 敏博 / 森 博之 |
| 17:00 - 18:15 | 会議センター 3F | BOF07 | C++、C++ / CLI、C# 適材適所 | えムナウのプログラミングのページ 児玉 宏之 / 高萩 俊行 / 福田 文紀 |
| 18:35 - 19:50 | E | T4-405 | Microsoft SharePoint Products and Technologies 2007 におけるワークフロー : 開発者向け詳説 | 松崎 剛 |
| 20:00 - 20:30 | INETA Japan リーダー・ミーティング |
| - | INETA Japan 懇親会 |
| TIME | ROOM | セッション ID | セッション タイトル |
| 9:30 - 10:45 | 会議センター 3F | BOF17 | VSUG オフライン フォーラム | VSUG 藤城 さつき / 社本 明弘 / 小野 雄太郎 |
| 10:45 - 11:05 | 会議センター 3F | Street Live! | コミュニティへの参加とそのベネフィット | Culminis デイブ サンダース / サンジェイ シェティ |
| 11:05 - 12:20 | A | T3-301 | .NET における動的言語への取り組み | 荒井 省三 |
| 13:30 - 13:50 | 会議センター 3F | Street Live! | ガジェットで VSUG しよう! ~ <VSUG Watcher>ガジェットのご紹介 ~ | VSUG : Visual Studio ユーザーグループ 井上 章 |
| 13:50 - 15:05 | Hands-on1 | H-208 | 私もできた! Microsoft Office SharePoint Server 2007 の標準機能の活用 |
| 15:05 - 15:25 | 会議センター 3F | Street Live! | ソフトウェア開発の自働化について | 日本 XP ユーザ会 (XPJUG) 小井土 亨 / 福井 厚 |
| 15:25 - 16:40 | D | T3-403 | ジニアス平井の Visual Studio Team System のススメ | ジニアス平井 (平井 昌人) |
| 16:40 - 17:00 | 会議センター 3F | Street Live! | Windows Vista の隠れた (?) 目玉、IIS 7.0 を使おう | ASP++ 高須 淳一 (Moo) / 長田 直樹 (ナオキ) |
| 17:00 - 18:15 | Hands-on2 | H-309 | LINQ を活用したデータ アクセス プログラミング |
| 19:30 - | 第三回平井さんを囲む会 |

明日からいよいよ Tech・Ed Yokohama に参加予定です。
「Tech・Ed 2007 Yokohama」
開催日: 2007/08/21(火)~24(金)
場所: パシフィコ横浜
私は、主に以下の二つに登壇予定です。どうぞ奮ってご参加ください。
■ Birds of a Feather in Yokohama
・8/21(火) 15:25-16:40
BOF09: 『今改めて語り合いたい。オブジェクト指向プログラミングをマスタするコツ』
○ こみゅぷらす
・8/21(火) 18:15-18:35
『マイクロソフト テクノロジの歴史を知ろう』
尚、こみゅぷらすの運営スタッフが登壇するその他のセッションは以下の通りです。
○ こみゅぷらす
・8/22(水) 12:20-12:40
『CTP ってなに? どう使うとうれしいの? CTP の魅力について語ろう』
○ codeseek
・8/21(火) 15:05-15:25
『VB6 on Vista + Vista 時代のモバイル開発環境構築』
・8/22(水) 10:45-11:05
『「読みやすいコード」とは?』
○ ASP++
・8/24(金) 16:40-17:00
『Windows Vista の隠れた (?) 目玉、IIS 7.0 を使おう』
○ Tips of VB.NET
・8/22(水) 15:05-15:25
『Visual Basic 変数名 100 連発』
また、その他関連コミュニティの BoF は以下の通りです。こちらにも顔を出したいと考えております。
○ アイネタ ジャパン
・8/21(火) 17:00-18:15
BOF06: 『ソフトウェア開発の問題解決のための開発者コミュニティの活用方法について』
○ VSUG (Visual Studio User Group)
・8/24(金) 09:30-10:45
BOF17: 『VSUG オフライン フォーラム』
※ 「Microsoft Tech・Ed 2007 in Yokohama」 の続き。
以下の通り Tech・Ed に参加した。
| Microsoft Tech・Ed 2007 in Yokohama | ||
|---|---|---|
| 期間 | 8月21日(火)~8月24日(金) | |
| 会場 | 名称 | パシフィコ横浜 |
| 住所 | 横浜市西区みなとみらい1-1-1 | |
| DAY | TIME | ROOM | セッション ID | セッション タイトル | スピーカー |
|---|---|---|---|---|---|
| 8月21日(火) | - 15:25 | 控え室 | こみゅぷらす BoF 準備 | ||
| 15:25 - 16:40 | 会議センター 3F | BOF09 | 今改めて語り合いたい。オブジェクト指向プログラミングをマスタするコツ | こみゅぷらす 小島 富治雄 / 尾崎 義尚 / 原 敬一 | |
| 16:40 - 17:00 | 会議センター 3F | Street Live! | わかっていますか? 女性のキモチ。~ 事例に見る “こんな時どうする” ~ | eパウダ~ 村岡 淳子 / 山田 真由美 / 藤城 さつき | |
| 17:00 - 18:15 | 控え室 | こみゅぷらす Street Live! 準備 | |||
| 18:15 - 18:35 | 会議センター 3F | Street Live! | マイクロソフト テクノロジの歴史を知ろう | こみゅぷらす 尾崎 義尚 (om (takanao)) / 小島 富治雄 (Fujiwo) | |
| 18:15 - 20:00 | 控え室 | Community Happy Hour | |||
| 8月22日(水) | 9:30 - 10:45 | C | T1-403 | Windows Communication Foundation のアーキテクチャと基本的な拡張手法 | アバナード株式会社 福井 厚 / 木下 秀義 |
| 10:45 - 11:05 | 会議センター 3F | Street Live! | 「読みやすいコード」とは? | codeseek 大橋 宏章 (NAL-6295) / 浜津 信弘 (deep) / 原 敬一 | |
| 11:05 - 12:20 | B | T3-405 | AJAX に対応した ASP.NET アプリケーション開発 | NRIラーニングネットワーク株式会社 矢嶋 聡 | |
| 12:20 - 12:40 | 会議センター 3F | Street Live! | CTP ってなに? どう使うとうれしいの? CTP の魅力について語ろう | こみゅぷらす 杉山 洋一 (かるあ) / 亀川 和史 (めさいあ (kkmaegawa)) | |
| 12:30 - 13:40 | スペシャル セッション | マイクロソフトの SOA と .NET Framework 3.X の適用について | Microsoft Corporation Vittorio Bertocci | ||
| 14:00 - 15:00 | 控え室 | VSUG BoF 打ち合わせ | |||
| 15:05 - 15:25 | 会議センター 3F | Street Live! | Visual Basic 変数名 100 連発 | Tips of VB.NET 吉原 優一 (Elfaria) / 杉山 洋一 (かるあ) | |
| 15:25 - 16:40 | 会議センター 3F | BOF04 | DI と AOP による業務アプリケーション開発 - Seasar.NET | 杉本 和也 / 青木 淳夫 / 福井 厚 | |
| 17:00 - 18:15 | 会議センター 3F | BOF02 | ソフトウェア ファクトリ BOF ~ソフトウェア開発の明日はどっちだ!? | 酒井 達明 / 森屋 英治 / 福井 厚 / マイクロソフトト 野村 一行 | |
| 18:35 - 20:15 | パシフィコ横浜 展示ホール B | Community & Networking | Tech・Ed 2007 Attendee Party Ask The Communiry Experts! | ||
| 8月23日(木) | 9:30 - 10:45 | B | オープニング & キーノート | アプリケーション アーキテクトを目指すあなたに贈るスペシャル セッション 「LINQ to the Future ~ LINQ が創る次世代型データ処理技術 ~」 | マイクロソフト株式会社 赤間 信幸 |
| 11:00 - 12:00 | INETA Japan リーダー・ミーティング | ||||
| 12:20 - 13:50 | ヨコハマ グランド インターコンチネンタルホテル 3F | Community & Networking | PeerTalk Lunch | ||
| 13:30 - 13:50 | 会議センター 3F | Street Live! | ダイナミック言語のおもしろさ | わんくま同盟 中 博俊 / 大沼 安正 (usay) / 松下 和貴 (恣意の) | |
| 13:50 - 15:05 | Hands-on2 | H-311 | ASP.NET AJAX による Web アプリケーション開発概要 | NRIラーニングネットワーク株式会社 矢嶋 聡 | |
| 15:25 - 16:40 | スペシャル セッション | Microsoft Corporation 波村 大悟 | |||
| 17:00 - 18:15 | 会議センター 3F | BOF07 | C++、C++ / CLI、C# 適材適所 | えムナウのプログラミングのページ 児玉 宏之 / 高萩 俊行 / 福田 文紀 | |
| 18:35 - 19:50 | E | T4-405 | Microsoft SharePoint Products and Technologies 2007 におけるワークフロー : 開発者向け詳説 | マイクロソフト株式会社 松崎 剛 | |
| 20:00 - 21:00 | INETA Japan リーダー・ミーティング | ||||
| 21:00 - | INETA Japan 懇親会 | ||||
| 8月24日(金) | 9:30 - 10:45 | 会議センター 3F | BOF17 | VSUG オフライン フォーラム | VSUG
: Visual Studio ユーザーグループ 藤城 さつき / 社本 明弘 / 小野 雄太郎 |
| 10:45 - 11:05 | 会議センター 3F | Street Live! | コミュニティへの参加とそのベネフィット | Culminis 佐藤 千佳 | |
| 11:05 - 12:20 | A | T3-301 | .NET における動的言語への取り組み | マイクロソフト株式会社 荒井 省三 | |
| 13:30 - 13:50 | 会議センター 3F | Street Live! | ガジェットで VSUG しよう! ~ <VSUG Watcher>ガジェットのご紹介 ~ | VSUG : Visual Studio ユーザーグループ 井上 章 | |
| 13:50 - 15:05 | Hands-on1 | H-208 | 私もできた! Microsoft Office SharePoint Server 2007 の標準機能の活用 | ||
| 15:05 - 15:25 | 会議センター 3F | Street Live! | ソフトウェア開発の自働化について | 日本 XP ユーザ会 (XPJUG) 小井土 亨 | |
| 15:25 - 16:40 | D | T3-403 | ジニアス平井の Visual Studio Team System のススメ | マイクロソフト株式会社 ジニアス平井 (平井 昌人) | |
| 16:40 - 17:00 | 会議センター 3F | Street Live! | Windows Vista の隠れた (?) 目玉、IIS 7.0 を使おう | ASP++ 高須 淳一 (Moo) / 長田 直樹 (ナオキ) | |
| 17:00 - 18:15 | Hands-on2 | H-309 | LINQ を活用したデータ アクセス プログラミング | NRIラーニングネットワーク株式会社 矢嶋 聡 | |
| 19:30 - | 横浜中華街 | 第三回平井さんを囲む会 | |||
マイクロソフトの「私が日本の MVP ~コミュニティの達人~」というサイトに載りました。
| コミュニティ勉強会に参加しよう~第 1 回・東京編 (アイネタ ジャパン & マイクロソフト共催) | |
|---|---|
| 日時 | 9月29日(土) 13:00 -18:00 |
| 場所 | マイクロソフト株式会社 新宿本社 小田急サザンタワー 5F セミナールーム |
| 主催 | アイネタ ジャパン、マイクロソフト株式会社 |
| 参加費 | 無料 (事前登録制) |
| セッション内容及び申込み | こちら |
今日は、以下に参加する。
| REMIX07 TOKYO | |
|---|---|
| 日時 | 9/19(水) 10:00~18:15 (REMIX Night: 参加者パーティー 18:15~20:00) |
| 場所 | 東京国際フォーラム |
| 主催 | マイクロソフト株式会社 |
| 参加費 | カンファレンス ¥5,000、展示 無料 |
| セッション内容 | こちら |

"Sandcastle" は、、ソースコードの XMLコメントから、ドキュメントを自動生成してくれるツールで、同様のツールであり .NET 2.0 への対応をやめてしまった NDoc の後継だ。
MSDN のような HTML Help (*.chm) を作ることができ、.NET 2.0に対応している。
コマンドライン ツールであるが、"Sandcastle Help File Builder" のような GUI もある。
"Sandcastle" の最新版である "September 2007 Community Technology Preview (CTP)" が 10/1 に、出、10/4 に "Sandcastle Help File Builder" の最新版である "1.6.0.0" が出た。
早速使ってみた。





(*1) 古いバージョンの Sandcastle がインストールされていた場合は、先にアンインストールする必要がある。
(*2) デフォルトのパスにインストールしないと 4 の手順に失敗する。
(*3) Vista の場合、権限が足りなくて実行に失敗することがある。その場合は、"Sandcastle Help File Builder" をインストールしたフォルダ内の "BuildReflectionData.bat" を「管理者として実行」する。
(*4) 新バージョンでは、"Project Properties" の "Help File" の "Language" の選択肢に「日本語(日本)」が出て来ない。
(*5) この時 public なクラスがなかったり XML コメントがないと、ビルドエラーになる。
(*6) 日本語に十分対応していないため、左側のペインのフォントの設定が不適切で文字化けを起こすことがある。
続きを読む "Sandcastle - September 2007 Community Technology Preview (CTP)" »
本日社内で開催。
Microsoft On - 出張ワークショップ
『開発者テストからはじめるチーム開発の実践 Visual Studio Team System の活用エッセンス』
講師: マイクロソフト デベロッパーエバンジェリスト 長沢 智治氏
開催日時: 2007年10月17日 13時00分~16時00分
受講者数: 31名
まとまっていて、とても分かり易いお話だった。感謝。
印象に残った点や思ったことを以下に箇条書き。
![]() |
||
|---|---|---|
| 東京会場 | 日付 | 2007年12月8日(土) |
| 会場 | ベルサール西新宿 1Fホール | |
| 大阪会場 | 日付 | 2008年1月19日(土) |
| 会場 | 新梅田研修センター | |
| 会費 | 無料(VSUG 会員登録が必要となります) | |
先週の金曜日、以下のイベントに参加してきた。
| 『マイクロソフト デベロッパー フォーラム -Microsoft's 2020 vision of technology-』 | |
|---|---|
| 日時 | 2007年11月9日(金) 11時15分~13時30分 |
| 会場 | ホテル ニューオータニ 東京 |
会場には、マイクロソフトの人たちや、Microsoft MVP 等の開発者、学生、プレスの方など何十人かの人たちが集まっていた。
やがて、明るい照明のもと、本日の主役であるマイクロソフト最高経営責任者 (CEO) スティーブ バルマー氏が登場した。

そして彼のプレゼンテーションが始まった。"Microsoft's 2020 vision of technology" ということで、「Software + Services」をメイン テーマに、今後十年位のマイクロソフトのビジョンが語られた。
その後は、質問タイムとなった。予め寄せられた質問やその場での質問にバルマー氏が答えてくれるのだ。

私も、その場で思い付いた質問をしてみた。「IT技術者はこれから、多くの多能工が必要とされていくのか、それとも分業化が益々進むのか、どちらだと考えているか?」というような質問をした (勿論日本語で)。バルマー氏が答えてくれた。「分業化が進むだろう…」(勿論英語で)。
答えてくれている数分間、彼とはずっと目が合った儘だった。バルマー氏の話を聴くのはこれが三回目だろうか。でもこんなに間近で見つめ合ったのは初めてだ。目力 (めぢから) を感じたというか、なんとも迫力があった。とてもエネルギッシュに話す人だ。
というわけで、生バルマーを堪能した。ただ、一ドル札に氏のサイン (謎) をもらえなかったのがちょっと残念だった。
その後、インタビューを受けたり、懇親会でマイクロソフトの人や他の MVP の人と話をしたりした。
尚、このイベントの模様は、近日以下の場所でWebキャストにて公開されるそうだ。ちょっと楽しみだ。
以下に参加してきた。
| MSDN オフラインセミナー 「.NET Framework における次世代データアクセステクノロジー概要」[北陸編] |
||
|---|---|---|
| 日時 | 日 | 2007年11月28日 |
| 時 | 13:30~16:30 | |
| 場所 | 住所 | 石川県金沢市 金沢パークビル 11F |
| 会場 | マイクロソフト株式会社 北陸支店 セミナールーム | |
| 講師 | マイクロソフト株式会社 デベロッパーエバンジェリスト 小高太郎氏 | |
久々に訪れた金沢。新しくできたマイクロソフト株式会社 北陸支店を初めて訪れた。
素晴らしいセミナーだった。
小高氏の話は、技術の背景から技術の要点、そして適用範囲まで、とても纏まっていて、技術の本質を見ることのできる分かりやすいものだった。また、沢山のデモがあって、かなり具体的に知ることができた。
Tech・Ed でも同内容を扱ったセッションは結構あったが、今回はそれをより深いところまで学ぶことができたようだ。
新しい ADO.NET について包括的に理解することができたように思う。
特に概念データモデルに関して、
とすることで、随分すっきりと「意図したことだけが書ける」ものだと感嘆したし、この一連のデモが目の前でいとも簡単に動いたときには深い感動を覚えた。
概念データモデルの考え方は、私が初めてデータベース プログラミングを学んだときに感じた、「ロジック部分の設計モデルとデータベースの設計モデルとの間を埋めるものが経験から来る暗黙知のようなものでしかないことからくる違和感」を解消してくれた気がした。
そしてまた、この概念モデルを書くための DSL (ドメイン特化言語) が追加され、それをまたサポートするツールが追加されたことが面白いと感じた。 用途に応じて DSL を使い分ける、ということの意図が段々と判ってきたような気がした。
余談だが、これらのDSL は右脳人間向きかも知れない、とか思った。
モデルを、より包括的に且つよりグラフィカルに表現するのは、左脳人間より右脳人間の方が得意とするところかも知れない。とすると、これまでのプログラミング言語は論理の組み立ての得意な左脳人間が操るものだったのが、今後の幾つかの新たなパラダイムから来ている言語に関しては、寧ろ右脳人間が得意とするようになるのかも知れない。
帰りに、「Power To The PRO」と書かれたカップヌードルRefillを頂いた (二個目)。
こみゅぷらすのスタートから今日で一年。新しい年度がスタートした。
運営スタッフは、大分増えて、現在 14名。
やったこと (オフライン):
今年度も色々とゆるい感じでやることになりそう。
なんか届いた。
早速ダウンロード。
慌てて、『VS 2008 最終リリースのインストール前にVS 2008ベータ2をアンインストールする方法』をやって、インストール。
また、"Microsoft Silverlight Tools Alpha for Visual Studio 2008 Beta 2" をアンインストールして、"Microsoft Silverlight 1.1 Tools Alpha for Visual Studio 2008" をダウンロード。
参考:
「第23回 codeseek勉強会 & こみゅぷらすと codeseek の忘年会 2007」への参加を、ざわざわニュースで伝えてみるテスト。
日帰りで、「VSUG DAY 2007 Winter 大阪」に行ってきた。
飛び入りで Silverlight のライトニングト-クスまでやらせてもらった。
懇親会と二次会にも出て、幸せな時を過ごせた。参加して良かった。
# 行きは高速バス、帰りは「急行ゆきぐに」で帰ってきたのだが、乗り過ごしたりして、往復に東京との往復に通常掛かる時間の4倍位掛かってしまった。距離でいうと半分位の筈なのだが。
「Source Code Outliner PowerToy for Visual Studio 2008」が公開されたので試した。
下記からダウンロードしてインストールすることで、Visual Studio の拡張機能として使うことができる。
開いているソースコードの概要 (クラスやメソッド、プロパティ等) がツリー状に表示される。

ソースコードの全体での現在位置を把握しながら、あちこちにジャンプしながら閲覧したり編集したりするのには、便利だと感じた。
.NET Framework ライブラリのソース コードが部分的に利用可能になったのでやってみた。
これは、Visual Studio 2008 からデバッグ時に .NET Framework ライブラリのソース コードが参照できるようになったものだ。
詳しい手順は、以下に載っている (英語)。
上記の手順によれば、先ず、Visual Studio 2008 QFE という Hotfix をインストールすることになっている。
だが、これは「このソフトウェア更新の対象製品はこのコンピュータにインストールされていません。」というエラーが出てできなかったのでやらなかった。

以降、手順通りでうまく行った。



例.
// Source Code by C#
static string 大字への變換(string 亞剌比亞數字)
{
亞剌比亞數字 = 亞剌比亞數字.Replace(",", "");
ulong 數;
if (!ulong.TryParse(亞剌比亞數字, out 數))
throw new ArgumentException();
var 數表現 = new[] { "", "壹", "貳", "參", "肆", "伍", "陸", "柒", "捌", "玖" };
var 小位表現 = new[] { "", "拾", "佰", "阡" };
var 大位表現 = new[] { "", "萬", "億", "兆", "京" };
var 大位表現使用濟み = new[] { false, false, false, false, false };
var 大字 = string.Empty;
for (var 位 = 0; 數 != 0ul; 數 /= 10ul, 位++) {
var 或る位の數 = (int)(數 % 10ul);
if (或る位の數 != 0) {
var 大位 = 位 / 4;
if (大位 >= 大位表現.Length)
throw new OverflowException();
if (!大位表現使用濟み[大位]) {
大字 = 大位表現[大位] + 大字;
大位表現使用濟み[大位] = true;
}
大字 = 數表現[或る位の數] + 小位表現[位 % 4] + 大字;
}
}
return 大字.Replace("貳拾", "廿").Replace("參拾", "卅");
}以下の無料勉強会を福井でやります。奮ってご参加ください。
| 『C#2.0&3.0勉強会』 | |
|---|---|
| 開催日 | 2008年3月12日(水) |
| 開催時間 | 13:30~17:30 (受付開始 13:00) |
| 会場 | ふくい産業支援センター ふくい産業支援センター パソコン実習室B |
| 参加費 | 無料 |
| 主催 | 福井情報技術者協会 (FITEA) |
| 内容 | C#2.0 や 3.0 で追加された最新言語仕様を用いて、品質が高く保守しやすいきれいなコードを書くための技術を学びます (例: yield、ラムダ式、クロージャ、LINQ等)。備え付けのPCを使った実習も予定しています。 |
| 詳細/申込先 | http://fitea.org/?p=74 |
以下の無料勉強会が福井で開催されます。奮ってご参加ください。
『コミュニティ勉強会に参加しよう~第4回・福井編』 |
|
|---|---|
| 開催日 | 2008年3月29日(土) |
| 開催時間 | 13:00~18:00 (受付開始 12:30) |
| 会場 | 福井県中小企業産業大学校 特別教室 (福井県福井市下六条町16-15) |
| 参加費 | 無料 |
| 主催/共催 | アイネタ ジャパン、マイクロソフト/福井情報技術者協会 (FITEA) |
| 内容 | コミュニティ支援団体であるNPO法人アイネタ ジャパンとマイクロソフトが共催で勉強会を開催します。2007 年東京、福岡、2008 年は岡山に引き続き 4 回目の開催となります。講師には各コミュニティで活躍されている方とマイクロソフトのエバンジェリストが登場し、最新製品・テクノロジに関する情報やいま話題のトピックなど、通常のマイクソロフト イベントとは違った視点から解説します。 |
| 詳細/申込先 | http://msevents.microsoft.com/CUI/EventDetail.aspx?EventID=1032371939&Culture=ja-JP |
福井で、.NET の無料セミナーを開催します。奮ってご参加ください。
| コミュニティ勉強会に参加しよう ~第4回・福井編 | |
|---|---|
| 主催/共催 | アイネタ ジャパン、マイクロソフト、福井情報技術者協会 (FITEA) |
| 内容 | コミュニティ支援団体であるNPO法人アイネタ ジャパンとマイクロソフトが共催で勉強会を開催します。2007年東京、福岡、2008年岡山に引き続き4回目の開催となります。講師には各コミュニティで活躍されている方とマイクロソフトのエバンジェリストが登場し、最新製品・テクノロジに関する情報やいま話題のトピックなど、通常のマイクソロフト イベントとは違った視点から解説します。 |
| 詳細/申込先 | http://msevents.microsoft.com/CUI/EventDetail.aspx?EventID=1032371939&Culture=ja-JP |
FITEA の担当セッションで、5分間のトークス (.NET 関係ならなんでも O.K.) をして頂ける方を募集します。
ご協力頂ける方・興味のある方は、info@fitea.org までご連絡ください。
明日より 2008 Microsoft MVP Summit に参加すべくシアトル及びレドモンドのマイクロソフト本社へ。
# ちなみにチベット仏教最高指導者ダライ・ラマ14世も今シアトルに滞在中の筈。
VSUG にコラムとして書きました。

皆様こんにちは。春暖の候如何お過ごしでしょうか。
新人の方が入ってこられ、活気があふれている職場も多いかと想像しています。
さて今回は、私が先週参加してまいりました "2008 MVP Global Summit" についてご紹介したいと思います。
| 2008 MVP Global Summit | |
|---|---|
| 参加者 | Microsoft MVP ※ Microsoft MVP についてご存じない方は、こちらをご参照ください。 |
| 開催期間 | 2008年4月14日(月)~17日(木) |
| 場所 | 米国ワシントン州
|
2008 MVP Global Summit というのは、年に一度位の頻度でマイクロソフト本社とその近くで開かれるイベントです。
今回は、日本から75名のMVP 、全世界では 1700 名を超えるMVPが参加し、マイクロソフトの製品グループや他のMVPとの直接の交流を行いました。
また、数百の技術セッションが行われNDA(Non-Disclosure Agreement=秘密保持契約)の元、様々な技術情報が提供されました。
NDAの関係でセッションの内容はお知らせできませんけれど、シアトルの様子などをレポートしてみたいと思います。
私が参加したセッションの会場は三箇所です。
先ずは、Washington State Convention & Trade Center。
・
Washington State Convention & Trade Center の外観。
・
Summit 会場入り口。
・
Summit 会場入り口の立て看板。

・
多くの MVP が会場前に集っていました。
・
キーノート会場。

・
キーノートの様子。
今年のキーノートは、マイクロソフトのCEOのスティーブ・バルマー氏とチーフ ソフトウェア アーキテクトのレイ ・オジー氏でした。
スティーブ・バルマー氏の話はとても熱く、迫力満点で圧倒されるものでした。
二つ目の会場は、レドモンドのマイクロソフト キャンパスです。
技術系のセッションは主にここで行われました。




・
会場には、様々な高カロリーそうなものやコーヒーなどが置いてあって、セッションの合間に楽しむことができます。

・
セッションの様子。
セッションは基本的に英語のみです。
しかもセッションは、日本の多くの場合と異なり、スピーカーの話を大人しく座って聴いている、という形式ではありません。
参加者が MVP ばかり、ということもあるでしょうけれど、聴衆から頻繁に質問の手が挙がります。そして、スピーカーとの活発なやり取りがはじまります。
筆者は今回三回目の参加となるのですが、今回はディスカッション形式のセッションがとても多く、聴き取るのが難しいものだらけでした。
一応 iKnow などを利用して、少しだけアリングを鍛えてきたつもりでした。しかし、ディスカッションが始まると、資料も図解も何もないところで英語のネイティブなスピーカーが中心となって早口で話し、論点も次々と変化していきます。そのため、全然話に付いていくことができませんでした。無念でした。
三つ目の会場は、会期中私が宿泊したホテルシェラトン シアトルです。
朝食の一部はここで提供されました。

・
シェラトン シアトルでの朝食風景。
夜毎にはパーティが開催されました。
二日目夜のプロダクト グループ毎のディナーでは、他国の C# のMVP の方と話をすることができました。
三日目のアテンディー パーティは、Experience Music Project という場所で行われました。
世界中の MVP の方がいて、様々な交流がありました。
また、最後の夜には、日本から参加している MVP だけのディナーがあり、日本の MVP とも交流しました。
次に、シアトルの紹介をしてみたいと思います。
シアトルは米国ワシントン州最大の都市です。ワシントン州はアメリカ西海岸最北部の州で、カナダとの国境があります。
シアトルは、ワシントン湖やレーニエ山など、美しい自然に囲まれた街です。治安も良いようで、とても良い場所であるように感じました (但し消費税率が約9%でした)。
ちなみに、日本との時差は16時間でした。前回ほどではありませんでしたが、時差ぼけになり、セッション中に気を失うように眠りに落ちてしまうこともありました。日本の家族と会話をするために、Skype のテレビ電話機能を使っていたのですが、家族の夕食時に掛けるためには、午前2時から5時の間に電話する必要がありました。
シアトルは一年を通じて穏やかな気候だということでした。4月の気温は摂氏6度から14度くらい。夏以外には雨の降る日が多いことで知られているそうです。
Summit 中も毎日のように雨が降っていました。また、日本よりもかなり肌寒く感じました。
但し、シアトルの雨は小雨が多く、傘を使うことはありませんでした。日本での服装に、撥水性のウィンドウ ブレーカーを羽織ると丁度良い感じでした。
シアトルとその近郊には、マイクロソフトをはじめとして、ボーイング、スターバックス、Amazon.comなどがあります。
マイクロソフトの本拠地があるのは、シアトル郊外 (東側) のレドモンドという町です。ここには、とても広い敷地内に沢山の建物があって、「マイクロソフト キャンパス」と呼ばれています。
合間に少しシアトル観光もしてきました。写真でレポートしてみます。
・
まずは、Northwest航空でSeattle-Tacoma空港 (通称SEA-TAC空港)に到着したところから。
・
メジャーリーグのシアトル マリナーズには、現在イチロー選手や城島選手が在籍しています。この写真は、イチローが打席にいるところ。

・
シアトルの市場パイク・プレイス・マーケットは代表的な観光スポット。新鮮な魚介類を中心に様々なものが売られています。

・
パイク・プレイス・マーケットのシンボル。「レイチェル」という豚のブロンズ像。

・
パイク・プレイス・マーケットの果物店。

・
パイク・プレイス・マーケットには魚屋が沢山あります。

・
スターバックス一号店もパイク・プレイス・マーケットにあります。看板の色は緑ではなく茶色。

・
シアトル ダウンタウンのウエスト レイク センターというショッピング センター。
・
ウエスト レイク センターの地下にはダイソーがありました。品揃えは日本と余り変わりませんでした。

・
シアトルはスターバックスコーヒー発祥の地。街の到るところにスターバックスをはじめとしたシアトル コーヒーの店があります。

・
丁度ダライ・ラマ14世がシアトルに滞在中でした。
ワシントン州はワインの産地としても知られていて、とても美味しいワインを楽しむことができました。
レッドフックブルワリーをはじめとしたシアトルの地ビールもとても美味しく連日堪能しました。

英語漬けの毎日でしたが、とても貴重な体験でした。
次のようなことを学べたような気がしています。
やはり、英語力不足のためにコミュニケーションが不十分になってしまうのは、とても歯痒くつらい経験でした。
直接のコミュニケーションはもちろん、オンラインでの海外との情報のやりとりも英語が必要です。
エンジニアとして、英語を使う機会をもっと増やしていこう、と決意を新たにした次第です。
今週末は、以下のイベントに参加。
私は、VSUG (Visual Studio User Group) 担当のセッション 1-1 「Visual Studio 2008による開発環境・プログラミングの進化」で、LINQ周りのプログラミングの話をする。
噂によると、数百名の参加登録があるそうだ。セッション後には、懇親会 (無料) も予定されているし、とても楽しみだ。
| 『INETA Day 2008~コミュニティパワーをリアルに体験しよう』 | ||
|---|---|---|
| 日時 | 日 | 2008年5月17日(土) |
| 時間 | 13:00-19:00 (受付開始 12:30) | |
| 場所 | 会場 | 日本科学未来館 |
| 住所 | 東京都江東区青海2-41 | |
| 参加費 | 無料 (事前登録制) | |
| 詳細 | サイト | |
| 参加申込み | Microsoft Events ― INETA Day 2008~コミュニティパワーをリアルに体験しよう | |
INETA Day 2008 (5/17) 終了。
懇親会での参加者の方々の楽しげな顔は忘れられない。
コミュニティならではの素敵なイベントだった。
私は、「Visual Studio 2008による 開発環境・プログラミングの進化」と
題して、プログラミング スタイルにどのような新たな選択肢が追加された
のか、という話をした。
C# によるデモが中心で、
"手続き型なべあつ" → "マルチパラダイムなべあつ" → "音声なべあつ" → "LINQなべあつ" → "コンポーネントなべあつ"
と進化させてみた。なべあつがマルチパラダイムで「三が付く数字と、三の倍数の時だけ、アホになり、五の倍数のときは、犬っぽく」なる。音声出力にも挑戦してみた。
# 時間の関係で、説明をとても端折ることになったのが残念だった。
# どこかでもう一度ちゃんとやりたい。
INETA のサイトで資料とデモ用サンプルソースが公開された。
http://www.ineta.jp/tabid/173/Default.aspx
Community Launch イベントを開催します。
# 私も「Visual Studio 2008による開発環境・プログラミングの進化」で登壇予定です。
|
|
|
| 日時 | 2008年6月14日(土) 12:00~16:00 |
| 場所 | 英国風パブ HUB 新宿駅近く |
| 主催/共催 | こみゅぷらす、eパウダ~、codeseek |
| 参加費 | ¥1,000 今回 Culminis 様や、アイネタジャパン様、株式会社東京紙工様よりご支援をいただき、低い参加費を実現しました! |
| 定員 | 50名限定 |
| おしゃれなお店をお借りして、お酒を飲みながら実施いたしますので、お気軽にご参加ください。詳細/申し込みは、特設ページをご覧ください。 | |
昨日新宿で、「こみゅぷらす Community Launch 2008」が開催されました。
ご参加いただいた皆様、ご後援いただいた皆様、スタッフの皆さん、ありがとうございました!
お陰様でとても楽しいイベントになりました。
昨年は渋谷のピンクカウで「パブで飲みながら技術セッション」をやりました。そして、今年も開催することができました。





# 私は、HUB のフィッシュ&チップスなどを楽しみながら、12時から12時まで12時間ビールとなりました。
伊丹空港から一風変わった飛行機で宮崎へ。
宮崎空港に到着。
会場のあるホテルへ。
VSUG Leaders Summit 2008。ライトニングトークスをやった。
宮崎料理の店。
宮崎のポピュラーな芋焼酎。
鶏炭火焼。柚子胡椒と一緒に食べる。びっくりするほど柔らかくて美味。
カワハギの刺身の真を実力を知った。肝も新鮮でぷりぷり。
大量の冷汁。
夜の街。
宿泊したホテルを朝出発。
昼間の飲み屋街。
有名な宮崎県庁。
みやざき物産館で昼地ビール。
昼の街並み。
昼食のチキン南蛮。
次に目指した釜揚げうどんの店「戸隠」は残念ながら定休日だった。
釜揚げうどんの店「重乃井」
釜揚げうどん。やわらかくてもちもちして美味い。最後にお湯に付け汁を入れて飲む。
中が異様にカラフルな電車で宮崎空港へ。
宮崎空港にあった東国原人形。
空港で揚げたての「おび天」を食べた。
宮崎空港の中央部。南国ムード。
宮崎空港で食べられるマンゴーのソフトクリーム。
宮崎のデパートで買ってきた家への土産。花畑牧場 (北海道十勝の田中義剛の牧場)の生キャラメル。宮崎と何の関係もないがとても美味。
Micosoft MVP (Most Valuable Professional) for Development Tools - Visual C# のアワードを再受賞することができました。四年目になります (Jul. 2005 - Jun. 2009)。
思えば、随分多くの方のお世話になってきました。
NIFTY-Serve のプログラマ系フォーラムでのオフラインでのコミュニケーション。そして、2002年頃からのオンラインでのコミュニケーション。
IT業界で多くの皆さんとお話をさせていただいたことが、私にとっての宝です。
いつもありがとうございます。
今後とも宜しくお願い致します。
今年のTech・Ed Yokohama でも こみゅぷらすで BoF に出ます。
四日間+αの長い夏祭りが終わりました。
『Microsoft Tech・Ed 2008 Yokohama』
2008/08/26~29
色々な体験がありました。
・沢山のセッション
・BoF12 の準備と本番
・各種飲み会
お付き合いいただいた方、お話をさせていただいた方、BoF12 にご参加いただいた方 (聴衆とスタッフとスピーカーの皆様)、本当にありがとうございました。
■ 会場のパシフィコ横浜

■ 今年の宿泊先は横浜桜木町ワシントンホテル

■ 沢山のセッションやハンズオン等にも参加

■ 巨大なトークスタイマー

■ 飲み会関連

Tech・Ed 2008 Yokohama のスピーカーを多数に招いてのセミナーを福井で実現しました。この機会をお見逃しなく!
お席に限りがございますので、皆様お早目のお申し込みをお願いいたします。
| 『Micosoft 最新技術最強セミナー in 福井~Tech・Ed 2008 記念~』 | |
|---|---|
| 日時 | 2008年10月3日(金) 13:00~18:00 (受付は12:30より開始) ※終了後に有志で懇親会を開催します。こちらもお気軽にどうぞ。 ※懇親会に参加される方はセミナー終了後福井駅前に移動して行います。 |
| 場所 | 福井県中小企業産業大学校 特別教室 |
| 定員 | 60名 |
| 内容 |
|
| 参加費 | 無料 ※懇親会に参加される方は別途。 ※今回のイベントはINETA Japanの支援を受けて開催する予定です。 |
| 対象者 |
|
| お申し込み締切 | 2008年9月30日(火)迄 →詳細はこちら:http://fitea.org/?p=84 →お申し込みはこちら:http://fitea.org/enterevents.html ※お席に余裕がありませんので、お早めにお申し込み下さい。 |
| 主催 | FITEA (福井情報技術者協会) 後援 アイネタ・ジャパン |
| 留意事項 | ※会場へのアクセスはこちらをご覧下さい。産業会館の隣です。 約400台収容可能な無料駐車場があります。 http://www.fukui-iic.or.jp/fiib/access.html ※無料のフレンドリーバスが通っています。「生活学習館前」です。 福井駅前を12:00に出れば間に合います。帰りは18:04、19:04のバスが使えると思います。 http://www.library.pref.fukui.jp/guide/bus.html |
| イベント終了後の懇親会について | 福井駅近く「庄内家」にて18:40開始の予定です。ご予算はお一人5000~6000円程度の予定です。 http://gourmet.gyao.jp/0003003173/ スピーカーの方も参加されますので、この機会に交流を深めましょう。 |
昨日は、九段で Open Day 2008 Tokyo に参加。
日本の Microsoft MVP の半数位に当たる 100名が一堂に会した。マイクロソフトの方も多くいらしていた。
私は、Windows 7 や Visual Studio 2010 などのセッションや懇親会に参加。
※ 最後の写真は、とある MVP の方に頂いた PDC のお土産。ジャグリング用。
Silverlight 2 SDK 日本語ドキュメント認知度アップキャンペーン。
Silverlight 2 アプリケーション開発に必要なドキュメントが日本語になっているので紹介。
CES2009 (International Consumer Electronics Show 2009) でのマイクロソフト CEO スティーブ バルマー氏の基調講演です。
途中でシャルロット・ジョーンズ氏による Windows 7 ベータと Windows Mobile と Windows Liveのデモがあります。
# 他に、ロビー バック氏による Zune と Xbox のデモ、ジャネット ガロアによる Microsoft Research の未来のコンピュータのデモもあります。
※ 動画を観るには Silverlight が必要です。
※ 口述筆記したものはこちら。
今年もやります。

『こみゅぷらす Community Launch 2009
~Windows 7 コミュニティ勉強会 with Tech Fielders 東京編~』
■ 会場: マイクロソフト株式会社 新宿本社
■ 日時: 2009年6月27日 13:00 ~ 18:30
■ 主催: こみゅぷらす、アイネタ ジャパン、codeseek
後援: マイクロソフト
※ 詳しくは、http://comuplus.net/clt2009/ へ。
INETA Day 2009 の参加者募集が開始された。
マイクロソフト 萩原正義氏とアークウェイ 森屋英治氏がクラウドのアーキテクチャの話をされる。
クラウドに関するお二人の話を続けて聴ける、というのは、中々にすごいことだと思う。
私はスタッフとして参加する他、最新技術トラックで Visual Studio 2010 と .NET Framework 4.0 をご紹介する予定。
| 『INETA Day 2009 ~コミュニティパワーをリアルに体験しよう』 | ||
|---|---|---|
| 日時 | 2009年7月11日(土) 13:00-17:30 (受付開始 12:15) | |
| 会場 | 日本科学未来館
|
|
| 申込 | http://www.ineta.jp/tabid/232/Default.aspx | |
| 定員 | 300名 | |
| 参加費 | 無料 (事前登録制・要 INETA への登録) | |
| セッション詳細 | 【最新技術トラック】 |
|
| 【コミュニティトラック】 |
|
|
| 【チュートリアルトラック】 |
|
|

イベントレポートをあげました。
ご参加いただいた皆様、本当にありがとうございました。
おかげさまで充実したときを過ごすことができました。
INETA DAY 2009 に参加してきました。
ご参加いただいた皆様、ありがとうございました。
終了後は、懇親会 → ガンダム → 新橋で飲み、という流れになりました。
エンジニアとして尊敬する方たちとの、本当に素敵なひと時でした。貴重な機会に感謝。
下記に参加してきた。
| ReMIX Tokyo 09 | |
|---|---|
| 日時 | 2009/07/16(木) |
| 場所 | 東京ミッドタウン・ホール |
| 主催 | マイクロソフト |
キーノートでは、米 Microsoft のスコット・ガスリー氏 (Scott Guthrie, デベロッパープラットフォームコーポレートバイスプレジデント) が登場。
話題の中心は、Silverlight 3。
ちなみにこの日、「Microsoft Visual Studio 2008 Service Pack 1用 Silverlight 3 Tools 日本語版」や「Silverlight 3 ソフトウェア開発キット 日本語版」の提供が開始された。
他には、Microsoft Expression 3 のデモも興味深いものだった。
昼には、『スコットガスリーMVPスペシャルセッション』にも参加。
午後からは、3トラックに分かれて、セッションが行われた。
多くのブースが出展していて、Silverlight 3 や Experssion 3、Windows Surface などのデモを見ることができた。
会場で、Silverlight 3 の本をゲット。
セッション後の参加者交流パーティでは、マイクロソフトの方をはじめとした多くの技術者と話をすることができた。
下記に参加してきました。
| 『Windows 7 コミュニティ勉強会 with Tech Fielders 北陸編』 | |
|---|---|
| 日付 | 2009/07/18(土) |
| 会場 | HOTEL KANAZAWA |
| 主催 | 北陸エンジニア交流支援 Redmine |
私は、Visual Studio 2010 のデモを中心としたセッションをやってきました。
スピーカーの多くが Mac ユーザー、開発環境に関しても Eclipse の話題が多く、マイクロソフト技術の勉強会なのにアウェー感の中でのセッションとなりました。
聴いていただいた皆様、スタッフの皆様、ありがとうございました。
Tech·Ed Japan 2009 の Birds of a Feather で、
『BOF-08 ~マルチパラダイム時代のプログラムの書き方~』
日時: 8月27日(木) 15:15-16:25
というのをやります。宜しければご参加ください。
現在 Micirosoft Visual Studio 2010 Beta 2 日本語版 がダウンロード可能だが、インストール前に Visual Studio 2010 Beta 1 や Silverlight 3.0 SDK などをアンインストールしておく必要がある。
詳しくは以下を参照:
- Visual Studio 2010 Beta2 English のアンインストール
- Visual Studio 2010 Tools for Office Runtime Beta2 のアンインストール
- Microsoft .NET Framework 4 Extended Beta2のアンインストール
- Microsoft .NET Framework 4 Client Profile Beta2のアンインストール
- Microsoft Visual C++ Redistributable - x86 9.0.30729.4148のアンインストール
Visual Studio 2010 Beta 2 をインストールする前に、Silverlight 3 SDK ビルド 3.0.40624 をアンインストールします。
Visual Studio 2010 Beta 1 に比較してかなり安定しており軽快になったようだ。
| イベント名 | Microsoft Tech·Days 2010 |
|---|---|
| 日時 | 平成22年2月23日(火)-24日(水) |
| 会場 | ホテル グランパシフィック LE DAIBA (東京都港区台場 2-6-1) |
| 主催 | マイクロソフト |
| 参加費 | 有料 |
| 詳細/申し込み | http://www.microsoft.com/japan/events/techdays/2010/ |
マイクロソフトの次世代プラットフォームを紹介するイベント PDC (Professional Developers Conference) の日本版です。
私はコミュニティ トラックの「Visual Studio 2010 でプチ・パラダイムシフトせよ!」 <2/23(火) 15:50-17:00 ROOM F> で登壇します。
| 『Microsoft 2010 MVP Global Summit』 | |
|---|---|
| 日時 | 2010/02/16(火)-21(日) |
| 場所 | 米国ワシントン州 ベルビュー (ベルビュー会場:Hyatt Regency Bellevue、レドモンド会場:マイクロソフト本社 オフィス) |
| 主催 | マイクロソフト |
※ 一日目のつぶやき。
※ 二日目のつぶやき。
※ 三日目のつぶやき。
※ 四日目のつぶやき。
※ 五日目のつぶやき。
※ 六日目のつぶやき。
『Microsoft Tech·Days 2010』
■ 日時: 2010/02/23(火)、24(水)
■ 場所: ホテル グランパシフィック LE DAIBA (東京都港区台場)
■ 主催: マイクロソフト株式会社
○ 02/23(火)
・キーノート「3 スクリーン + クラウドの世界を切り拓くマイクロソフトの最新テクノロジ ~ Let's dream and then Let's build ~」大場 章弘氏、スティーブ マークス 氏
・「Visual Studio 2010 でプチ・パラダイムシフトせよ!」 小島 富治雄氏、原 敬一氏
・「クラウド時代のアーキテクチャ + Azure + Data」 森屋 英治氏
○ 02/24(水)
・「並列プログラミングのパターンと Visual Studio 2010 を使ったその適用」 川西 裕幸氏
・「.NET Framework 4 時代の言語」 荒井 省三氏
・「インテルの並列化プログラミングへの取り組みとインテル Parallel Universe Portal のご紹介」 菅原 清文氏
・「ISV 向け Windows Azure による SaaS アプリケーション開発」 平井 昌人氏
・「クラウド コンピューティングのデータ、アプリケーション、開発手法のメタアプローチ」 萩原 正義氏
※ 一日目のつぶやき。
※ 二日目のつぶやき。
『2010 Community Open Day with INETA Japan - Osaka ~ Microsoft Visual Studio 2010 編』
■ 日時: 2010/04/03(土) 13:00-19:00
■ 場所: マイクロソフト株式会社 関西支店 セミナールーム
■ 主催: マイクロソフト株式会社、アイネタジャパン
■ セッション:
雷鳥で福井から大阪へ。
マイクロソフト大阪支店に到着。

小井土氏の司会でイベント開始。

森氏の『VS 2010 による Extensibility プログラミング』。
小井土氏「自動テストのススメ by VS 2010」。
四つのセッションが終わってハッピーアワ ーが始まった。
全て終わって新大阪から電車で帰宅。
『こみゅぷらす Community Launch 2010』
こみゅぷらす恒例、酒あり食べ物あり、参加者が入り乱れた楽しいラウンチイベントです。
料金もかなりリーズナブル!
豪華な景品や粗品が当たる抽選会も計画中です!
今回は Visual Studio 2010 のラウンチ イベントです。
・主催 : こみゅぷらす、codeseek
・開催日: 2010年7月10日(土)
・会場 : 新宿の居酒屋(詳細な店舗名はお申し込みした方にメールで) (東京都)
・定員 : 50名
・参加費 : 3,000円 (※ この会費は、利益を出すものではありません。店の食事代になります)
・詳細/お申込み: http://comuplus.net/clt2010/
予定セッション
■ 「Expression Blend 4とVisual Studio 2010によるSilverlight 4アプリケー
ション開発」
マイクロソフト 大西 彰 氏
■ 「IronPython、IronRuby on Silverlight」
マイクロソフト 荒井 省三 氏
■ 「Visual Studio デバッギング (仮)」
亀川 和史 (めさいあ) 氏
■ 「Visual Studio 2010 で C# ・WPF アプリケーション」
宇宙仮面 氏
■ 「C# VS. F# on Visual Studio 2010」
小島 富治雄 氏 (Fujiwo)
■ その他
素晴らしいスピーカー陣を予定しています。
奮ってご参加ください。
ユーザーインタフェースからクラウドまで、幅広いマイクロソフト技術をまとめて学べる贅沢なセミナーが福井で開催されます。
マイクロソフトは近年、Visual Studio 2010やWPF、Silverlight、LINQ、Windows Azureなど、さまざまな分野のテクノロジーを続々と発表しています。これらの技術について、一度に学べるセミナーは福井ではめったにありません。日本海側最大級!!
マイクロソフトエバンジェリストやマイクロソフトMVPが多数講師として参加しますので、この機会をお見逃し無く!
お席に限りがございますので、皆様お早目のお申し込みをお願いいたします。
■ セミナー名: 『Hokuriku.NET Vol.4』
■ 主旨: マイクロソフト系の技術を広く語り合う定期開催勉強会
■ 開催日: 2010年7月17日(土) 13:00 - 17:30 (12:40受付開始)
■ 場所: アオッサ6F 研修室603 (http://www.aossa.jp/access.html) JR福井駅から徒歩1分
■ 参加費: 500円程度のカンパをお願いしています
■ 主催: FITEA (http://fitea.org)/北陸エンジニアグループ (http://groups.google.co.jp/group/hokuriku_engineer)
■ 定員: 40名
■ 内容
タイムテーブル:
○12:40 受付開始
○13:00 開会挨拶 FITEA/北陸エンジニアグループ
○13:10 基調講演 『Visual Studio 2010の世界観』
マイクロソフト株式会社 デベロッパー&プラットフォーム統括本部 エバンジェリスト 長沢智治氏
○14:00 技術セッション 『Silverlight での開発 (仮)』
北陸エンジニアグループ 西村誠氏
○14:30 休憩
○14:40 Team WF による特別講演 第一部 『最適なテクノロジーの選択とアーキテクチャ』
Team WF (株式会社アークウェイ 代表取締役、Microsoft MVP 森屋英治氏、
アバナード株式会社 ソリューション・アーキテクト、Microsoft MVP、MCA 福井厚氏)、
福井コンピュータ株式会社 シニアエキスパート、Microsoft MVP 小島 富治雄氏
「マイクロソフトは、UI層、サービス層、データアクセス層のそれぞれにおいて様々なテクノロジーを提供しています。UI層であれば WinForm、WPF、ASP.NET、WebForm、ASP.NET MVC、Silverlightなど、サービス層であればASMX、WCFなど、データアクセス層であれば、レガシーADO.NET、Entity Framework、LINQ to SQLなど。さらにWindows Azureを加えるとテクノロジーの選択肢は広がります。これら多数のテクノロジーの特長を理解し、要求の応じた最適なテクノロジーを選択するためにはどうすればよいかを、ホワイトボードを使いながら、ディスカッション形式で解説します」
○15:30 休憩
○15:40 Team WF による特別講演 第二部 『Entity Framework Deep Dive』
Team WF (株式会社アークウェイ 代表取締役、Microsoft MVP 森屋英治氏、
アバナード株式会社 ソリューション・アーキテクト、Microsoft MVP、MCA 福井厚氏)
「マイクロソフトのORマッピング実装であるEntity Frameworkを深く掘り下げて解説します。エンティティ継承とテーブル マッピング、レイジーロード、テスト容易性、状態管理とシリアライゼーション、N層アプリケーションへの対応などを扱います」
○16:50 ライトニングトークス 中西 孝之氏 ((株)アイジュピタ)、小島 富治雄氏 (福井コンピュータ(株)、Microsoft MVP) 他
○17:25 締めのあいさつ・アンケート
■懇親会:
終了後福井駅近くで懇親会を予定しています。
場所は決まり次第お知らせいたします。
■お申し込み:
こちらからどなたでもお気軽にどうぞ
→ http://fitea.org/enterevents.html
ライトニング トークスの参加者を募集しています。
ライトニング トークスをしていただける方は、参加お申し込み時にお願いします。
■ 参考:
・Hokuriku.NET の Webサイト: http://sites.google.com/site/hokurikunet/
・Hokuriku.NET Vol.3 は石川県野々市町で先週土曜日に開催されました。
http://atnd.org/events/4182
http://blog.livedoor.jp/coelacanth_blog/archives/55384003.html
7/10(土) に開催した『こみゅぷらす Community Launch 2010』の様子を写真で紹介。
今年も Microsoft Tech·Ed で BoF やります。
「BOF-05 最速導入 Visual Studio 2010」(Day 2: 8/26 13:45-14:55、会議センター 3F)。
宜しければご参加ください。
http://www.microsoft.com/japan/teched/2010/session/session.aspx?SessionID=BOF-05
Microsoft Tech·Ed Japan 2010 に行ったときの写真。
| ■ 宿泊先の横浜桜木町ワシントンホテルから会場のパシフィコ横浜 |
|
| ■ 会場のパシフィコ横浜 |
|
| ■ 基調講演 |
|
|
| ■ 会場内の様子 |
|
| ■ セッションの様子 |
|
| ■ Hand-on Lab の様子 |
|
| ■ Birds of a Feather の様子 |
|
| ■ Attendee Party (8/25 18:05-19:30) の様子 |
|
|
| ■ MVPs & Tech·Ed Speakers Lunch (8/26 12:15-13:30) の様子 |
|
|
| ■ Windows Azure Community 発足パーティ (8/26 18:30-19:15) の様子 |
|
| ■ PeerTalk Lunch (8/27 12:05-13:45) の様子 |
|
using System;
using System.Collections.Generic;
class TestProgram
{
struct StructCounter
{
public int Number { get; set; }
public void Increment() { Number++; } // struct に内部の状態を変えるようなメソッドを持たすときは要注意。
}
class ClassCounter
{
public int Number { get; set; }
public void Increment() { Number++; }
}
// struct では、readonly なときとそうでないときで、内部の状態を変えるようなメソッドを呼んだときの挙動が異なるので要注意。
// 勘違いから思わぬバグの元になることも。
class ReadOnlyStructTest
{
// class を使った例。
ClassCounter classCounter = new ClassCounter();
readonly ClassCounter readOnlyClassCounter = new ClassCounter();
// struct を使った例。
StructCounter structCounter;
readonly StructCounter readOnlyStructCounter;
public void Run()
{
// クラスの場合はどっちも結果は同じなので、違いを特に意識しなくても大丈夫。
classCounter.Increment();
Console.WriteLine(classCounter.Number ); // 結果は 1。
readOnlyClassCounter.Increment();
Console.WriteLine(readOnlyClassCounter.Number ); // 結果は 1。
// struct の場合は、readonly なときとそうでないときで、要素の内部の状態を変えるようなメソッドを呼んだときの挙動が異なる。
structCounter.Increment();
Console.WriteLine(structCounter.Number ); // 結果は 1。structCounter そのものにアクセスしている。
readOnlyStructCounter.Increment();
Console.WriteLine(readOnlyStructCounter.Number); // 結果は 0。readOnlyStructCounter そのものではなく、コピーにアクセスしている。
}
}
// struct の配列とリストでは、要素の内部の状態を変えるようなメソッドを呼んだときの挙動が異なるので要注意。
// 勘違いから思わぬバグの元になることも。
static void StructArrayTest()
{
// class の場合はどっちも結果は同じなので、違いを特に意識しなくても大丈夫。
var classArray = new ClassCounter[] { new ClassCounter() };
classArray[0].Increment();
Console.WriteLine(classArray[0].Number); // 結果は 1。
var classList = new List<ClassCounter>() { new ClassCounter() };
classList[0].Increment();
Console.WriteLine(classList[0].Number); // 結果は 1。
// struct の場合は、配列とリストでインデクサ経由で要素の内部の状態を変えるようなメソッドを呼んだときの挙動が異なる。
var structArray = new StructCounter[] { new StructCounter() };
structArray[0].Increment();
Console.WriteLine(structArray[0].Number); // 結果は 1。structArray のインデクサを通じて、内部のアイテムに直にアクセスできる。
var structList = new List<StructCounter>() { new StructCounter() };
structList[0].Increment();
Console.WriteLine(structList[0].Number); // 結果は 0。structList のインデクサは、内部のアイテムのコピーを返す。
}
static void Main()
{
new ReadOnlyStructTest().Run();
StructArrayTest();
}
}
SQL Server の IDENTITY 列は、テーブルに新しい行が挿入されるときに自動的にその値がインクリメントされます。主キーとしてたいへん便利です。
行の挿入後に IDENTITY 列の値を取得したいことがあります。ADO.NET における、そのやり方を見て行きましょう。
先ず、IDENTITY 列を持ったテーブルを SQL Server 側に準備しましょう。

| Item | ||||
|---|---|---|---|---|
| 列名 | 主キー | データ型 | Null を許容 | IDENTITY の設定 |
| Id | ○ | int | × | はい |
| Name | × | nvarchar(100) | × | いいえ |

CREATE TABLE [dbo].[Item](
[Id] [int] IDENTITY(1,1) NOT NULL,
[Name] [nvarchar](100) NOT NULL,
CONSTRAINT [PK_Item] PRIMARY KEY CLUSTERED
(
[Id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
データベース側の準備ができましたので、これから ADO.NET の各種手法において、新規行の IDENTITY 列の値の取得方法を見ていきましょう。
SQL Server で使用する Transact-SQL では、"SCOPE_IDENTITY" という関数を使って、INSERT 後に、自動採番された値を取得することができます。
※ 参考: MSDN - SCOPE_IDENTITY (Transact-SQL)
それでは、実際のプログラムで試してみましょう。
using System.Data.SqlClient;
namespace TypedDataSetUpdateWithIdentityColumn
{
class Program
{
static void Main()
{
const string connectionString = @"Data Source=localhost\SQLEXPRESS;Initial Catalog=Test;Integrated Security=True";
using (var connection = new SqlConnection(connectionString)) {
connection.Open();
// 新規 Item の INSERT
using (var command = new SqlCommand("INSERT INTO Item (Name) VALUES (@Name);SELECT CAST(SCOPE_IDENTITY() AS int)", connection)) {
command.Parameters.Add(new SqlParameter("@Name", "Hoge"));
var newId = (int)command.ExecuteScalar();
}
}
}
}
}

command を ExecuteScalar することで、新たな行が挿入され、その行の Id 列の値が返ってきています。その直後の "newId" の値が、新たな Id 値である "1" になっているのが確認できます。型付き DataSet の場合はどうでしょう。試してみましょう。







INSERT"
でファイルの中を検索してみると、以下のような記述が見つかります。this._adapter.InsertCommand.CommandText = "INSERT INTO [dbo].[Item] ([Name]) VALUES (@Name)";
using TypedDataSetUpdateWithIdentityColumn.ItemDataSetTableAdapters;
namespace TypedDataSetUpdateWithIdentityColumn
{
class Program
{
static void Main()
{
using (var itemTableAdapter = new ItemTableAdapter()) {
// Item テーブルの SELECT
var itemTable = new ItemDataSet.ItemDataTable();
itemTableAdapter.Fill(itemTable);
// 新規 Item の INSERT
var newItemRow = itemTable.NewItemRow();
newItemRow.Name = "Hoge";
itemTable.Rows.Add(newItemRow);
itemTableAdapter.Update(itemTable);
}
}
}
}



this._adapter.InsertCommand.CommandText = "INSERT INTO [dbo].[Item] ([Name]) VALUES (@Name);\r\nSELECT Id, Name FROM Item WHERE (Id = SCOPE_IDENTITY())";

LINQ to SQL の場合はどうでしょう。試してみましょう。



[global::System.Data.Linq.Mapping.TableAttribute(Name="dbo.Item")]
public partial class Item : INotifyPropertyChanging, INotifyPropertyChanged
{
// ……途中省略……
[global::System.Data.Linq.Mapping.ColumnAttribute(Storage="_Id", AutoSync=AutoSync.OnInsert, DbType="Int NOT NULL IDENTITY", IsPrimaryKey=true, IsDbGenerated=true)]
public int Id
{
get
{
return this._Id;
}
set
{
if ((this._Id != value))
{
this.OnIdChanging(value);
this.SendPropertyChanging();
this._Id = value;
this.SendPropertyChanged("Id");
this.OnIdChanged();
}
}
}
AutoSync=AutoSync.OnInsert
という指定によって、行の挿入時に自動で Id 列の値を取得する SELECT 文が発行されます。
using System;
namespace TypedDataSetUpdateWithIdentityColumn
{
class Program
{
static void Main()
{
using (var itemData = new ItemDataClassesDataContext()) {
// 生成される SQL を標準出力ストリームに出力
itemData.Log = Console.Out;
// 新規 Item の INSERT
var newItem = new Item { Name = "Hoge" };
itemData.Item.InsertOnSubmit(newItem);
itemData.SubmitChanges();
}
}


INSERT INTO [dbo].[Item]([Name]) VALUES (@p0) SELECT CONVERT(Int,SCOPE_IDENTITY()) AS [value] -- @p0: Input NVarChar (Size = 4000; Prec = 0; Scale = 0) [Hoge] -- Context: SqlProvider(Sql2008) Model: AttributedMetaModel Build: 4.0.30319.1
最後に、Entity Framework の場合を見てみましょう。





namespace TypedDataSetUpdateWithIdentityColumn
{
class Program
{
static void Main()
{
using (var itemData = new ItemEntities()) {
// 新規 Item の INSERT
var newItem = new Item { Name = "Hoge" };
itemData.AddToItems(newItem);
itemData.SaveChanges();
}
}
}
}

2011/2/27(日) ~ 3/6(日) に米国のシアトルとその周辺に行き、Microsoft 主催の 2011 MVP Global Summit に参加してきた。
世界中から千数百人の Microsoft MVP が集まり、沢山のセッションやパーティが開かれた。
とても貴重で、思い出に残る体験だった。
えちぜん鉄道 → JR サンダーバード → Delta (関西国際空港 → シアトル・タコマ国際空港)





機内食。
無事シアトル・タコマ国際空港に到着。そして入国。昨年までは必要だった I-94W と呼ばれる入国カードが不要になっていた。
空港内には S.T.S. (Satellite Transit System) と呼ばれる無人の地下鉄があって、これでターミナルから移動する。
シアトル・タコマ国際空港からは Shuttle Express で Hyatt Regency Bellevue へ移動。 Shuttle Express は片道 $19 位。タクシー ($30-40) より大分安い。バスよりは高いが、空港からホテルまでドア・ツー・ドアなのが魅力。
ホテルの隣にある Bellevue Place。
ホテル近くのベルビューの街並み。



昨年部屋飲みした韓国の MVP の人たちと、今年もご一緒した。
ホテルから Microsoft Redmond campus へはバスで移動。
Bellevue Square の前を通って移動。
Insiders Party at MVP Summit 2011
ホテルで朝食
バスでキーノート会場 (Meydenbauer Center) へ
間もなくキーノートが始まる。
キーノートが始まった。
Microsoft の現 CEO の Steven Ballmer 登場。
Seattle Mariners のマスコットのMariner Moose 登場。
キーノート後の昼食 (Meydenbauer Center)
右端は Turbo Pascal、Delphi そして C# の父である Anders Hejlsberg。
会場を Mariner Moose がうろついていた。
グラウンではロックバンドの他にバッティングやベースランニングが楽しめる。
その他、イチローが記者会見をしているプレス用の部屋やロッカールーム等、球場内の様様な施設を見学できた。

今回初めて Light Rail に乗った。一日券を買うと経済的。ホテル近くの駅からシアトルのダウンタウンへ。
Light Rail には、Seattle-SeaTac Airport から、シアトルのダウンタウンに中心にある終点の Westlake まで乗る。片道 $2.5 だが、$5 の一日券を買うと経済的。
シアトルのダウンタウンに到着。
ダウンタウンの中心に位置するショッピングモール "WESTLAKE CENTER" にやってきた。ここにはダイソーもある。
Starbucks を始めとしたシアトル コーヒーの店が至るところに。
徒歩で Pike Place Market へ。ここはシアトル名物の魚介類や野菜や果物、土産物等、様様なものが売られている市場。
徒歩で Beecher's Handmade Cheese へ。
Beecher's の名物メニュー "MAC & CHEESE"。
すぐ近くには Starbucks の一号店がある。ここはいつ来ても数人のパフォーマーが音楽を演奏している。
Pike Place Market は海沿いにある。
Pike Place Market では様様なものが売られている。
Pike Place Market を出て海沿いの道へ向かうと、レストランや土産物屋が多く並んでいる。この頃雨が強めに降り出した。シアトルは雨が多い街だ。
Seattle Aquarium (シアトル水族館) が有った。
雨が激しくなってきたので、土産物屋やゲームセンターで雨宿り。
漸く雨が上がった。
再び Light Rail に乗って、宇和島屋に向かう。中華街の近く。
宇和島屋は、シアトル最大の日本食スーパー。日本のスーパーマーケットのように日本の食品が売られている。日本で買うより大分高め。
宇和島屋店内には紀伊國屋書店があった。
また Light Rail に乗ってダウンタウンに戻ってきた。
今度は PACIFIC PLACE へ。ここもショッピング モール。
PACIFIC PLACE 内の COACH で財布等を購入。COACH は米国ニューヨークのブランド。
夜のシアトルを歩いて、再び海沿いのレストラン街へ。
シアトル名物のクラムチャウダーで有名なシーフード レストラン Ivar's にやってきた。
シアトルの地ビールは最高。
生牡蠣を注文。三種類の牡蠣を楽しんだ。その他のシーフードもとても美味しかった。しかもリーズナブル。
四度 Light Rail に乗って、シアトル・タコマ空港近くの Holiday Inn Seattle-SeaTac Intl Airport へ戻った。


隣席の米国人夫妻と話す。なんでも関西にいる妹のところに行って観光するとのこと。
そして、離陸から4時間程経ったときだった。機内に機長からの声が流れた。
「電気系統の故障が見つかったので、Uターンしてシアトルに戻りつつある」という内容だった。
なんと中間地点まで飛んだのにこれから引き返すというのだ。
米国人の奥さんはショックで泣き出してしまった。
乗客はホテルと別の便の手続きをするために長い列を作った。
この手続には夜9時から1時間位掛かったが、ホテルのクーポンをくれただけで、帰りの飛行機は手配してくれる訳ではない。
帰りの飛行機を勝手に電話を掛けて確保してくれとのことだった。
多くの日本人が帰りの飛行機を確保出来ずに困っていた。

Delta に電話をかけ、なんとか帰りの飛行機を予約した。何も伝わっていないので、事情を説明するところからしなければならない。
結局翌日の関西空港行きは既にいっぱいで取れず、同日の成田空港行きを取った。成田から自宅までの交通費分余計に掛かるが仕方がない。
その後、用意された Doubletree Hotel Seattle Airport にシャトルバスで向かう。

Doubletree Hotel Seattle Airport を出発。



昨日の飛行機で隣り合わせた米国人夫妻と空港でばったり。関西空港行きの飛行機が取れたとのこと。少し話して、お互いの幸運を願って別れた。
これから搭乗する Delta 機。今度のは、成田行きなので、各座席には映画などが観られるモニターが付いている。

無事成田空港に到着した Delta 機。


シアトルに行く前から欲しかったものを少し買って帰った。
シアトル名物 Beecher's のチーズとクラッカーと RED HOOK のビール。それぞれ米国でも食べたり飲んだりした。
前回の 2010 MVP Global Summit の Atendee Party で米国の人に勧められたバーボン「KNOB CREEK」
Microsoft 製のマウスや Web カメラ。
その他、COACH の財布、シアトル チョコレート、子供向けの土産等。
※ 参考: Sleep sortの各言語での実装まとめ
// Sleep Sort C#/.NET4 版
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
public static class SleepSort
{
public static IEnumerable<T> Sort<T>(this IEnumerable<T> collection, Func<T, int> convert)
{
var queue = new ConcurrentQueue<T>();
collection.AsParallel().ForAll(
value => {
Thread.Sleep(convert(value));
queue.Enqueue(value);
}
);
return queue;
}
}
static class Program
{
static void Main()
{
var sortedCollection = new[] { 30, 200, 70, 400, 800, 35 }.Sort(x => x);
foreach (var item in sortedCollection)
Console.WriteLine(item);
}
}
「Hokuriku.NET Vol.7 in 福井」というイベントを開催した。
レポートはこちらに書いた。
以下のイベントを開催した。
2007年から毎年夏にやっている恒例のこみゅぷらすイベントで、今年は、震災の影響で開催が延期されてしまった Microsoft Tech・Ed Japan の代わりのイベントである「Tech Party 2011」の一環で開催した。
例によって、飲み放題付き。
| こみゅぷらす 『Tech・Aid 2011 夏』 | |
|---|---|
| 開催日時 | 2011年8月20日(土) 10:00~17:00 |
| 開催場所 | 新宿 |
| 主催 | こみゅぷらす、codeseek、日本C#ユーザー会 |
| 関連サイト | 「Tech Party 2011」 |
| 『Tech Party 2011 in 北陸』 | |
|---|---|
| 日時 | 8月27日(土) 10:00~17:00 |
| 会場 | 石川工業高等専門学校 (石川県河北郡津幡町北中条タ1) |
| 参加費 | 無料 |
| Webサイト | http://atnd.org/events/18440 |
| 関連イベント | Tech Party 2011 |
最近我が家で、滑石を鑢や紙鑢で削り、勾玉等の様様な形を作るのが流行っている。
滑石は加工し易い石で、これを棒鑢や荒い (80番程度の) 紙鑢 で大雑把に形を決めた後、細部は精密鑢で削っていく。最後は細かい (1500番程度の) 耐水性紙鑢に水を付けて擦り、新聞紙で磨いて艶を出す。
子供達とやっていると、結構楽しい。
C# を作ってみた (ちゃんと ♯ じゃなくて #)。
WebMatrix は Microsoft 製の Windows 上に Web サイトを構築するための無料ツール。 簡単に ASP.NET ベースや PHP ベースの Web サイトを準備できる。 必要なデータベースを同時にインストールしたり、後から編集したりすることもできる。
ASP.NET の新しい構文である Razor にも対応。
※ ちなみに、以前リリースされた ASP.NET Web Matrix とは別物。
WebMatrix 自体も Microsoft Web Platform Installer によって簡単にインストールできる。 必要なコンポーネント (適切なバージョンの .NET Framework、SQL Server 関連コンポーネント、IIS Express 等) も自動でインストールされる。 ASP.NET ベースや PHP ベースの Web アプリケーション (CMS 等) のインストールも行うことができる。
使い始めるには先ず「Microsoft WebMatrix おすすめ学習コンテンツ」で紹介されているコンテンツがお勧め。現時点では以下のようなものが紹介されている。
他には以下の記事が参考になる:
Microsoft の開発者向けカンファレンス「BUILD」が開催されている。
Windows 8 を初めとして非常に多くの新たなテクノロジーが発表され、Preview 版等が使用できるようになっている。
以下で 関連記事・コンテンツを紹介したい。
# 「C# を作ってみた」の続き。
C# に続いて F# も作ってみた。
今度は Visual Studio のロゴっぽいものに挑戦。
福井で Windows Phone のハッカソンが開催される。
14日はアイデアソンで、Windows Phone 開発の概要説明やアイディアの出し合いなどが行われる。21日は、開発し、実際にマーケットに登録。
| 『Windows Phone Hackathon 2012 in Fukui』 | |
|---|---|
| 日時 | 2012年4月14日(土) 10:00~夕方、 4月21日 10:00~18:00 |
| 会場 | 福井県産業情報センター システム設計室 (福井県坂井市丸岡町熊堂第3号7番地1-16) |
| 参加費用 | 4月14日: 500円、4月21日: 500円 (会場費) |
| 主催 | Hokuriku.NET |
| 詳細/申し込み | http://atnd.org/events/26574 |
2012 MVP Global Summit (2012/02/28-03/02) に参加してきた。
参加レポート: 2012 MVP Global Summit 参加レポート - slideshare
| 『Windows Developer Days』 | |
|---|---|
| 日時 | 2012年4月24日(火)~25日(水) 10:00-18:00 |
| 会場 | ザ・プリンスパークタワー東京 |
| 参加費 | ¥84,000 (早期割引 4月18日 16:59 迄 税込¥63,000) |
| 詳細/参加登録 | Windows Developer Days |
モンティ・ホール問題 というのがある。 アメリカのゲームショー番組の中で行われた以下のようなゲームに関する問題である。
- 三つのドアのどれか一つの後ろに当たりの商品が隠されている。残りの二つはハズレだ。
- プレイヤーである番組参加者は、一つのドアを選ぶ。
- 番組司会者のモンティは、残り二つのドアのうちからハズレのドアを一つ開けて見せる。
- モンティはプレイヤーに「ドアを選びなおしても良い」と言う。
- プレイヤーはドアを選びなおすべきだろうか?
![]()
当たりはどれか一つ。
この問題の正解は、「選びなおした方が良い。何故なら当たる確率が倍になるから」というものだ。
しかし、これを正解とするということに納得しない人が多いらしい。直感と異なるからだ。
教授レベルの数学者を含む多くの人が反論したらしい。
大きな論争となったこの問題は、結局コンピューター上でのシミュレーションで決着がついたそうだ。
とても興味深い題材なので、私も C# でシミュレーションをやってみた。
// モンティ・ホール問題 - Wikipedia
// http://ja.wikipedia.org/wiki/%E3%83%A2%E3%83%B3%E3%83%86%E3%82%A3%E3%83%BB%E3%83%9B%E3%83%BC%E3%83%AB%E5%95%8F%E9%A1%8C
using System;
using System.Linq;
namespace モンティ・ホール問題
{
static class プログラム
{
static void Main(string[] コマンドライン引数)
{ シミュレーター.シミュレート(コマンドライン引数から繰り返し回数を得る(コマンドライン引数)); }
static int コマンドライン引数から繰り返し回数を得る(string[] コマンドライン引数)
{
if (コマンドライン引数.Length < 0)
{
int 繰り返し回数;
if (int.TryParse(コマンドライン引数[0], out 繰り返し回数))
return 繰り返し回数;
}
const int デフォルトの繰り返し回数 = 1000000;
return デフォルトの繰り返し回数;
}
}
static class シミュレーター
{
public static void シミュレート(int 試す回数)
{ 結果.表示(選びなおす場合に当たる確率: シミュレート(試す回数, 選びなおす: true),
選びなおさない場合に当たる確率: シミュレート(試す回数, 選びなおす: false)); }
static double シミュレート(int 試す回数, bool 選びなおす)
{
var 当たりの回数 = Enumerable.Range(1, 試す回数).Count(_ =< ゲーム.プレイする(選びなおす));
var 当たる確率 = (double)当たりの回数 / 試す回数;
結果.表示(選びなおす, 試す回数, 当たりの回数, 当たる確率);
return 当たる確率;
}
static class 結果
{
public static void 表示(double 選びなおす場合に当たる確率, double 選びなおさない場合に当たる確率)
{ 表示(選びなおす場合と選びなおさない場合の当たる確率の比:
選びなおす場合に当たる確率 / 選びなおさない場合に当たる確率); }
public static void 表示(double 選びなおす場合と選びなおさない場合の当たる確率の比)
{ Console.WriteLine("・結論: 選びなおす場合は、選びなおさない場合に比べて、{0} 倍当たりやすい。",
選びなおす場合と選びなおさない場合の当たる確率の比); }
public static void 表示(bool 選びなおした, int 試した回数, int 当たった回数, double 当たる確率)
{ Console.WriteLine("・{0}場合は、当たりの回数は: {1} 回中 {2} 回で、当たる確率は {3}。",
選びなおした ? "選びなおした" : "選びなおさなかった", 試した回数, 当たった回数, 当たる確率); }
}
}
static class ゲーム
{
const int 全ドアの数 = 3;
public static bool プレイする(bool 選びなおす)
{
var 当たりのドア = ランダムなドア();
var プレイヤーが選択したドア = ランダムなドア();
var モンティの開けたドア = 残りのドアから一つ(当たりのドア, プレイヤーが選択したドア);
if (選びなおす)
プレイヤーが選択したドア
= 残りのドアから一つ(プレイヤーが選択したドア, モンティの開けたドア);
return プレイヤーが選択したドア == 当たりのドア;
}
static int 残りのドアから一つ(int 当たりのドア, int プレイヤーが選択したドア)
{
return 当たりのドア == プレイヤーが選択したドア
? 残りのドアからランダムに一つ(除外するドア: 当たりのドア)
: 残りのドアからどれでも一つ(一つ目の除外するドア: 当たりのドア,
二つ目の除外するドア: プレイヤーが選択したドア);
}
static int 残りのドアからランダムに一つ(int 除外するドア)
{ return 或るドアから数えてX番目のドア(除外するドア,
そのドアから数えてX番目: ランダム.一から或る数までの乱数(全ドアの数 - 1)); }
static int 或るドアから数えてX番目のドア(int 或るドア, int そのドアから数えてX番目)
{ return (或るドア + そのドアから数えてX番目) % 全ドアの数; }
static int 残りのドアからどれでも一つ(int 一つ目の除外するドア, int 二つ目の除外するドア)
{ return Enumerable.Range(0, 全ドアの数)
.First(ドア =< ドア != 一つ目の除外するドア && ドア != 二つ目の除外するドア); }
static int ランダムなドア()
{ return ランダム.零から或る数までの乱数(全ドアの数); }
}
static class ランダム
{
static readonly Random 乱数 = new Random();
public static int 零から或る数までの乱数(int 或る数)
{ return 乱数.Next(或る数); }
public static int 一から或る数までの乱数(int 或る数)
{ return 乱数.Next(或る数) + 1; }
}
}
以下のように正解の通りの結果となった。
- 選びなおした場合は、当たりの回数は: 1000000 回中 666662 回で、当たる確率は 0.666662。
- 選びなおさなかった場合は、当たりの回数は: 1000000 回中 333802 回で、当たる確率は 0.333802。
- 結論: 選びなおす場合は、選びなおさない場合に比べて、1.99717796777731 倍当たりやすい。
実は、プログラムを書いていく過程で、問題が整理されていったため、途中から実行する迄もなく結果は明白なように感じていた。
以下のように考えたのだ。
▼日付: 2012年06月16日(土)受付9:55~予定
▼会場: マイクロソフト品川オフィス 31F
▼参加費用: 無料(VSUG 会員登録が必要となります)
▼日付: 2012年12月15日(土) 受付9:30~予定
▼会場: マイクロソフト品川オフィス 31F
▼参加費用: 無料 (VSUG 会員登録が必要となります)
Windows Store アプリと Windows Phone アプリ、Silverlight アプリ、WPF アプリは、どれも XAML を使って開発することができ、共通する部分も多い。
そこで、これらでソースコードを共通化する方法に関する記事を紹介する。
先ずは @IT の岩永 信之氏の記事から。"Portable Class Library" と呼ばれるマルチ プラットフォーム クラス ライブラリによるソースコードの共通化、MVVMパターンによるビューとモデルの分離方法について、判りやすく書かれている。
続いて Windows Store アプリ開発に関する記事を多く書かれている山本 康彦氏のスライドから。
こちらでは、スライドの30ページ目から Portable Class Library について書かれている。
このスライドでは以下の記事が引用されている:
この記事では、MVVM パターンを用いている。各プラットフォーム用に View を分け、Model と ViewModel を Portable Class Library で共通化することを推奨している。
Portable Class Library に関して幾つか他の記事を紹介する:
プラットフォーム間での XAML の違いについては、以下の記事も参考になる:
この記事では、匿名メソッドとラムダ式の意味の違いについて考えてみたい。
匿名メソッドとラムダ式は、同じように使うことができる場面が多い。
例えば、以下のようなデリゲートを引数にとるメソッドがあったとして、
using System;
using System.Collections.Generic;
static class Enumerable
{
// 述語に基づいて値のシーケンスをフィルター処理
public static IEnumerable<TSource> Where<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate)
{
foreach (var item in source) {
if (predicate(item))
yield return item;
}
}
}
この predicate には、通常のメソッドも匿名メソッドもラムダ式も渡すことができ、いずれも同じ結果となる。
using System;
class Program
{
static bool IsEven(int number)
{
return number % 2 == 0;
}
static void Main()
{
var numbers = new[] { 3, 7, 1, 6, 4, 5 };
// 普通のメソッドをデリゲートに入れて渡す例
var evenNumbers1_1 = numbers.Where(new Func<int, bool>(IsEven));
// 上に同じ
var evenNumbers1_2 = numbers.Where(IsEven);
// 匿名メソッドを使った例
var evenNumbers2 = numbers.Where(delegate(int number) { return number % 2 == 0; });
// ラムダ式を使った例
var evenNumbers3 = numbers.Where(number => number % 2 == 0);
}
}
いずれの場合も、同じ型のデリゲートとして渡されることになるのだ。
以下の例でも同じ型のデリゲートとして受けられることが判る。
Func<int, bool> delegate1_1 = new Func<int, bool>(IsEven);
Func<int, bool> delegate1_2 = IsEven; // 上と同じ
Func<int, bool> delegate2 = delegate(int number) { return number % 2 == 0; };
Func<int, bool> delegate3 = number => number % 2 == 0;
上の例では、一行目 delegate1_1 の例と二行目 delegate1_2 の例は全く同じ意味だ。書き方が違うだけの、単なる糖衣構文に過ぎない。
では、三行目の匿名メソッドを使った delegate2 の例 と四行目のラムダ式を使った delegate3 の例も、単なる糖衣構文なのだろうか?
確かに、ラムダ式を使った方が、型推論の恩恵を存分に受けられ、書き方がぐっとシンプルになる。だが、書き方だけの違いなのだろうか?
今度は、両者で違いが出る例を見てみよう。
LINQ to SQL を使った例だ。
予め SQL Server に Employee というシンプルなテーブルを用意した。

次に、Visual Studio でプロジェクトを作り、EmployeeDataClasses.dbml という名前で「LINQ to SQL クラス」を追加した。

では、ラムダ式を使ってデータ アクセスを行う例から見てみよう。
// LINQ to SQL の例 - ラムダ式で書いた場合
using LambdaExpressionSample; // EmployeeDataClassesDataContext の名前空間
using System;
using System.Linq;
class Program
{
static void Main()
{
var dataContext = new EmployeeDataClassesDataContext();
dataContext.Log = Console.Out; // 発行された SQL をモニターする為に、コンソールに出力
var data1 = dataContext.Employee.Where(employee => employee.Name.Contains("山"));
var data2 = data1.OrderBy(employee => employee.Name);
var data3 = data2.Select(employee => employee.Name);
data3.ToList().ForEach(Console.WriteLine);
}
}
この例で発行された SQL は、以下の通り。
SELECT [t0].[Name] FROM [dbo].[Employee] AS [t0] WHERE [t0].[Name] LIKE @p0 ORDER BY [t0].[Name] -- @p0: Input NVarChar (Size = 4000; Prec = 0; Scale = 0) [%山%]
三行に分けて書いているのに、SQL の発行は data3.ToList() のときの一回だけだ。data1、data2、data3 と三回も戻り値を受けているのに、ToList() されるまで評価が遅延されている訳だ。
SQL の発行は一回だけだが、where は WHERE、OrderBy は ORDER BY、Select で Name だけなのは SELECT で Name だけと、ちゃんと狙い通りのものが発行されているのが判る。
LINQ to SQL (や Entity Framework) の凄いところだ。
因みに、上ではメソッド構文を使って書いているが、クエリ構文を使って書くこともできる。
以下の二つは意味的には同じで、糖衣構文だ。
// メソッド構文
var data3 = dataContext.Employee
.Where(employee => employee.Name.Contains("山"))
.OrderBy(employee => employee.Name)
.Select(employee => employee.Name);
// クエリ構文
var data3 = from employee in dataContext.Employee
where employee.Name.Contains("山")
orderby employee.Name
select employee.Name;
次に、この例を匿名メソッドに置き換えてやってみよう。ラムダ式が匿名メソッドの単なる糖衣構文なら同じ結果になる筈だ。
// LINQ to SQL の例 - 匿名メソッドで書いた場合
using LambdaExpressionSample;
using System;
using System.Linq;
class Program
{
static void Main()
{
var dataContext = new EmployeeDataClassesDataContext();
dataContext.Log = Console.Out; // 発行された SQL をモニターする為に、コンソールに出力
var data1 = dataContext.Employee.Where(delegate(Employee employee) { return employee.Name.Contains("山"); });
var data2 = data1.OrderBy(delegate(Employee employee) { return employee.Name; });
var data3 = data2.Select(delegate(Employee employee) { return employee.Name; });
data3.ToList().ForEach(Console.WriteLine);
}
}
結果はこうなった。
SELECT [t0].[Id], [t0].[Name] FROM [dbo].[Employee] AS [t0]
ラムダ式の場合と大きく異なる。
WHERE による行の絞り込みも SELECT による列の絞り込みも ORDER BY による並べ替えも全然反映されていない。
つまり、この例では、LINQ to SQL で単純に Employee テーブルの全列、全行を取ってきて、後は LINQ to Object でオンメモリで処理している訳だ。
これでは全く非効率だ。ラムダ式を使うべき、と云うことになろう。
序でに、両者を混在させた例も見ておこう。
// LINQ to SQL の例 - ラムダ式と匿名メソッドを混在させた場合
using LambdaExpressionSample; // EmployeeDataClassesDataContext の名前空間
using System;
using System.Linq;
class Program
{
static void Main()
{
var dataContext = new EmployeeDataClassesDataContext();
dataContext.Log = Console.Out; // 発行された SQL をモニターする為に、コンソールに出力
var data1 = dataContext.Employee.Where(employee => employee.Name.Contains("山"));
var data2 = data1.OrderBy(delegate(Employee employee) { return employee.Name; });
var data3 = data2.Select(employee => employee.Name);
data3.ToList().ForEach(Console.WriteLine);
}
}
結果はこうなった。
SELECT [t0].[Id], [t0].[Name] FROM [dbo].[Employee] AS [t0] WHERE [t0].[Name] LIKE @p0 -- @p0: Input NVarChar (Size = 4000; Prec = 0; Scale = 0) [%山%]
SQL に反映されているのは、ラムダ式を使った Where までで、匿名メソッドより後ろは LINQ to Object で処理されているのが判る。
この例で、ラムダ式は匿名メソッドと意味的に同じものではない、ということが判った。
では、両者はどう意味が違うのだろうか。
上の LINQ to SQL の例の Where の部分を比べてみよう。
// ラムダ式を使った例
var data1 = dataContext.Employee.Where(employee => employee.Name.Contains("山"));
// 匿名メソッドを使った例
var data1 = dataContext.Employee.Where(delegate(Employee employee) { return employee.Name.Contains("山"); });
Visual Studio を使って、両者の Where の部分を右クリックし、「定義へ移動」してみよう。
一見同じ Where メソッドを呼んでいるようだが、異なった Where メソッドを呼んでいることが判るだろう。
// ラムダ式を使った例から呼ばれる Where メソッド
namespace System.Linq
{
public static class Queryable
{
public static IQueryable<TSource> Where<TSource>(this IQueryable<TSource> source, Expression<Func<TSource, bool>> predicate);
}
}
// 匿名メソッドを使った例から呼ばれる Where メソッド
namespace System.Linq
{
public static class Enumerable
{
public static IEnumerable<TSource> Where<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate);
}
}
匿名メソッドの方は Func<TSource, bool>、即ちデリゲートだが、ラムダ式の方は Expression と云う違うもので受けている。
参考までに、Func と Expression の定義は以下のようになっている。
// Func はデリゲート
namespace System
{
public delegate TResult Func<in T, out TResult>(T arg);
}
// Expression はデリゲートではない
namespace System.Linq.Expressions
{
public sealed class Expression<TDelegate> : LambdaExpression;
}
ラムダ式は、あくまでも「式」として渡されているのだ。
対して、匿名メソッドは Expression として扱うことができない。
試してみよう。
using LambdaExpressionSample; // EmployeeDataClassesDataContext の名前空間
using System;
using System.Linq.Expressions;
class Program
{
static void Main()
{
// デリゲートとして使う分には、どちらでも OK
Func<Employee, bool> delegate1 = employee => employee.Name.Contains("山");
Func<Employee, bool> delegate2 = delegate(Employee employee) { return employee.Name.Contains("山"); };
// だが、Expression として使えるのはラムダ式の方だけ
Expression<Func<Employee, bool>> expression1 = employee => employee.Name.Contains("山");
//Expression<Func<Employee, bool>> expression2 = delegate(Employee employee) { return employee.Name.Contains("山"); }; // コンパイル エラーになる
}
}
匿名メソッドの方は、「匿名メソッド式を式のツリーに変換することはできません」と云うコンパイル エラーとなる。
即ち、ラムダ式を Expression として扱っている場合は、匿名メソッドは代わりにはならない、ということになる。
ラムダ式は匿名メソッドの糖衣構文などではないのだ。
一般的には、ラムダ式を使うことで書き方もシンプルになることだし、匿名メソッドは使わずにラムダ式で書くようにした方が良いだろう。
今回は、匿名メソッドとラムダ式の意味の違いについて考察した。
ラムダ式はデリゲートのみならず、式としても扱うことができる、と云うことだ。
LINQ to SQL のような LINQ プロバイダーでは、ときにラムダ式を式として扱うことが大切だろう、と想像できる。
だが、それ以外のプログラミングでラムダ式を式として扱って便利なことってあるんだろうか?
と云う訳で、次回は、ラムダ式を Expression として扱うと便利な例について書く予定だ。
前回、「匿名メソッドとラムダ式の違い」と云う記事で、匿名メソッドとラムダ式の意味の違いについて考えた。
それについて、少し補足しておきたい。
「ラムダ式を Expression として扱っている場合は、匿名メソッドは代わりにはならない」と述べたが、ラムダ式であれば何でも Expression として扱える訳ではないのだ。
ラムダ式として足し算を行うだけの (x, y) => x + y と云うシンプルなものを用意し、これを Expression で受けてみる。
using System;
using System.Linq.Expressions;
class Program
{
static void Main()
{
Expression<Func<int, int, int>> expression = (x, y) => x + y;
}
}
これは特に問題ない。
ラムダ式の => より右の部分には式だけでなくステートメントのブロックも置くことができる。
このラムダ式の => より右の部分を、式ではなく、ステートメントのブロックに変えてみよう。
例えば、(x, y) => { return x + y; } とし、これを Expression で受けてみる。どうなるだろうか?
using System;
using System.Linq.Expressions;
class Program
{
static void Main()
{
Expression<Func<int, int, int>> expression = (x, y) => { return x + y; };
}
}
すると今度は、「ステートメント本体を含むラムダ式は、式のツリーに変換できません」というコンパイル エラーになる。
つまり、ラムダ式の => の右の部分が式である場合は Expression として扱えるが、ステートメントのブロックである場合は Expression として扱えないのだ。
「式でないものは式として扱えない」と云う、云わば当たり前の結果だ。
ところで、F# や Scala、Haskell などの関数型プログラミングをより得意とする言語では、if 等多くのものが式として扱える。
それらと比較すると、C# ではまだまだ式として書ける範囲は少ない。
例えば、if は文だ (勿論 C# でも三項演算子で書けるものは式で表現できる)。
だが、C# でもっと多くのものが、F# や Scala、Haskell などの言語のように文でなく式で表せるようになれば、益益 Expression で扱える範囲が増える、と云うことになるだろう。
今回は、前回の記事の補足を軽く行った。
今後、ラムダ式を Expression として利用する例を何回かに分けて、紹介していく予定だ。
次回は、先ず Expression そのものへの理解を深めるために Expression の構造を調べてみたいと思う。
「匿名メソッドとラムダ式の違い」と云う記事で、匿名メソッドとラムダ式の意味の違いについて考えた。
「ラムダ式を Expression として扱っている場合は、匿名メソッドは代わりにはならない」と述べたが、ラムダ式を Expression として扱う例について、これから数回に分けて書いていきたい。
ラムダ式を Expression として扱う例を挙げる前に、今回は、先ずは Expression 自体について理解を深めたいと思う。
Expression はどのような構造をしているのだろうか? 中がどうなっているのかを見てみることにしよう。
手始めに、Visual Studio のデバッガーを用いて Expression の中を覗いてみる。
ラムダ式として足し算を行うだけの (x, y) => x + y と云うシンプルなものを用意し、これを Expression で受ける。
using System;
using System.Linq.Expressions;
class Program
{
static void Main()
{
Expression<Func<int, int, int>> expression = (x, y) => x + y;
}
}
これをデバッグ実行して、デバッガーで expression の中を覗いてみる。
Body、Name、NodeType、Parameters、Type 等の各プロパティとその値が見えているのが判るだろう。
Name はなく、NodeType は Lambda だ。名前がないと云うことと、式の種類がラムダ式であることを表している。
Type は Func`3、つまり TResult Func<in T1, in T2, out TResult> になっている。
Body の中を見てみよう。
ラムダ式の中の => より後ろの x + y の部分であることが判る。引数の x が見えている。NodeType は Add で足し算の式であることを表している。
Type は Int32、つまり
int だ。
Left は x、Right は y となっていて、二項演算である足し算の左オペランドが x、右オペランドが y であることを表している。
更に Left の中を見てみる。
Name は x、Type は Int32 つまり int。
NodeType は Parameter で式の種類が引数であることを表している。
続いて Parameters。
Parameters は要素数 2 のコレクションになっているようだ。ラムダ式の中の => より前の部分の引数を表していることが判る。
Parameters の一つ目の要素を見てみよう。
引数の x が見えている。NodeType は Parameter つまり式の種類は引数、Type は Int32 つまり int だ。
上の例では、expression のNodeType は Lambda であり、この式の種類がラムダ式であることを表していた。
その他にも、幾つかの式が出てきている。
例えば、Body の部分。ラムダ式の中の => より後ろの x + y の部分だが、ここも式であり、式の種類は二項演算の足し算の式であった。
デバッガーでよく見てみると、これらの式も Expression であることが判る。
Expression はその中に再帰的に Expression を持つことでツリー構造になっているようだ。
Expression には幾つかの種類があることも判る。
ラムダ式、二項演算の式、などだ。
実は、Expression クラスには沢山の派生クラスがあり、それぞれが様様な式の種類を表している。
以下を参照してほしい:
その一部を示すと、こんな感じだ。
| Expression クラスの派生クラス | 種類 | NodeType プロパティの値 | |
|---|---|---|---|
| NodeType | NodeType の説明 | ||
| LambdaExpression | ラムダ式 | Lambda | ラムダ式 |
| BinaryExpression | 二項演算式 | Add | 足し算 |
| AddChecked | オーバーフロー チェックを行う足し算 | ||
| Subtract | 引き算 | ||
| SubtractChecked | オーバーフロー チェックを行う引き算 | ||
| Multiply | 掛け算 | ||
| MultiplyChecked | オーバーフロー チェックを行う掛け算 | ||
| Divide | 割り算 | ||
| Modulo | 剰余演算 | ||
| Power | 累乗 | ||
| UnaryExpression | 単項演算式 | Negate | 単項マイナス演算 |
| NegateChecked | オーバーフロー チェックを行う単項マイナス演算 | ||
| UnaryPlus | 単項プラス演算 | ||
| Not | ビット補数演算または論理否定演算 | ||
| Convert | キャスト演算 | ||
| ConvertChecked | チェック付きキャスト演算 | ||
| TypeAs | as による型変換 | ||
| ArrayLength | 配列の長さを取得する演算 | ||
| Quote | 定数式 | ||
それでは、Expression のツリー構造を実際に見てみよう。
ツリー構造になった Expression を再帰的に辿りながら表示していくクラスを作ってみた。
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq.Expressions;
static class EnumerableExtensions
{
// コレクションの各要素に対して、指定された処理をインデックス付きで実行
public static void ForEach<TItem>(this IEnumerable<TItem> collection, Action<TItem, int> action)
{
int index = 0;
foreach (var item in collection)
action(item, index++);
}
}
// Expression の中身をダンプ (ラムダ式、二項演算式、単項演算式以外は簡易表示)
static class ExpressionViewer
{
// Expression の中を (再帰的に) 表示
public static void Show(this Expression expression, int level = 0)
{
if (expression as LambdaExpression != null)
// ラムダ式のときは詳細に表示
ShowLambdaExpression((LambdaExpression)expression, level);
else if (expression as BinaryExpression != null)
// 二項演算のときは詳細に表示
ShowBinaryExpression((BinaryExpression)expression, level);
else if (expression as UnaryExpression != null)
// 単項演算のときは詳細に表示
ShowUnaryExpression((UnaryExpression)expression, level);
else if (expression != null)
// それ以外も沢山あるが、今回は省略してベース部分だけ表示
ShowExpressionBase(expression, level);
}
// Expression のベース部分を表示
static void ShowExpressionBase(Expression expression, int level)
{
ShowText(string.Format("☆Expression: {0}", expression), level);
ShowText(string.Format("ノードタイプ: {0}", expression.NodeType), level + 1);
}
// LambdaExpression (ラムダ式) の中を (再帰的に) 表示
static void ShowLambdaExpression(LambdaExpression expression, int level)
{
ShowExpressionBase(expression, level);
ShowText(string.Format("名前: {0}", expression.Name), level + 1);
ShowText(string.Format("戻り値の型: {0}", expression.ReturnType), level + 1);
ShowParameterExpressions(expression.Parameters, level + 1); // 引数のコレクション
ShowText(string.Format("本体: {0}", expression.Body), level + 1);
expression.Body.Show(level + 2); // 本体を再帰的に表示
}
// BinaryExpression (二項演算式) の中を (再帰的に) 表示
static void ShowBinaryExpression(BinaryExpression expression, int level)
{
ShowExpressionBase(expression, level);
ShowText(string.Format("型: {0}", expression.Type), level + 1);
ShowText(string.Format("左オペランド: {0}", expression.Left), level + 1);
expression.Left.Show(level + 2); // 左オペランドを再帰的に表示
ShowText(string.Format("右オペランド: {0}", expression.Right), level + 1);
expression.Right.Show(level + 2); // 右オペランドを再帰的に表示
}
// UnaryExpression (単項演算式) の中を (再帰的に) 表示
static void ShowUnaryExpression(UnaryExpression expression, int level)
{
ShowExpressionBase(expression, level);
ShowText(string.Format("型: {0}", expression.Type), level + 1);
ShowText(string.Format("オペランド: {0}", expression.Operand), level + 1);
expression.Operand.Show(level + 2); // オペランドを再帰的に表示
}
// 引数の式のコレクションを表示
static void ShowParameterExpressions(ReadOnlyCollection<ParameterExpression> parameterExpressions, int level)
{
ShowText("引数群", level);
if (parameterExpressions == null || parameterExpressions.Count == 0)
ShowText("引数なし", level);
else
parameterExpressions.ForEach((parameterExpression, index) => ShowParameterExpression(parameterExpression, index, level + 1));
}
// 引数の式の中を表示
static void ShowParameterExpression(ParameterExpression parameterExpression, int index, int level)
{
ShowText(string.Format("引数{0}", index + 1), level + 1);
ShowExpressionBase(parameterExpression, level + 1);
ShowText(string.Format("引数の型: {1}, 引数の名前: {2}", parameterExpression.NodeType, parameterExpression.Type, parameterExpression.Name), level + 2);
}
// 文字列をレベルに応じてインデント付で表示
static void ShowText(string itemText, int level)
{
Console.WriteLine("{0}{1}", Indent(level), itemText);
}
// インデントの為の文字列を生成
static string Indent(int level)
{
return level == 0 ? "" : new string(' ', (level - 1) * 4 + 1) + "|-- ";
}
}
この ExpressionViewer クラスを使って、先程の expression の中を見てみよう。
class Program
{
public static void Main()
{
Expression<Func<int, int, int>> expression = (x, y) => x + y;
((Expression)expression).Show();
}
}
実行してみると、こうなる。
☆Expression: (x, y) => (x + y)
|-- ノードタイプ: Lambda
|-- 名前:
|-- 戻り値の型: System.Int32
|-- 引数群
|-- 引数1
|-- ☆Expression: x
|-- ノードタイプ: Parameter
|-- 引数の型: System.Int32, 引数の名前: x
|-- 引数2
|-- ☆Expression: y
|-- ノードタイプ: Parameter
|-- 引数の型: System.Int32, 引数の名前: y
|-- 本体: (x + y)
|-- ☆Expression: (x + y)
|-- ノードタイプ: Add
|-- 型: System.Int32
|-- 左オペランド: x
|-- ☆Expression: x
|-- ノードタイプ: Parameter
|-- 右オペランド: y
|-- ☆Expression: y
|-- ノードタイプ: Parameter
「☆Expression」とあるところが式 (Expression) だ。ツリー構造になっている。
「引数群」のところでは、x と y がそれぞれ引数という種類の式としてネストしている。
「本体」は x + y の部分で、足し算を表す式としてネストしている。
その足し算の左オペランドである x と右オペランドである y が、それぞれ引数タイプの式として更にネストしている。
次に、ちょっとだけラムダ式を複雑にしてみよう。(x, y) => x + y を (x, y, z) => x + y + z に変えてみる。
こうだ。
class Program
{
public static void Main()
{
Expression<Func<int, int, int, int>> expression = (x, y, z) => x + y + z;
((Expression)expression).Show();
}
}
さて、結果はどう変化するだろうか。
☆Expression: (x, y, z) => ((x + y) + z)
|-- ノードタイプ: Lambda
|-- 名前:
|-- 戻り値の型: System.Int32
|-- 引数群
|-- 引数1
|-- ☆Expression: x
|-- ノードタイプ: Parameter
|-- 引数の型: System.Int32, 引数の名前: x
|-- 引数2
|-- ☆Expression: y
|-- ノードタイプ: Parameter
|-- 引数の型: System.Int32, 引数の名前: y
|-- 引数3
|-- ☆Expression: z
|-- ノードタイプ: Parameter
|-- 引数の型: System.Int32, 引数の名前: z
|-- 本体: ((x + y) + z)
|-- ☆Expression: ((x + y) + z)
|-- ノードタイプ: Add
|-- 型: System.Int32
|-- 左オペランド: (x + y)
|-- ☆Expression: (x + y)
|-- ノードタイプ: Add
|-- 型: System.Int32
|-- 左オペランド: x
|-- ☆Expression: x
|-- ノードタイプ: Parameter
|-- 右オペランド: y
|-- ☆Expression: y
|-- ノードタイプ: Parameter
|-- 右オペランド: z
|-- ☆Expression: z
|-- ノードタイプ: Parameter
一段ネストが深くなったのがお判りだろうか。
x + y + z のところが (x + y) + z、即ち「『x + y という足し算の式』を左オペランドとし、z を右オペランドとする足し算の式」になっている。
では、更にネストを深くする為に、もっと複雑なラムダ式を使ってみよう。
「デリゲートを入力としデリゲートを出力とするラムダ式」でやってみる。
例えばこんなやつ。
Func<Func<double, double, double>, Func<int, int, int>> convertFunc = func => ((x, y) => (int)func((double)x, (double)y));
余談だが、一応このラムダ式について説明してみる。
このラムダ式は、「int 二つを引数とし int を返すメソッド」を「double 二つを引数とし double を返すメソッド」に変換する。
double を対象とした足し算を int を対象とした足し算に変換したり、double
を対象とした引き算を int を対象とした引き算に変換したりできることになる。
余り意味のない例だが、こんな感じだ。
using System;
static class Program
{
static double Add(double x, double y)
{
return x + y;
}
static double Subtract(double x, double y)
{
return x - y;
}
public static void Main()
{
Func<Func<double, double, double>, Func<int, int, int>> convertFunc = func => ((x, y) => (int)func((double)x, (double)y));
int answer1 = convertFunc(Add )(1, 2);
int answer2 = convertFunc(Subtract)(4, 3);
}
}
このラムダ式を ExpressionViewer クラスを使ってダンプしてみよう。
class Program
{
public static void Main()
{
Expression<Func<Func<double, double, double>, Func<int, int, int>>> expression = func => ((x, y) => (int)func((double)x, (double)y));
((Expression)expression).Show();
}
}
結果は下のようになる。
☆Expression: func => (x, y) => Convert(Invoke(func, Convert(x), Convert(y)))
|-- ノードタイプ: Lambda
|-- 名前:
|-- 戻り値の型: System.Func`3[System.Int32,System.Int32,System.Int32]
|-- 引数群
|-- 引数1
|-- ☆Expression: func
|-- ノードタイプ: Parameter
|-- 引数の型: System.Func`3[System.Double,System.Double,System.Double], 引数の名前: func
|-- 本体: (x, y) => Convert(Invoke(func, Convert(x), Convert(y)))
|-- ☆Expression: (x, y) => Convert(Invoke(func, Convert(x), Convert(y)))
|-- ノードタイプ: Lambda
|-- 名前:
|-- 戻り値の型: System.Int32
|-- 引数群
|-- 引数1
|-- ☆Expression: x
|-- ノードタイプ: Parameter
|-- 引数の型: System.Int32, 引数の名前: x
|-- 引数2
|-- ☆Expression: y
|-- ノードタイプ: Parameter
|-- 引数の型: System.Int32, 引数の名前: y
|-- 本体: Convert(Invoke(func, Convert(x), Convert(y)))
|-- ☆Expression: Convert(Invoke(func, Convert(x), Convert(y)))
|-- ノードタイプ: Convert
|-- 型: System.Int32
|-- オペランド: Invoke(func, Convert(x), Convert(y))
|-- ☆Expression: Invoke(func, Convert(x), Convert(y))
|-- ノードタイプ: Invoke
「引数群」のところは、単に一つのデリゲートになっている。
「本体」のところは、ラムダ式がネストしている。
そして、ネストしたラムダ式は、通常のラムダ式と同じようにツリー構造に展開されている。
と云う訳で、今回は Expression の構造を見てみた。
次回以降で、愈愈ラムダ式を Expression として利用する例を紹介したい。
「匿名メソッドとラムダ式の違い」と云う記事で、匿名メソッドとラムダ式の意味の違いについて考えた。
それについて、些細な補足をする。
上の記事中で、「ラムダ式を使った方が、型推論の恩恵を存分に受けられ、書き方がぐっとシンプルになる。」、「一般的には、ラムダ式を使うことで書き方もシンプルになることだし、匿名メソッドは使わずにラムダ式で書くようにした方が良いだろう。」と述べた。
一般的にはこれは真実だ。しかし、ごく稀にだが、ラムダ式よりも匿名メソッドの方がシンプルになることがある。
上の記事で述べたように、通常は、ラムダ式の方が型推論が強力なので、シンプルに書ける。
かなり極端な例だが、
using System;
class Subject
{
public event Action<Subject, object, object, object, object, object> Update;
}
class Program
{
static void DoSomething(Subject subject)
{}
static void Main()
{
var subject = new Subject();
// 匿名メソッドで書いた場合
subject.Update += delegate(Subject sender, object a, object b, object c, object d, object e) { DoSomething(sender); };
// ラムダ式で書いた場合
subject.Update += (sender, a, b, c, d, e) => DoSomething(sender);
}
}
ラムダ式の方では、型名の記述その他が省略できてシンプルな記述になっている。
ところが、内部で引数を使わないような下の例ではどうだろう。
using System;
class Subject
{
public event Action<Subject, object, object, object, object, object> Update;
}
class Program
{
static void DoSomething()
{}
static void Main()
{
var subject = new Subject();
// 匿名メソッドで書いた場合
subject.Update += delegate { DoSomething(); };
// ラムダ式で書いた場合
subject.Update += (_, a, b, c, d, e) => DoSomething();
}
}
匿名メソッドの方が、ちょっとだけシンプルになっている気がしないだろうか。
とは云え、これは上で述べているように極端で、そして余り意味のない例だ。
こんなに引数が多いデリゲートなど殆ど必要ないだろう。
矢張り、「一般的には、ラムダ式を使うことで書き方もシンプルになることだし、匿名メソッドは使わずにラムダ式で書くようにした方が良いだろう。」と云って問題ないと思う。
前回「Expression の構造を調べてみる」と云う記事で、Expression の内部のツリー構造を調べた。
その中で、ラムダ式を Expression として扱うことで、式の中の名前が取れることが判った。
今回は これを利用してラムダ式のメンバー名を取得する例を挙げてみたい。
先ずシンプルなクラスを一つ用意する。
class Item
{
public int Id { get; set; }
}
そして、item => item.Id というラムダ式を渡してプロパティ名を取得するメソッドを作ってみよう。
前回の知識が役に立つ。
前回の調査で、ラムダ式の => の右側の式は Body で取れることが解かっている。
次に、Visual Studio のデバッガーなどを駆使して、Body の式の種類を調べよう。Body は MemberExpression であることが判る筈だ。
MemberExpression であれば、プロパティの内の Member の Name で名前が取得できる。
実際にメソッドを作ってみるとこんな感じだ:
using System;
using System.Linq.Expressions;
public static class ObjectExtensions
{
// Expression からメンバー名を取得
public static string GetMemberName<ObjectType, MemberType>(this ObjectType @this, Expression<Func<ObjectType, MemberType>> expression)
{
return ((MemberExpression)expression.Body).Member.Name;
}
}
早速使ってみよう。
using System;
class Item
{
public int Id { get; set; }
}
class Program
{
static void Test()
{
var memberName = new Item().GetMemberName(item => item.Id);
Console.WriteLine(memberName);
}
public static void Main()
{
Test();
}
}
実行結果は下の通りで、ラムダ式からプロパティ名が取得できた。
Id
次に、Item クラスの内部からもやってみよう。
今度のラムダ式は () => Id だ。
ObjectExtensions クラスに先程作ったメソッドから第一引数を無くしたメソッドを追加して、
using System;
using System.Linq.Expressions;
public static class ObjectExtensions
{
// Expression からメンバー名を取得
public static string GetMemberName<ObjectType, MemberType>(this ObjectType @this, Expression<Func<ObjectType, MemberType>> expression)
{
return ((MemberExpression)expression.Body).Member.Name;
}
// Expression からメンバー名を取得
public static string GetMemberName<MemberType>(Expression<Func<MemberType>> expression)
{
return ((MemberExpression)expression.Body).Member.Name;
}
}
Item クラス内部に Test 用のメソッドを追加し、そこから呼んでみる。
using System;
class Item
{
public int Id { get; set; }
public void Test()
{
var memberName = ObjectExtensions.GetMemberName(() => Id);
Console.WriteLine(memberName);
}
}
class Program
{
static void Test()
{
var memberName = new Item().GetMemberName(item => item.Id);
Console.WriteLine(memberName);
}
public static void Main()
{
Test();
new Item().Test();
}
}
実行結果は下の通り。クラス内部からもラムダ式からプロパティ名が取得できた。
Id Id
Expression を利用してラムダ式のメンバー名を取得してみた。
しかし、こんなことが何の役に立つのだろうか?
次回から、これを利用した応用例について書こうと思う。
C# 4 から dynamic が使えるようになった。
動的言語のように、動的にプロパティを参照したり、メソッドを呼んだり出来るようになった訳だ。
そして、.NET Framework では 4 から System.Dynamic と云う名前空間ができた。
今回は、この名前空間の中の DynamicObject を使ってみたい。
DynamicObject は、メソッド呼び出し、プロパティへのアクセス、インデックスによるアクセス等のそれぞれに対応する virtual メソッドを持つ。
例.
| virtual メソッド名 | 説明 |
|---|---|
| TrySetMember | オーバーライドして、プロパティに値が設定されるときの動作を定義できる |
| TryGetMember | オーバーライドして、プロパティから値が取得されるときの動作を定義できる |
| TrySetIndex | オーバーライドして、インデックスを用いて値が設定されるときの動作を定義できる |
| TryGetIndex | オーバーライドして、インデックスを用いて値が取得されるときの動作を定義できる |
| TryInvokeMember | オーバーライドして、メソッドが呼び出されるときの動作を定義できる |
DynamicObject から派生したクラスを作り、これらの virtual メソッド をオーバーライドすることで、動的な動作を定義することができる。
実際に試してみよう。
今回は、TrySetMember と TryGetMember をオーバーライドしてプロパティにアクセスしてみる。
using System;
using System.Dynamic;
class Dynamic : DynamicObject
{
// プロパティに値を設定しようとしたときに呼ばれる
public override bool TrySetMember(SetMemberBinder binder, object value)
{
return base.TrySetMember(binder, value);
}
// プロパティから値を取得しようとしたときに呼ばれる
public override bool TryGetMember(GetMemberBinder binder, out object result)
{
return base.TryGetMember(binder, out result);
}
}
class Program
{
static void Main()
{
dynamic item = new Dynamic();
item.Id = 100; // 実行時エラー (Id の定義がない)
Console.WriteLine(item.Id); // 実行時エラー (Id の定義がない)
item.Name = "田中一郎"; // 実行時エラー (Name の定義がない)
Console.WriteLine(item.Name); // 実行時エラー (Name の定義がない)
}
}
Visual Studio を使って、オーバーライドした TrySetMember と TryGetMember にブレークポイントを置いてデバッグ実行してみると、それぞれプロパティの設定時と取得時にちゃんと呼ばれていることが判る。
base.TrySetMember(binder, value) と base.TryGetMember(binder, out result) は false を返している。
TrySetMember が false を返すとプロパティの設定に失敗し、TryGetMember が false を返すとプロパティの取得に失敗する。
従って、このプログラムを実行してみると、以下のような実行時エラーになる。
TrySetMember と TryGetMember の中をもう少しちゃんと実装して、動くようにしてみよう。
TrySetMember の中で、Dictionary を使って設定した値を覚えるようにしてみる。
そして、同じプロパティが再設定されるときは、型が同じか派生クラスのときだけ許すようにしてみる
(この実装はやや妥当ではないが、今回はこうしてみる)。
そして、TryGetMember の中では、Dictionary から値を取り出して返すようにしてみよう。
こんな感じだ。
using System;
using System.Collections.Generic;
using System.Dynamic;
class Dynamic : DynamicObject
{
// observableTarget の該当するプロパティ名毎に型と値を格納
Dictionary<string, Tuple<Type, object>> values = new Dictionary<string, Tuple<Type, object>>();
// プロパティに値を設定しようとしたときに呼ばれる
public override bool TrySetMember(SetMemberBinder binder, object value)
{
Tuple<Type, object> tuple;
// 該当するプロパティの型と値を values から取得
if (values.TryGetValue(binder.Name, out tuple)) {
var valueType = value.GetType();
// もしプロパティに設定しようとしている値の型が、そのプロパティの型もしくはそのサブクラスでなかったら
if (!valueType.Equals(tuple.Item1) && !valueType.IsSubclassOf(tuple.Item1))
return false;
// 元の型の儘で値を再設定
values[binder.Name] = new Tuple<Type, object>(tuple.Item1, value);
return true;
}
// 型と値を新規に格納
values[binder.Name] = new Tuple<Type, object>(value.GetType(), value);
return true;
}
// プロパティから値を取得しようとしたときに呼ばれる
public override bool TryGetMember(GetMemberBinder binder, out object result)
{
Tuple<Type, object> tuple;
// 該当するプロパティの型と値を values から取得
if (values.TryGetValue(binder.Name, out tuple)) {
result = tuple.Item2;
return true;
}
result = null;
return false;
}
}
class Program
{
static void Main()
{
dynamic item = new Dynamic();
item.Id = 100; // OK
Console.WriteLine(item.Id); // OK
//item.Id = "田中一郎"; // 実行時エラー (異なった型の値を設定しようとしている)
item.Name = "田中一郎"; // OK
Console.WriteLine(item.Name); // OK
//Console.WriteLine(item.Address); // 実行時エラー (設定していないプロパティの値を取得しようとしている)
}
}
今度の実行結果は次の通り。
100 田中一郎
item.Id に異なった型の値を設定しようとしたり、設定していないプロパティの値を取得しようとしたりしている箇所は、実行時エラーになる。
それ以外は正常に動く。
プロパティの再設定時に、再設定する値の型が派生クラスなら設定できるが派生クラスでなければ設定できないことも、確認しておこう。
class Super {}
class Sub : Super {}
class Program
{
static void Main()
{
dynamic item = new Dynamic();
item.Property1 = new Super(); // OK
item.Property1 = new Sub(); // OK
item.Property2 = new Sub(); // OK
item.Property2 = new Super(); // 実行時エラー (型が違うし、派生クラスでもない)
}
}
最後の、型が同じでも派生クラスでもなかったときだけ実行時エラーとなる。
次に、同じ名前空間 System.Dynamic の中の ExpandoObject も試してみよう。
DynamicObject は派生して使うが、ExpandoObject は派生せずにその儘使う。
こんな感じだ。
using System;
using System.Dynamic;
class Program
{
static void Main()
{
dynamic item = new ExpandoObject();
item.Id = 100; // OK
Console.WriteLine(item.Id); // OK
item.Id = "田中一郎"; // OK
Console.WriteLine(item.Id); // OK
item.Name = "田中一郎"; // OK
Console.WriteLine(item.Name); // OK
//Console.WriteLine(item.Address); // 実行時エラー(設定していないプロパティの値を取得しようとしている)
}
}
実行結果は次の通り。
100 田中一郎 田中一郎
ExpandoObject はその儘で、動的にプロパティを設定したり、取得したりできる。
こちらは、最後の設定してないプロパティを取得しようとしたとき以外は、実行時エラーにならない。
今回は、DynamicObject を使ってみた。
近く、これの応用例として、DynamicObject をオブザーバー パターンの実装に利用してみる予定だ。
お楽しみに。
前回の「DynamicObject を使ってみよう」の続き。
前回は、DynamicObject と ExpandoObject を使ってみた。
DynamicObject の派生クラスや ExpandoObject は連想配列のように機能した。
但し、通常の連想配列とはインタフェイスが異なる。
今回も、引き続き DynamicObject と ExpandoObject を使ってみよう。
DynamicObject の派生クラスがインタフェイスの異なる連想配列のように使える、と云うことを利用してちょっとしたラッパー クラスを作ってみよう。
例えば、ASP.NET や ASP.NET MVC では、セッション変数を利用する際、以下のように連想配列のように使う。
Session["Id"] = 100;
int? id = Session["Id"] as int?;
DynamicObject を利用したラッパー クラスを作ってみよう。
例えば、こんな感じだ。
using System.Dynamic;
using System.Web;
public class SessionObject : DynamicObject
{
readonly HttpSessionStateBase session;
public SessionObject(HttpSessionStateBase session)
{
this.session = session;
}
// プロパティに値を設定しようとしたときに呼ばれる
public override bool TrySetMember(SetMemberBinder binder, object value)
{
session[binder.Name] = value;
return true;
}
// プロパティから値を取得しようとしたときに呼ばれる
public override bool TryGetMember(GetMemberBinder binder, out object result)
{
result = session[binder.Name];
return true;
}
}
すると、従来のこんな書き方が、
// ASP.NET MVC の場合の一例 (従来の書き方)
using System.Web;
using System.Web.Mvc;
using System.Web.UI;
public class HomeController : Controller
{
public ActionResult Index()
{
var id = Session["Id"]; // まだ Session["Id"] は無いので、id は null
Session["Id"] = 100; // Session["Id"] に 100 を設定
id = Session["Id"]; // id には 100 が返ってくる
var item = Session["Item"]; // まだ Session["Item"] は無いので、item は null
Session["Item"] = new { Id = 200, Name = "田中一郎" }; // Session["Item"] に匿名型のオブジェクトを設定
id = DataBinder.Eval(Session["Item"], "Id"); // id には 200 が返ってくる
return View();
}
}
SessionObject クラスを使うことによって、こんな書き方になる。
// ASP.NET MVC の場合の一例 (SessionObject クラスを使った場合)
using System.Web;
using System.Web.Mvc;
public class HomeController : Controller
{
dynamic session = null;
public ActionResult Index()
{
session = new SessionObject(Session);
var id = session.Id; // まだ session.Id は無いので、id は null
session.Id = 100; // session.Id に 100 を設定
id = session.Id; // id には 100 が返ってくる
var item = session.Item; // まだ session.Item は無いので、item は null
session.Item = new { Id = 200 }; // session.Item に匿名型のオブジェクトを設定
id = session.Item.Id; // id には 200 が返ってくる
return View();
}
}
dynamic に書けることで、コードがややシンプルになった。
一応、Visual Studio でデバッグ実行し、デバッガーを使って値をチェックしてみると、次のようになった。
まあ、実用的な意味合いは余りないが、連想配列的な部分には、使える可能性がある、と云う一例だ。
次は、ExpandoObject だ。
前回は、ExpandoObject を使って動的なプロパティっぽいものを実現した。
再度やってみよう。
using System;
using System.Dynamic;
class Program
{
static void Main()
{
dynamic item = new ExpandoObject();
// プロパティっぽいものを追加 その1
item.Id = 10;
Console.WriteLine(item.Id);
}
}
実行結果は次の通り。
10
もうちょっと複雑な例。
using System;
using System.Dynamic;
class Program
{
static void Main()
{
dynamic item = new ExpandoObject();
// プロパティっぽいものを追加 その2
item.SubItem = new { Id = 100, Name = "田中一郎" };
Console.WriteLine(item.SubItem);
Console.WriteLine(item.SubItem.Id);
Console.WriteLine(item.SubItem.Name);
}
}
実行結果は次の通り。
{ Id = 100, Name = 田中一郎 }
100
田中一郎
オブジェクトが何でも追加できるのであれば、デリゲートだって追加できる筈だ。
デリゲートが追加できるのであれば、メソッドっぽいものも実現できるのではないか。
試しに、static メソッドっぽいものの追加を試してみよう。
こんな感じ。
using System;
using System.Dynamic;
class Program
{
static void Main()
{
dynamic item = new ExpandoObject();
// static メソッドっぽいものを追加
item.SetId = new Action<dynamic, int>((o, value) => { o.Id = value; });
item.GetId = new Func<dynamic, int>(o => o.Id);
// 呼んでみる
item.SetId(item, 30);
Console.WriteLine(item.GetId(item));
}
}
実行結果は次の通り。
30
デリゲートが追加できるのであれば、もうちょっとイベントっぽくしてみよう。
こんな感じだ。
using System;
using System.Dynamic;
class Program
{
static void Main()
{
dynamic item = new ExpandoObject();
// イベントっぽいものを追加
// 1. 先ず、Update と云う名のイベントっぽいものを用意
item.Update = null;
// 2. 次に、Update にイベントハンドラーっぽいものを追加
item.Update += new Action<dynamic, EventArgs>((sender, _) => Console.WriteLine(sender.Name));
// 3. そして、SetName と云うメソッドっぽいものを用意して、中で Update と云うイベントっぽいものを起こす
item.SetName = new Action<dynamic, string>(
(o, value) => {
o.Name = value;
if (o.Update != null)
o.Update(o, EventArgs.Empty);
}
);
// 4. では、試してみよう:
// SetName を呼ぶと Update が起きて Console.WriteLine(sender.Name) されるかな?
item.SetName(item, "田中次郎");
}
}
実行結果は次の通り。
田中次郎
イベントっぽい。
ここまでを纏めてみる。最後にメンバーの一覧を取ってみよう。
using System;
using System.Dynamic;
class Program
{
static void Main()
{
dynamic item = new ExpandoObject();
// プロパティっぽいものを追加 その1
item.Id = 10;
Console.WriteLine(item.Id);
// プロパティっぽいものを追加 その2
item.SubItem = new { Id = 20, Name = "田中一郎" };
Console.WriteLine(item.SubItem);
Console.WriteLine(item.SubItem.Id);
Console.WriteLine(item.SubItem.Name);
// static メソッドっぽいものを追加
item.SetId = new Action<dynamic, int>((o, value) => { o.Id = value; });
item.GetId = new Func<dynamic, int>(o => o.Id);
// 呼んでみる
item.SetId(item, 30);
Console.WriteLine(item.GetId(item));
// イベントっぽいものを追加
// 1. 先ず、Update と云う名のイベントっぽいものを用意
item.Update = null;
// 2. 次に、Update にイベントハンドラーっぽいものを追加
item.Update += new Action<dynamic, EventArgs>((sender, _) => Console.WriteLine(sender.Name));
// 3. そして、SetName と云うメソッドっぽいものを用意して、中で Update と云うイベントっぽいものを起こす
item.SetName = new Action<dynamic, string>(
(o, value) => {
o.Name = value;
if (o.Update != null)
o.Update(o, EventArgs.Empty);
}
);
// 4. では、試してみよう:
// SetName を呼ぶと Update が起きて、イベントハンドラーの中で Console.WriteLine(sender.Name) されるかな?
item.SetName(item, "田中次郎");
// メンバーの一覧の取得
Console.WriteLine("\nメンバーの一覧:\n");
foreach (var member in item)
Console.WriteLine(member);
}
}
実行結果は次のようになった。
10
{ Id = 20, Name = 田中一郎 }
20
田中一郎
30
田中次郎
メンバーの一覧:
[Id, 30]
[SubItem, { Id = 20, Name = 田中一郎 }]
[SetId, System.Action`2[System.Object,System.Int32]]
[GetId, System.Func`2[System.Object,System.Int32]]
[Update, System.Action`2[System.Object,System.EventArgs]]
[SetName, System.Action`2[System.Object,System.String]]
[Name, 田中次郎]
追加したプロパティっぽいものや、メソッドっぽいもの、イベントっぽいものが、型が違うだけのオブジェクトとして追加されているのが判る。
因みに、ExpandoObject は、IDictionary<string, Object> を実装しているので、こんな風に一覧を取ることができるし、要素数を取得したり、要素を削除したりすることもできる。
今回も、DynamicObject と ExpandoObject を使ってみた。
dynamic な動作は、実行時にオーバーヘッドが大きいので、そこは要注意だが、応用例を考えてみると面白いのではないだろうか。
動的処理の一例として、今回はプラグイン処理を行ってみる。
プラグイン処理によって、アプリケーションに対して動的に機能を追加できるようにすることができる。
今回のプラグインは、以下のような規約ベースで動くものとする。
では、実装してみよう。
先ずプラグイン側だ。
プラグインは、クラスライブラリとして作成する。
public なクラスを持ち、その中には、public な Name プロパティと public な Run() メソッドを持てば良い。
using System;
public class Plugin
{
public string Name
{
get { return "Sample Plugin"; }
}
public void Run()
{
Console.WriteLine("This is sample plugin!");
}
}
次はプラグインが組み込まれる本体側だ。
using System;
using System.IO;
using System.Linq;
using System.Reflection;
class Program
{
// プラグインのフォルダー名
const string pluginFolderName = "Plugin";
// 拡張子が dll のものをプラグインと看做す
const string pluginFileName = "*.dll";
static void Main()
{
// プラグインのフォルダーへのフルパス名を取得し、
var pluginFolderName = GetPluginFolderName();
// もしプラグインのフォルダーが在ったら、
if (Directory.Exists(pluginFolderName))
// プラグインのフォルダーの各のプラグインのパス名を使って、プラグインを実行
Directory.GetFiles(pluginFolderName, pluginFileName).ToList().ForEach(Run);
}
// プラグインのフォルダーへのフルパス名を取得
static string GetPluginFolderName()
{
// 実行中のこのプログラム自体のアセンブリのパスのフォルダーの下の Plugin フォルダーへのフルパスを取得
return Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) + Path.DirectorySeparatorChar + pluginFolderName;
}
// プラグインを実行
static void Run(string pluginPath)
{
// アセンブリを読み込み、その中から public な最初のクラスを取り出す
var pluginType = Assembly.LoadFrom(pluginPath).GetExportedTypes().FirstOrDefault(type => type.IsClass);
if (pluginType != null)
// インスタンスを生成し、それをプラグインとして実行
Run(Activator.CreateInstance(pluginType));
}
// プラグインを実行
static void Run(dynamic plugin)
{
Console.WriteLine(plugin.Name); // プラグインの Name を表示し、
plugin.Run(); // プラグインを Run
}
}
規約ベースなので、特にプラグイン側は interface 等は持っていないし、本体側も interface で探したりしていない。
規約通りのものを実行する。
双方が特定の interface に依存しているのでなく、規約に依存している。
こういう場合は、dynamic による処理が合っている。
実行してみよう。
先ずは、Plugin フォルダーを準備しない儘で実行してみる。
何も表示されない。
次に、プログラムを実行しているフォルダーの下に Plugin と云う名前のフォルダーを作成して実行してみる。
矢張り、何も表示されない。
次に、クラスライブラリであるプラグインをビルドし、出来上がった dll ファイルを Plugin フォルダーの直下にコピーし、実行してみる。
Sample Plugin This is sample plugin!
プラグインが実行された。
Plugin フォルダーの dll を二つにしてみると、
Sample Plugin This is sample plugin! Sample Plugin This is sample plugin!
両方のプラグインが実行される。
因みに、interface を用いた場合の例だと次のようになる。
先ず interface をクラス ライブラリとして準備する。
public interface IPlugin
{
string Name { get; }
void Run();
}
プラグイン側で interface のライブラリを参照し、クラスで IPlugin を実装する。
public class Plugin : IPlugin
{
public string Name
{
get { return "Sample Plugin"; }
}
public void Run()
{
Console.WriteLine("This is sample plugin!");
}
}
本体側でも interface のライブラリを参照する。
プラグイン側と本体側が同じ interface に依存することになる。
interface を持ったクラスを探して、interface によって Name や Run() にアクセスする。
先の例と違って、dynamic は使わない。
using System;
using System.IO;
using System.Linq;
using System.Reflection;
class Program
{
// プラグインのフォルダー名
const string pluginFolderName = "Plugin";
// 拡張子が dll のものをプラグインと看做す
const string pluginFileName = "*.dll";
static void Main()
{
// プラグインのフォルダーへのフルパス名を取得し、
var pluginFolderName = GetPluginFolderName();
// もしプラグインのフォルダーが在ったら、
if (Directory.Exists(pluginFolderName))
// プラグインのフォルダーの各のプラグインのパス名を使って、プラグインを実行
Directory.GetFiles(pluginFolderName, pluginFileName).ToList().ForEach(Run);
}
// プラグインのフォルダーへのフルパス名を取得
static string GetPluginFolderName()
{
// 実行中のこのプログラム自体のアセンブリのパスのフォルダーの下の Plugin フォルダーへのフルパスを取得
return Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) + Path.DirectorySeparatorChar + pluginFolderName;
}
// プラグインを実行
static void Run(string pluginPath)
{
// アセンブリを読み込み、その中から public で IPlugin インタフェイスを持った最初のクラスを取り出す
var pluginType = Assembly.LoadFrom(pluginPath).GetExportedTypes().FirstOrDefault(type => type.IsClass && typeof(IPlugin).IsAssignableFrom(type));
if (pluginType != null)
// インスタンスを生成し、それをプラグインとして実行
Run((IPlugin)Activator.CreateInstance(pluginType));
}
// プラグインを実行
static void Run(IPlugin plugin)
{
Console.WriteLine(plugin.Name); // プラグインの Name を表示し、
plugin.Run(); // プラグインを Run
}
}
今回は、プラグイン処理をやってみた。
interface を使ってやることもできるが、規約ベースで dynamic を使ってやる方法の方が依存関係がシンプルになる。
dynamic な動作は、実行時にオーバーヘッドが大きいので、そこは要注意だが、応用例を考えてみると面白いのではないだろうか。
「Expression を使ってラムダ式のメンバー名を取得する」と云う記事で、ラムダ式の中のメンバーの名前を取得した。
これから数回に渡って、Observer パターンの C# による実装を何種類か例に挙げ、その中で上記を活用していきたい。
今回は、その第一回として、Expression どころか C# の event すら使わない、Observer パターンの最も古典的な実装を見てみよう。
先ず Observer パターンの簡単な説明から。
Observer パターンは、GoF による 23 種のデザインパターンの中の一つで、特にポピュラーなものの一つだ。
早速 C# で Observer パターンを実装していこう。
今回は、手始めに上の参考資料で紹介されているような由緒ある方法で実装してみる。
クラス図はこんな感じ。
上半分の青い部分がフレームワーク部分だ。
IObserver が Observable を監視している。
Observable の Add で IObserver を実装したオブジェクト (= オブザーバー) を追加する。
Observable は更新されると、以下の手順で全オブザーバーに更新があったことを通知してオブザーバーの表示が更新されるようにする。
下半分の赤い部分がアプリケーション部分だ。
Employee は Observable だ。
Model として Number と Name と云う二つのプロパティを持っている。
EmployeeView がオブザーバーだ。
EmployeeView は Employee を表示するための View で、DataSource として Employee が渡されると、Employee のオブザーバーとして自分自身を追加する。
EmployeeView は、表示部として TextControl を二つ持っていて、それぞれに DataSource の Number と Name を表示する。
それでは、実装していこう。
一部エラー処理を省略する。
先に、フレームワーク部の IObserver と Observable から実装しよう。
こんな感じだ。
// C# による Oberver パターンの実装 その1
using System;
using System.Collections.Generic;
// フレームワーク部
interface IObserver // 更新を監視する側
{
void Update(Observable observable);
}
abstract class Observable // 更新を監視される側
{
List<IObserver> observers = new List<IObserver>();
public void Add(IObserver observer) // オブサーバーの追加
{
observers.Add(observer);
}
protected void Update() // 更新イベント
{
NorifyObservers();
}
void NorifyObservers() // 全オブザーバーに更新を通知
{
observers.ForEach(observer => observer.Update(this));
}
}
続いて、アプリケーション部。ここには、Employee と EmployeeView、TextControl、そして Main を含んだクラス Program がある。
// C# による Oberver パターンの実装 その1
using System;
// アプリケーション部
// Model
class Employee : Observable
{
int number = 0;
string name = string.Empty;
public int Number
{
get { return number; }
set
{
if (value != number) {
number = value;
Update();
}
}
}
public string Name
{
get { return name; }
set
{
if (value != name) {
name = value;
Update();
}
}
}
}
class TextControl // テキスト表示用のUI部品 (ダミー)
{
public string Text
{
set { Console.WriteLine("TextControl is updated: {0}", value); }
}
}
class EmployeeView : IObserver // Employee 用の View
{
TextControl numberTextControl = new TextControl(); // Number 表示用
TextControl nameTextControl = new TextControl(); // Name 表示用
public Employee DataSource
{
set { value.Add(this); }
}
public void Update(Observable observable) // データソースのどのプロパティが更新されてもここに来る
{
var employee = observable as Employee;
if (employee != null) {
numberTextControl.Text = employee.Number.ToString();
nameTextControl.Text = employee.Name;
}
}
}
class Program
{
static void Main()
{
var employee = new Employee(); // Model, Observable
var employeeView = new EmployeeView(); // View, Observer
employeeView.DataSource = employee; // データバインド
employee.Number = 100; // Number を変更。employeeView に反映されたかな?
employee.Name = "福井太郎"; // Name を変更。employeeView に反映されたかな?
}
}
実行してみると、次のようになる。
TextControl is updated: 100 TextControl is updated: TextControl is updated: 100 TextControl is updated: 福井太郎
Employee の Number や Name が更新される度に、EmployeeView の Update が呼ばれ、二つの TextControl の表示が更新される。
Number の更新でも Name の更新でも同じ Update が呼ばれている。
ここは、出来ればそれぞれ処理したいところだ。
さて、Observer パターンの C# による実装の第一回として、古典的な実装を行ってみた。
interface と抽象クラスを用いてフレームワーク部を書いている。
次回は、もう少し C# らしい、event を用いた実装をやってみよう。
前回「C# による Observer パターンの実装 その1 - 古典的な実装」と云う記事で、Observer パターンの C# による実装の第一回として、古典的な実装を行ってみた。
interface と抽象クラスを用いた実装だった訳だが、こうした場合、C# では event を用いるのが普通だろう。
今回は、「C# による Observer パターンの実装 その2」として、event による実装を行ってみる。
クラス図はこんな感じ。
前回 と比べるとフレームワーク部 (抽象部) が無くなっているのが判る。
C# が持っている event が強力なので、それだけで更新によって呼ばれるべき処理が呼べてしまうのだ。
Employee は Model として Number と Name と云う二つのプロパティを持っている。
また、Employee は Update というイベントを持っている。
Employee は更新されると Update イベントを起こす。
Update イベントが起きたときに、もしそのイベント ハンドラーがあれば、それが呼ばれる。
EmployeeView は Employee を表示するための View で、DataSource として Employee が渡されると、Employee の Update イベントにイベント ハンドラーとして Update メソッドを設定する。
EmployeeView は、表示部として TextControl を二つ持っていて、それぞれに DataSource の Number と Name を表示する。
それでは、実装していこう (一部エラー処理を省略)。
// C# による Oberver パターンの実装 - event による実装 1
using System;
using System.Collections.Generic;
// アプリケーション部
// Model: 更新を監視される側
class Employee
{
public event Action<Employee> Update; // 更新イベント
int number = 0;
string name = string.Empty;
public int Number
{
get { return number; }
set {
if (value != number) {
number = value;
if (Update != null)
Update(this);
}
}
}
public string Name
{
get { return name; }
set {
if (value != name) {
name = value;
if (Update != null)
Update(this);
}
}
}
}
class TextControl // テキスト表示用のUI部品 (ダミー)
{
public string Text {
set { Console.WriteLine("TextControl is updated: {0}", value); }
}
}
class EmployeeView // Employee 用の View: 更新を監視する側
{
TextControl numberTextControl = new TextControl(); // Number 表示用
TextControl nameTextControl = new TextControl(); // Name 表示用
public Employee DataSource
{
set {
value.Update += Update; // データソースの更新イベントにハンドラを設定
}
}
void Update(Employee employee) // データソースのどのプロパティが更新されてもここに来る
{
numberTextControl.Text = employee.Number.ToString();
nameTextControl.Text = employee.Name;
}
}
class Program
{
static void Main()
{
var employee = new Employee(); // Model, Observable
var employeeView = new EmployeeView(); // View, Observer
employeeView.DataSource = employee; // データバインド
employee.Number = 100; // Number を変更。employeeView に反映されたかな?
employee.Name = "福井太郎"; // Name を変更。employeeView に反映されたかな?
}
}
実行してみると、次のようになる。
前回と同じ結果だ。
TextControl is updated: 100 TextControl is updated: TextControl is updated: 100 TextControl is updated: 福井太郎
今回も矢張り、Employee の Number や Name が更新される度に、EmployeeView の Update が呼ばれ、二つの TextControl の表示が更新される。
上では、Number と Name のどちらが更新されても両方の TextControl が更新されている。
これは、Number と Name のどちらが更新されても同じイベントが起きるようになっている為だ。
更新イベントを分けて、それぞれの TextControl が更新されるように改良してみよう 。
クラス図はこうなる。
一つだった Employee 側の Update イベントが、UpdateNumber と UpdateName に分かれている。
それでは、実装してみよう (一部エラー処理を省略)。
// C# による Oberver パターンの実装 - event による実装 2
using System;
using System.Collections.Generic;
// アプリケーション部
// Model: 更新を監視される側
class Employee
{
public event Action<Employee> UpdateNumber; // Number 更新イベント
public event Action<Employee> UpdateName; // Name 更新イベント
int number = 0;
string name = string.Empty;
public int Number
{
get { return number; }
set {
if (value != number) {
number = value;
if (UpdateNumber != null)
UpdateNumber(this);
}
}
}
public string Name
{
get { return name; }
set {
if (value != name) {
name = value;
if (UpdateName != null)
UpdateName(this);
}
}
}
}
class TextControl // テキスト表示用のUI部品 (ダミー)
{
public string Text {
set { Console.WriteLine("TextControl is updated: {0}", value); }
}
}
class EmployeeView // Employee 用の View: 更新を監視する側
{
TextControl numberTextControl = new TextControl(); // Number 表示用
TextControl nameTextControl = new TextControl(); // Name 表示用
public Employee DataSource
{
set {
// それぞれの更新イベントでそれぞれ TextControl を更新
value.UpdateNumber += employee => numberTextControl.Text = employee.Number.ToString();
value.UpdateName += employee => nameTextControl.Text = employee.Name;
}
}
}
class Program
{
static void Main()
{
var employee = new Employee(); // Model, Observable
var employeeView = new EmployeeView(); // View, Observer
employeeView.DataSource = employee; // データバインド
employee.Number = 100; // Number を変更。employeeView に反映されたかな?
employee.Name = "福井太郎"; // Name を変更。employeeView に反映されたかな?
}
}
実行してみると、次のようになる。
Number と Name それぞれの更新で、それぞれの TextControl が更新されている。
TextControl is updated: 100 TextControl is updated: 福井太郎
それぞれで更新されるようになったのは良いが、Employee 側でプロパティ毎にイベントを用意しなければいけない。
これではプロパティの数が増えてくると、コードが複雑になりそうだ。
さて、Observer パターンの C# による実装の第二回として、C# の event を用いた実装を行ってみた。
前回のようなフレームワーク部がない。
次回は、フレームワーク部を復活させてみる。
前回「C# による Observer パターンの実装 その2 - event による実装」と云う記事で、Observer パターンの C# による実装の第二回として、C# の event を用いた実装を行ってみた。
event を用いることで、より C# らしい実装となった。
「C# での Observer パターンの実装 1 - 古典的な実装」のフレームワーク部にあたる部分を C# の event 機構で行ってしまっている訳だ。
すっきりとした実装となったが、監視される側 (Observer 側) でプロパティ毎にイベントを用意しなければいけない、という面があった。
「プロパティの数が増えてくると、コードが複雑になりそう」と云う点が気になった。
そこで、今回は、「C# による Observer パターンの実装 その3 - 複数のプロパティの更新イベントをフレームワーク側で振り分け」として、その2で無くなったフレームワーク部を復活させ、そちらに複数のイベント処理を任せてみる。
今回のクラス図はこんな感じだ。
「C# による Observer パターンの実装 その1」のときと同様、上半分の青い部分がフレームワーク部、下半分の赤い部分がアプリケーション部だ。
先ずは、フレームワーク部の方から。
Observable (更新を監視される側) は、今回は、一つだけの Update というイベントを持つ。
どのプロパティが更新されてもこのイベントを起こす。
但し、プロパティ名を引数で受け取るようになっている。
RaiseUpdate と云うメソッドに文字列でプロパティ名を渡すことで、Update イベントが起きる。
また、Observer (更新を監視する側) は、 DataSource として Observable が渡されると、Observable の Update イベントにイベント ハンドラーとして Update メソッドを設定する。
AddUpdateAction は、プロパティの名称毎に処理を登録するメソッドだ。
Observable 側で更新イベントが起きてイベント ハンドラーの Update が呼ばれると、プロパティの名毎の処理が呼ばれる仕組みだ。
最後に、ObjectExtensions だが、このクラスは、オブジェクトのプロパティ名からプロパティの値を取り出すために使われるメソッド Eval を持つ。
このメソッド Eval は、Observer の中で、プロパティ名からプロパティの値を取り出すために使われる。
Employee は Observable だ。
今回も Model として Number と Name と云う二つのプロパティを持っている。
Update イベントは、ここには無い。
各プロパティが更新されたときに、RaiseUpdate をプロパティ名を引数にして呼ぶことで、ベース クラス Observable の持つ Update イベントを起こす。
EmployeeView はオブザーバーだ。
今回も、EmployeeView は Employee を表示するための View で、AddUpdateAction メソッドを使って、プロパティ毎の更新処理を登録する。
それでは、実装していこう (一部エラー処理を省略)。
フレームワーク部の ObjectExtensions と Observable、Observer から実装しよう。
// C# による Oberver パターンの実装 その3
using System;
using System.Collections.Generic;
// フレームワーク部
public static class ObjectExtensions
{
// オブジェクトの指定された名前のプロパティの値を取得
public static object Eval(this object item, string propertyName)
{
var propertyInfo = item.GetType().GetProperty(propertyName);
return propertyInfo == null ? null : propertyInfo.GetValue(item, null);
}
}
abstract class Observable // 更新を監視される側
{
public event Action<string> Update;
protected void RaiseUpdate(string propertyName)
{
if (Update != null)
Update(propertyName);
}
}
abstract class Observer // 更新を監視する側
{
Dictionary<string, Action<object>> updateExpressions = new Dictionary<string, Action<object>>();
Observable dataSource = null;
public Observable DataSource
{
set {
dataSource = value;
value.Update += Update;
}
}
protected void AddUpdateAction(string propertyName, Action<object> updateAction)
{
updateExpressions[propertyName] = updateAction;
}
void Update(string propertyName)
{
Action<object> updateAction;
if (updateExpressions.TryGetValue(propertyName, out updateAction))
updateAction(dataSource.Eval(propertyName));
}
}
続いて、アプリケーション部。Employee と EmployeeView、TextControl、そして Main を含んだクラス Program だ。
// C# による Oberver パターンの実装 その3
using System;
// アプリケーション部
// Model
class Employee : Observable
{
int number = 0;
string name = string.Empty;
public int Number
{
get { return number; }
set {
if (value != number) {
number = value;
RaiseUpdate("Number");
}
}
}
public string Name
{
get { return name; }
set {
if (value != name) {
name = value;
RaiseUpdate("Name");
}
}
}
}
class TextControl // テキスト表示用のUI部品 (ダミー)
{
public string Text
{
set { Console.WriteLine("TextControl is updated: {0}", value); }
}
}
class EmployeeView : Observer // Employee 用の View
{
TextControl numberTextControl = new TextControl(); // Number 表示用
TextControl nameTextControl = new TextControl(); // Name 表示用
public EmployeeView()
{
AddUpdateAction("Number", number => numberTextControl.Text = ((int)number).ToString());
AddUpdateAction("Name", name => nameTextControl.Text = (string)name);
}
}
class Program
{
static void Main()
{
var employee = new Employee(); // Model, Observable
var employeeView = new EmployeeView(); // View, Observer
employeeView.DataSource = employee; // データバインド
employee.Number = 100; // Number を変更。employeeView に反映されたかな?
employee.Name = "福井太郎"; // Name を変更。employeeView に反映されたかな?
}
}
先のクラス図に、引数と戻り値の型を書き加えたものを示す。
実行してみると、次のようになる。
前回の二つ目と同じ結果だ。
TextControl is updated: 100 TextControl is updated: 福井太郎
Number と Name それぞれの更新で、それぞれの TextControl が更新されている。
今回は、フレームワーク部で、プロパティ毎の更新処理が呼ばれるようにした。
結果として、アプリケーション部の Employee でプロパティ毎に更新イベントを用意する必要がなくなった。
しかし、アプリケーション部の一部に文字列でプロパティを指定する部分が出来てしまった。
Employee の中の、RaiseUpdate("Number") と RaiseUpdate("Name")、EmployeeView の中の AddUpdateAction("Number", ...) と AddUpdateAction("Name", ...) だ。
次回は、この問題を解決しよう。
前回「C# による Observer パターンの実装 その3 - 複数のプロパティの更新イベントをフレームワーク側で振り分け」と云う記事で、Observer パターンの C# による実装の第三回として、フレームワーク部で、プロパティ毎の更新処理が呼ばれるようにした。
結果として、アプリケーション部の Employee でプロパティ毎に更新イベントを用意する必要がなくなった。
しかし、アプリケーション部の一部に文字列でプロパティを指定する部分が出来てしまった。
そこで、今回は、「C# による Observer パターンの実装 その4 - Expression を使ってプロパティの指定をタイプセーフに」として、文字列でプロパティを指定する部分を解消してみよう。
以前、「Expression を使ってラムダ式のメンバー名を取得する」と云う記事で、Expression を使ってラムダ式からプロパティ名を取得したことがある。
これを用いることが出来る。
前回の ObjectExtensions に、件の記事から、Expression からメンバー名を取得する部分を持ってこよう。
using System;
using System.Linq.Expressions;
public static class ObjectExtensions
{
// オブジェクトの指定された名前のプロパティの値を取得 (前回作成)
public static object Eval(this object item, string propertyName)
{
var propertyInfo = item.GetType().GetProperty(propertyName);
return propertyInfo == null ? null : propertyInfo.GetValue(item, null);
}
// Expression からメンバー名を取得 (「Expression を使ってラムダ式のメンバー名を取得する」で作成)
public static string GetMemberName<ObjectType, MemberType>(this ObjectType @this, Expression<Func<ObjectType, MemberType>> expression)
{
return ((MemberExpression)expression.Body).Member.Name;
}
// Expression からメンバー名を取得 (「Expression を使ってラムダ式のメンバー名を取得する」で作成)
public static string GetMemberName<MemberType>(Expression<Func<MemberType>> expression)
{
return ((MemberExpression)expression.Body).Member.Name;
}
}
そして、前回のフレームワーク部の Observable と Observer のソースコード:
// C# による Oberver パターンの実装 その3 (前回)
using System;
using System.Collections.Generic;
// フレームワーク部
abstract class Observable // 更新を監視される側
{
public event Action<string> Update;
protected void RaiseUpdate(string propertyName)
{
if (Update != null)
Update(propertyName);
}
}
abstract class Observer // 更新を監視する側
{
Dictionary<string, Action<object>> updateExpressions = new Dictionary<string, Action<object>>();
Observable dataSource = null;
public Observable DataSource
{
set {
dataSource = value;
value.Update += Update;
}
}
protected void AddUpdateAction(string propertyName, Action<object> updateAction)
{
updateExpressions[propertyName] = updateAction;
}
void Update(string propertyName)
{
Action<object> updateAction;
if (updateExpressions.TryGetValue(propertyName, out updateAction))
updateAction(dataSource.Eval(propertyName));
}
}
これの、文字列でプロパティ名を受けている箇所に、Expression で受けるメソッドを追加する。
こうだ。
// C# による Oberver パターンの実装 その4
using System;
using System.Collections.Generic;
using System.Linq.Expressions;
abstract class Observable // 更新を監視される側
{
public event Action<string> Update;
protected void RaiseUpdate<PropertyType>(Expression<Func<PropertyType>> propertyExpression)
{
RaiseUpdate(ObjectExtensions.GetMemberName(propertyExpression));
}
void RaiseUpdate(string propertyName)
{
if (Update != null)
Update(propertyName);
}
}
abstract class Observer<ObservableType> where ObservableType : Observable // 更新を監視する側
{
Dictionary<string, Action<object>> updateExpressions = new Dictionary<string, Action<object>>();
ObservableType dataSource = null;
public ObservableType DataSource
{
set {
dataSource = value;
value.Update += Update;
}
}
protected void AddUpdateAction<PropertyType>(Expression<Func<ObservableType, PropertyType>> propertyExpression, Action<object> updateAction)
{
AddUpdateAction(dataSource.GetMemberName(propertyExpression), updateAction);
}
void AddUpdateAction(string propertyName, Action<object> updateAction)
{
updateExpressions[propertyName] = updateAction;
}
void Update(string propertyName)
{
Action<object> updateAction;
if (updateExpressions.TryGetValue(propertyName, out updateAction))
updateAction(dataSource.Eval(propertyName));
}
}
すると、前回のアプリケーション部の Employee と EmployeeView のソースコード:
// C# による Oberver パターンの実装 その3 (前回)
using System;
// アプリケーション部
// Model
class Employee : Observable
{
int number = 0;
string name = string.Empty;
public int Number
{
get { return number; }
set {
if (value != number) {
number = value;
RaiseUpdate("Number"); // ☆ 文字列でプロパティを指定
}
}
}
public string Name
{
get { return name; }
set {
if (value != name) {
name = value;
RaiseUpdate("Name"); // ☆ 文字列でプロパティを指定
}
}
}
}
class TextControl // テキスト表示用のUI部品 (ダミー)
{
public string Text
{
set { Console.WriteLine("TextControl is updated: {0}", value); }
}
}
class EmployeeView : Observer // Employee 用の View
{
TextControl numberTextControl = new TextControl(); // Number 表示用
TextControl nameTextControl = new TextControl(); // Name 表示用
public EmployeeView()
{
// ☆ AddUpdateAction の第一引数で文字列でプロパティを指定
AddUpdateAction("Number", number => numberTextControl.Text = ((int)number).ToString());
AddUpdateAction("Name", name => nameTextControl.Text = (string)name);
}
}
これが、こうなる。
// C# による Oberver パターンの実装 その4
using System;
// アプリケーション部
// Model
class Employee : Observable
{
int number = 0;
string name = string.Empty;
public int Number
{
get { return number; }
set {
if (value != number) {
number = value;
RaiseUpdate(() => Number); // ☆ RaiseUpdate がタイプセーフに
}
}
}
public string Name
{
get { return name; }
set {
if (value != name) {
name = value;
RaiseUpdate(() => Name); // ☆ RaiseUpdate がタイプセーフに
}
}
}
}
class TextControl // テキスト表示用のUI部品 (ダミー)
{
public string Text
{
set { Console.WriteLine("TextControl is updated: {0}", value); }
}
}
class EmployeeView : Observer<Employee> // Employee 用の View
{
TextControl numberTextControl = new TextControl(); // Number 表示用
TextControl nameTextControl = new TextControl(); // Name 表示用
public EmployeeView()
{
// ☆ AddUpdateAction の第一引数がタイプセーフに
AddUpdateAction(employee => employee.Number, number => numberTextControl.Text = ((int)number).ToString());
AddUpdateAction(employee => employee.Name, name => nameTextControl.Text = (string)name);
}
}
文字列でプロパティを指定している箇所が消えた。
Main を含んだクラス Program は、変更なし。
class Program
{
static void Main()
{
var employee = new Employee(); // Model, Observable
var employeeView = new EmployeeView(); // View, Observer
employeeView.DataSource = employee; // データバインド
employee.Number = 100; // Number を変更。employeeView に反映されたかな?
employee.Name = "福井太郎"; // Name を変更。employeeView に反映されたかな?
}
}
実行結果も変わらない。
TextControl is updated: 100 TextControl is updated: 福井太郎
矢張り、Number と Name それぞれの更新で、それぞれの TextControl が更新されている。
さてクラス図はこうなった。
前回と余り変わらない。
例によって、青い部分がフレームワーク部、赤い部分がアプリケーション部だ。
今回は、前回のものに対して、プロパティを文字列で指定している部分を改良した。
そうすることで、アプリケーション部でプロパティを指定する箇所がタイプセーフになった。
アプリケーション部が随分すっきりと書けるようになったのが判る。
さて次回だが、今回 Expression を使って行ったのと同様のことを別の方法で実現してみたい。
お楽しみに。
Windows 8 の ストア アプリ開発を始めるにあたり、参考にさせていただいたサイトをご紹介する。
以前、「Windows Store アプリと Windows Phone アプリ、Silverlight アプリ、WPF アプリでソースコードを共通化する方法に関する記事」と云う記事でポータブル クラス ライブラリに関して少しだけご紹介した。
今回は、ポータブル クラス ライブラリについて、更に調べてみよう。
Windows ストア アプリで参照している .NET は、他のアプリケーションで参照しているものと少し異なる。
その様子を先ず確認してみよう。
現時点で最新の環境で何種類かのアプリケーションをデフォルトで追加してみて、参照している .NET を見てみる。
「.NET for Windows Phone」となっている。
こちらでは、「.NET for Windows Store apps」となっているのが判る。
つまり、これらで参照している .NET は、全てが共通な訳ではない。
コア部分は共通なのだろうか?
私が試してみたところ、System 名前空間の付近でも微妙な違いがあるようだ。
今回は、以下のようなコードを用いて、この辺りを検証してみたい。
// コンソール アプリケーション
// .NET Framework 4.5
using System.Reflection; // ※ GetRuntimeProperties に必要
class Program
{
class Super { }
class Sub : Super
{
public int Number { get; set; }
public string Name { get; set; }
}
static void Test()
{
var sub = new Sub();
bool result1 = sub.GetType().IsSubclassOf(typeof(Super));
bool result2 = sub.GetType().IsAssignableFrom(typeof(Super));
bool result3 = sub is Super;
var properties1 = typeof(Sub).GetProperties();
var properties2 = typeof(Sub).GetRuntimeProperties();
}
static void Main()
{
Test();
}
}
これはコンソール アプリケーションのソースコードだが、正常にコンパイルでき、正常に動作する。
Visual Studio のデバッガーで値を確認してみると、次のようになった。
同様のことを、WPF と Silverlight の場合で試してみると次のようになる。
先ず WPF から。
// WPF アプリケーション
// .NET Framework 4.5
using System.Windows;
namespace WpfApplication
{
using System.Reflection; // ※ GetRuntimeProperties に必要
public partial class App : Application
{
class Super { }
class Sub : Super
{
public int Number { get; set; }
public string Name { get; set; }
}
static void Test()
{
var sub = new Sub();
bool result1 = sub.GetType().IsSubclassOf(typeof(Super)); // ○ OK
bool result2 = sub.GetType().IsAssignableFrom(typeof(Super)); // ○ OK
bool result3 = sub is Super; // ○ OK
var properties1 = typeof(Sub).GetProperties(); // ○ OK
var properties2 = typeof(Sub).GetRuntimeProperties(); // ○ OK
}
public App()
{
Test();
}
}
}
WPF では問題なく、全てコンパイルでき、正常に動作する。
参照している .NET が同じ .NET Framework 4.5 なので、当たり前と云えば当たり前だ。
次に Silverlight。
// Silverlight 5
using System;
using System.Windows;
namespace SilverlightApplication
{
using System.Reflection; // ※ GetRuntimeProperties に必要
public partial class App : Application
{
class Super { }
class Sub : Super
{
public int Number { get; set; }
public string Name { get; set; }
}
static void Test()
{
var sub = new Sub();
bool result1 = sub.GetType().IsSubclassOf(typeof(Super)); // ○ OK
bool result2 = sub.GetType().IsAssignableFrom(typeof(Super)); // ○ OK
bool result3 = sub is Super; // ○ OK
var properties1 = typeof(Sub).GetProperties(); // ○ OK
var properties2 = typeof(Sub).GetRuntimeProperties(); // × コンパイル エラー
}
public App()
{
Test();
// ... 以下省略 ...
}
// ... 以下省略 ...
}
}
Silverlight の方は、次のようなコンパイル エラーになる。
この GetRuntimeProperties は、 System.Reflection 名前空間の RuntimeReflectionExtensions クラスが持つ拡張メソッドだ。
.NET Framework 4.5 で使えるようになったものだが、Silverlight では使えないようだ。
ちなみに、Windows Phone では次のようになる。
// Windows Phone OS 8.0
using Microsoft.Phone.Controls;
using Microsoft.Phone.Shell;
using PhoneApp.Resources;
using System.Diagnostics;
using System.Windows;
using System.Windows.Markup;
using System.Windows.Navigation;
namespace PhoneApp
{
using System;
using System.Reflection; // ※ GetRuntimeProperties に必要
public partial class App : Application
{
class Super { }
class Sub : Super
{
public int Number { get; set; }
public string Name { get; set; }
}
static void Test()
{
var sub = new Sub();
bool result1 = sub.GetType().IsSubclassOf(typeof(Super)); // ○ OK
bool result2 = sub.GetType().IsAssignableFrom(typeof(Super)); // ○ OK
bool result3 = sub is Super; // ○ OK
var properties1 = typeof(Sub).GetProperties(); // ○ OK
var properties2 = typeof(Sub).GetRuntimeProperties(); // ○ OK
}
public App()
{
Test();
// ... 以下省略 ...
}
// ... 以下省略 ...
}
}
全て問題なくコンパイル・実行できる。
さて、Windows ストア アプリではどうなるだろうか。
こうなるのだ。
// Windows ストア アプリ
using System;
using Windows.ApplicationModel;
using Windows.ApplicationModel.Activation;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
namespace WindowsStoreApp
{
using System.Reflection; // ※ GetRuntimeProperties に必要
sealed partial class App : Application
{
class Super { }
class Sub : Super
{
public int Number { get; set; }
public string Name { get; set; }
}
static void Test()
{
var sub = new Sub();
bool result1 = sub.GetType().IsSubclassOf(typeof(Super)); // × コンパイル エラー
bool result2 = sub.GetType().IsAssignableFrom(typeof(Super)); // × コンパイル エラー
bool result3 = sub is Super; // ○ OK
var properties1 = typeof(Sub).GetProperties(); // × コンパイル エラー
var properties2 = typeof(Sub).GetRuntimeProperties(); // ○ OK
}
public App()
{
Test();
// ... 以下省略 ...
this.InitializeComponent();
this.Suspending += OnSuspending;
}
// ... 以下省略 ...
}
}
次のようなコンパイル エラーとなる。
なんと、System 名前空間の Type 型が IsSubclassOf や IsAssignableFrom を持っていないようだ。
実際に調べてみると、Windows ストア アプリが参照している System 名前空間の Type 型は以下の public メンバーを持っている。
// Windows ストア アプリが参照している System 名前空間の Type 型
// アセンブリ System.Runtime.dll, v4.0.0.0
// Framework\.NETCore\v4.5\System.Runtime.dll
namespace System
{
public abstract class Type
{
public static readonly object Missing;
public abstract string AssemblyQualifiedName { get; }
public abstract Type DeclaringType { get; }
public abstract string FullName { get; }
public abstract int GenericParameterPosition { get; }
public abstract Type[] GenericTypeArguments { get; }
public bool HasElementType { get; }
public bool IsArray { get; }
public bool IsByRef { get; }
public abstract bool IsConstructedGenericType { get; }
public abstract bool IsGenericParameter { get; }
public bool IsNested { get; }
public bool IsPointer { get; }
public abstract string Name { get; }
public abstract string Namespace { get; }
public virtual RuntimeTypeHandle TypeHandle { get; }
public override bool Equals(object o);
public bool Equals(Type o);
public abstract int GetArrayRank();
public abstract Type GetElementType();
public abstract Type GetGenericTypeDefinition();
public override int GetHashCode();
public static Type GetType(string typeName);
public static Type GetType(string typeName, bool throwOnError);
public static Type GetTypeFromHandle(RuntimeTypeHandle handle);
public abstract Type MakeArrayType();
public abstract Type MakeArrayType(int rank);
public abstract Type MakeByRefType();
public abstract Type MakeGenericType(params Type[] typeArguments);
public abstract Type MakePointerType();
public override string ToString();
}
}
IsSubclassOf、IsAssignableFrom、GetProperties 等が見当たらない。
一方、通常の .NET Framework 4.5 の名前空間の Type 型は遥かに多くの public メンバーを持っている。
// 通常の .NET Framework 4.5 の名前空間の Type 型
// アセンブリ mscorlib.dll, v4.0.0.0
// Framework\.NETFramework\v4.5\mscorlib.dll
namespace System
{
public abstract class Type : MemberInfo, _Type, IReflect
{
bool IsSubclassOf(Type c);
bool IsAssignableFrom(Type c);
PropertyInfo[] GetProperties();
// ...その他遥かに多くのメンバー...
}
}
先ず、アセンブリが異なる。Windows ストア アプリの方は、 mscorlib.dll ではなく System.Runtime.dll だった。
また、こちらは Windows ストア アプリの方の Type と異なり、「class Type : MemberInfo, _Type, IReflect」となっているのが判る。
この中の _Type は実は interface で、以下のように IsSubclassOf、IsAssignableFrom、GetProperties を含む多くのメンバーを持っているのだ。
// 通常の .NET Framework 4.5 の名前空間の Type 型が実装している interface _Type
namespace System.Runtime.InteropServices
{
public interface _Type
{
bool IsSubclassOf(Type c);
bool IsAssignableFrom(Type c);
PropertyInfo[] GetProperties();
// ...その他多くのメンバー...
}
}
では、ポータブル クラス ライブラリを利用した場合はどうなるだろうか。
先ず、ポータブル クラス ライブラリを作成する。
今回は、ターゲット フレームワークとしてデフォルトの儘、.NET Framework 4.5、Silverlight 4 以上、Windows Phone 7 以上、.NET for Windows Store apps を選ぶ。
ソースコードは以下の通り。
Type 型の IsSubclassOf、IsAssignableFrom、GetProperties をそれぞれ呼ぶだけのメソッドを用意することにする。
// ポータブル クラス ライブラリ
//
// ターゲット フレームワーク:
// ・.NET Framework 4.5
// ・Silverlight 4 以上
// ・Windows Phone 7 以上
// ・.NET for Windows Store apps
namespace PortableClassLibrary
{
using System;
using System.Reflection;
public static class TestClass
{
public static bool IsSubclassOf(Type sub, Type super)
{
return sub.IsSubclassOf(super);
}
public static bool IsAssignableFrom(Type type1, Type type2)
{
return type1.IsAssignableFrom(type2);
}
public static PropertyInfo[] GetProperties(Type type)
{
return type.GetProperties();
}
}
}
これは問題なくコンパイルできる。
ちなみに、このポータブル クラス ライブラリで参照している Type 型は次のようなものだ。
// このポータブル クラス ライブラリが参照している Type 型
// アセンブリ mscorlib.dll, v2.0.5.0
// Framework\.NETPortable\v4.0\Profile\Profile4\mscorlib.dll
namespace System
{
public abstract class Type : MemberInfo
{
bool IsSubclassOf(Type c);
bool IsAssignableFrom(Type c);
PropertyInfo[] GetProperties();
// ...その他多くのメンバー...
}
}
こちらは、IsSubclassOf、IsAssignableFrom、GetProperties 等を持っている。_Type interface は持っていない。
また、このポータブル クラス ライブラリが参照している .NET はこうなっている。
「.NET Portable Subset」となっている。
では、早速このポータブル クラス ライブラリを各アプリケーション (コンソール アプリケーション、WPF アプリケーション、Silverlight アプリケーション、Windows Phone アプリ、Windows ストア アプリ) でそれぞれ参照してみよう。
そして、各アプリケーションで、以下のようにこのポータブル クラス ライブラリの三つのメソッド、IsSubclassOf、IsAssignableFrom、GetProperties を呼んでみる。
// 参照したポータブル クラス ライブラリの利用
bool result4 = PortableClassLibrary.TestClass.IsSubclassOf(sub.GetType(), typeof(Super));
bool result5 = PortableClassLibrary.TestClass.IsAssignableFrom(sub.GetType(), typeof(Super));
var properties3 = PortableClassLibrary.TestClass.GetProperties(sub.GetType());
すると、コンソール アプリケーション、WPF アプリケーション、Silverlight アプリケーション、Windows Phone アプリ、Windows ストア アプリの何れでも、コンパイルでき、正常に動作する。
つまり、ポータブル クラス ライブラリにであれば、この部分のコードは共通化でき、且つそれぞれのアプリケーションから問題なく呼べるのだ。
今回は、ポータブル クラス ライブラリに関して、調査をしてみた。
アプリケーションの種類によって、参照している .NET が異なるので、.NET のコア部分を使ったソースコードでも共通のものが使えないことがあることが判った。
ポータブル クラス ライブラリを使うことで、そのような部分のソースコードをより共通化することができるだろう。
前回「C# による Observer パターンの実装 その4 - Expression を使ってプロパティの指定をタイプセーフに」と云う記事で、Observer パターンの C# による実装の第四回として、Expression を用いることで、前々回文字列でプロパティを指定していた部分をタイプセーフにしてみた。
今回は、それに対する補足だ。
前回の Expression による遣り方とは別の方法で更にアプリケーション部をシンプルにしてみたい。
C# 5 の新機能の Caller Info を使った方法だ。
先ず前回のフレームワーク部の Observable のソースコードを再掲する。
// C# による Oberver パターンの実装 その4
using System;
using System.Collections.Generic;
using System.Linq.Expressions;
abstract class Observable // 更新を監視される側
{
public event Action<string> Update;
protected void RaiseUpdate<PropertyType>(Expression<Func<PropertyType>> propertyExpression)
{
RaiseUpdate(ObjectExtensions.GetMemberName(propertyExpression));
}
void RaiseUpdate(string propertyName)
{
if (Update != null)
Update(propertyName);
}
}
Observable の RaiseUpdate において、Expression で受けることでプロパティ名を動的に取得している。
この部分を C#5.0 の CallerInfo を使う形で書き換えてみよう。
Caller Info 属性の一つである [CallerMemberName] を付けておくと、呼び出し元のメンバー名 (この場合はプロパティ名) がコンパイラーによって渡されてくる。
これを利用してみよう。
// C# による Oberver パターンの実装 その5
using System;
using System.Runtime.CompilerServices; // ☆ [CallerMemberName] の為に必要
abstract class Observable // 更新を監視される側
{
public event Action<string> Update;
protected void RaiseUpdate([CallerMemberName] string propertyName = "") // ☆ Caller Info 属性を利用
{
if (Update != null)
Update(propertyName);
}
}
すると、Observable の派生クラスで、プロパティ名を渡さなくて良くなる。
前回の Employee はこうだった。
// C# による Oberver パターンの実装 その4
using System;
// アプリケーション部
// Model
class Employee : Observable
{
int number = 0;
string name = string.Empty;
public int Number
{
get { return number; }
set {
if (value != number) {
number = value;
RaiseUpdate(() => Number); // ☆ RaiseUpdate がタイプセーフに
}
}
}
public string Name
{
get { return name; }
set {
if (value != name) {
name = value;
RaiseUpdate(() => Name); // ☆ RaiseUpdate がタイプセーフに
}
}
}
}
これがこうなる。
// C# による Oberver パターンの実装 その5
using System;
// アプリケーション部
// Model
class Employee : Observable
{
int number = 0;
string name = string.Empty;
public int Number
{
get { return number; }
set {
if (value != number) {
number = value;
RaiseUpdate(); // ☆ RaiseUpdate の中で文字列でプロパティを渡す必要がなくなった
}
}
}
public string Name
{
get { return name; }
set {
if (value != name) {
name = value;
RaiseUpdate(); // ☆ RaiseUpdate の中で文字列でプロパティを渡す必要がなくなった
}
}
}
}
前回のような動的にプロパティ名を取得する方法と異なり、コンパイラーが渡してくれるので、オーバーヘッドも小さくなる筈だ。
全体のソースコードはこうなった。
// C# による Oberver パターンの実装 その5
using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Runtime.CompilerServices; // ☆ [CallerMemberName] の為に必要
// フレームワーク部
public static class ObjectExtensions
{
// オブジェクトの指定された名前のプロパティの値を取得
public static object Eval(this object item, string propertyName)
{
var propertyInfo = item.GetType().GetProperty(propertyName);
return propertyInfo == null ? null : propertyInfo.GetValue(item, null);
}
// Expression からメンバー名を取得
public static string GetMemberName<ObjectType, MemberType>(this ObjectType @this, Expression<Func<ObjectType, MemberType>> expression)
{
return ((MemberExpression)expression.Body).Member.Name;
}
}
abstract class Observable // 更新を監視される側
{
public event Action<string> Update;
protected void RaiseUpdate([CallerMemberName] string propertyName = "") // ☆ Caller Info 属性を利用
{
if (Update != null)
Update(propertyName);
}
}
abstract class Observer<ObservableType> where ObservableType : Observable // 更新を監視する側
{
Dictionary<string, Action<object>> updateExpressions = new Dictionary<string, Action<object>>();
ObservableType dataSource = null;
public ObservableType DataSource
{
set {
dataSource = value;
value.Update += Update;
}
}
protected void AddUpdateAction<PropertyType>(Expression<Func<ObservableType, PropertyType>> propertyExpression, Action<object> updateAction)
{
AddUpdateAction(dataSource.GetMemberName(propertyExpression), updateAction);
}
void AddUpdateAction(string propertyName, Action<object> updateAction)
{
updateExpressions[propertyName] = updateAction;
}
void Update(string propertyName)
{
Action<object> updateAction;
if (updateExpressions.TryGetValue(propertyName, out updateAction))
updateAction(dataSource.Eval(propertyName));
}
}
// C# による Oberver パターンの実装 その5
using System;
// アプリケーション部
// Model
class Employee : Observable
{
int number = 0;
string name = string.Empty;
public int Number
{
get { return number; }
set {
if (value != number) {
number = value;
RaiseUpdate(); // ☆ RaiseUpdate の中で文字列でプロパティを渡す必要がなくなった
}
}
}
public string Name
{
get { return name; }
set {
if (value != name) {
name = value;
RaiseUpdate(); // ☆ RaiseUpdate の中で文字列でプロパティを渡す必要がなくなった
}
}
}
}
class TextControl // テキスト表示用のUI部品 (ダミー)
{
public string Text
{
set { Console.WriteLine("TextControl is updated: {0}", value); }
}
}
class EmployeeView : Observer<Employee> // Employee 用の View
{
TextControl numberTextControl = new TextControl(); // Number 表示用
TextControl nameTextControl = new TextControl(); // Name 表示用
public EmployeeView()
{
AddUpdateAction(employee => employee.Number, number => numberTextControl.Text = ((int)number).ToString());
AddUpdateAction(employee => employee.Name, name => nameTextControl.Text = (string)name);
}
}
class Program
{
static void Main()
{
var employee = new Employee(); // Model, Observable
var employeeView = new EmployeeView(); // View, Observer
employeeView.DataSource = employee; // データバインド
employee.Number = 100; // Number を変更。employeeView に反映されたかな?
employee.Name = "福井太郎"; // Name を変更。employeeView に反映されたかな?
}
}
勿論、実行結果に変わりはない。
TextControl is updated: 100 TextControl is updated: 福井太郎
今回は、前回のものに対して補足を行った。
Caller Info を使うことで、アプリケーション部でプロパティを指定する箇所が更にシンプルになった。
次回は、DynamicObject を使った全然別のアプローチを試してみたい。
お楽しみに。
前回の「プラグイン処理」の続き。
今回は、前回のコードに少し付け足して、様々な種類のプラグインに対応してみよう。
前回は、DLL だけをプラグインとして使えるようにしたが、今回は、それに加えて、C# と Python のプラグインも使えるようにしてみたい。
今回のプラグインも、前回同様、以下のような規約ベースで動くものとする。
では、プラグイン側から実装してみよう。
今回用意するのは、以下の三種類だ。
DLL プラグインは、クラスライブラリとして作成し、ビルドして "Test.dll" とする。
public なクラスを持ち、その中には、public な Name() メソッドと public な Run() メソッドを持つ。
// DLL プラグイン (クラスライブラリとして作成し、ビルドして "Test.dll" に)
using System;
public class Plugin
{
public string Name()
{
return "DLL Plugin";
}
public void Run()
{
Console.WriteLine("DLL Plugin is running!");
}
}
C# プラグインは、一つの cs ファイルだ。"Test.cs" として保存する。
public なクラスを持ち、その中には、public な Name() メソッドと public な Run() メソッドを持つ。
// C# プラグイン ("Test.cs" として保存)
using System;
public class Plugin
{
public string Name()
{
return "C# Plugin";
}
public void Run()
{
Console.WriteLine("C# Plugin is running!");
}
}
Python プラグインは、一つの py ファイルだ。"Test.py" として保存する。
Name() メソッドと Run() メソッドを持つ。
# Python Plugnin (Save as "Test.py".) def Name(): return "Python plugin" def Run(): print "Python plugin is running!\n"
次に、プラグインが組み込まれる本体側の実装だ。
先ず、Python をプラグインとして使えるようにするために、IronPython をインストールしよう。
IronPython は、Visual Studio で NuGet からインストール出来る。
IronPython のインストールが終わると、プロジェクトの参照設定は、次のように IronPython を使う為の参照が追加されている。
では、本体側を実装しよう。
using IronPython.Hosting; // Python プラグインの処理に必要
using Microsoft.CSharp; // C# プラグインの処理に必要
using System;
using System.CodeDom.Compiler; // C# プラグインの処理に必要
using System.IO;
using System.Linq;
using System.Reflection;
class Program
{
// プラグインのフォルダー名
const string pluginFolderName = "Plugin";
static void Main()
{
// プラグインのフォルダーへのフルパス名を取得し、
var pluginFolderName = GetPluginFolderName();
// もしプラグインのフォルダーが在ったら、
if (Directory.Exists(pluginFolderName))
// プラグインのフォルダーの各のプラグインのパス名を使って、プラグインを実行
Directory.GetFiles(pluginFolderName, "*.*").ToList().ForEach(Run);
}
// プラグインのフォルダーへのフルパス名を取得
static string GetPluginFolderName()
{
// 実行中のこのプログラム自体のアセンブリのパスのフォルダーの下の Plugin フォルダーへのフルパスを取得
return Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) + Path.DirectorySeparatorChar + pluginFolderName;
}
// プラグインを実行
static void Run(string pluginPath)
{
switch (Path.GetExtension(pluginPath).ToLower()) {
case ".dll": /* DLL の場合 */ RunDll(pluginPath); break;
case ".cs" : /* C# のコードの場合 */ RunCSharp(pluginPath); break;
case ".py": /* Python のコードの場合 */ RunPython(pluginPath); break;
}
}
// DLL プラグインを実行
static void RunDll(string path)
{
// DLL をアセンブリとして読み込む
var assembly = Assembly.LoadFrom(path);
if (assembly != null)
// アセンブリをプラグインとして実行
Run(assembly);
}
// C# プラグインを実行
static void RunCSharp(string pathName)
{
// C# のコードをアセンブリに変換
var assembly = CodeToAssembly(pathName);
if (assembly != null)
// アセンブリに変換されたプラグインを実行
Run(assembly);
}
// Python プラグインを実行
static void RunPython(string pathName)
{
dynamic plugin = Python.CreateRuntime().UseFile(pathName); // Python のコードからランタイムを作成し、
Run(plugin); // それを実行
}
// プラグインを実行
static void Run(Assembly pluginAssembly)
{
// アセンブリを読み込み、その中から public な最初のクラスを取り出す
var pluginType = pluginAssembly.GetExportedTypes().FirstOrDefault(type => type.IsClass);
if (pluginType != null)
// インスタンスを生成し、それをプラグインとして実行
Run(Activator.CreateInstance(pluginType));
}
// プラグインを実行
static void Run(dynamic plugin)
{
Console.WriteLine(plugin.Name()); // プラグインの Name を表示し、
plugin.Run(); // プラグインを Run
}
// C# のコードをコンパイルしてアセンブリに変換
public static Assembly CodeToAssembly(string csharpCode)
{
using (var cscp = new CSharpCodeProvider()) {
// コンパイルした結果のアセンブリを返す
return cscp.CompileAssemblyFromFile(new CompilerParameters { GenerateInMemory = true }, csharpCode).CompiledAssembly;
}
}
}
dynamic を使うことで、Python のプラグインも同じように実行することができる。
では、実行してみよう。
プログラムを実行しているフォルダーの下に Plugin と云う名前のフォルダーを作成し、そこに三つのプラグイン "Test.dll"、"Test.cs"、"Test.py" を置く。
本体プログラムの実行結果は次の通りだ。
DLL Plugin DLL Plugin is running! C# Plugin C# Plugin is running! Python plugin Python plugin is running!
各プラグインが実行された。
今回は、前回のプラグイン処理に少し補足を行った。
Visual Basic.NET や F#、IronRuby 等も同様に扱えるのでないだろうか。
本ブログでは、これ迄五回に渡り、C# による Observer パターンの実装をご紹介してきた。
前前回の「C# による Observer パターンの実装 その4 - Expression を使ってプロパティの指定をタイプセーフに」と前回の「C# による Observer パターンの実装 その5 - Caller Info を使ってプロパティの指定をよりシンプルに」と云う記事では、Expression や Caller Info を用いることで、第三回で文字列でプロパティを指定していた部分をシンプルな記述にしてみた。
今回は、同じく第三回からの改良を行ってみたい。
第四回、第五回の遣り方とは別のアプローチでアプリケーション部をシンプルにしてみたい。
以前、「DynamicObject を使ってみよう」や「DynamicObject を使ってみよう その 2」と云う記事で DynamicObject をご紹介した。
これらの記事で、DynamicObject を用いることで、プロパティの設定時の処理を定義出来ることを示した。
今回は、この仕組みを用いて、フレームワーク部の Observable で動的にプロパティの更新を捕まえ、更新イベントを発行してみよう。
全体像をざっと把握していただくために、先ずクラス図を示そう。
引数と戻り値の型を書き加えたクラス図だとこうなる。
例によって、青い部分がフレームワーク部、赤い部分がアプリケーション部だ。
では、フレームワーク部から実装していこう。
第三回の ObjectExtensions を少し書き換えて、今回の DynamicObject を用いた例に対応できるようにする。
using System.Dynamic;
// フレームワーク部
public static class ObjectExtensions
{
// オブジェクトの指定された名前のプロパティの値を取得
public static object Eval(this object item, string propertyName, out bool isSucceeded)
{
var propertyInfo = item.GetType().GetProperty(propertyName);
if (propertyInfo == null) {
isSucceeded = false;
return null;
}
isSucceeded = true;
return propertyInfo.GetValue(item, null);
}
// オブジェクトの指定された名前のプロパティの値を設定
public static void SetPropertyValue(this object item, string propertyName, object value)
{
var propertyInfo = item.GetType().GetProperty(propertyName);
if (propertyInfo != null)
propertyInfo.SetValue(item, value, null);
}
// DynamicObject の指定された名前のプロパティの値を取得
public static object GetPropertyValue(this DynamicObject item, string propertyName)
{
object result;
return item.TryGetMember(new MyGetMemberBinder(propertyName), out result) ? result : null;
}
// GetPropertyValue 用
class MyGetMemberBinder : GetMemberBinder
{
public MyGetMemberBinder(string name) : base(name, false)
{ }
public override DynamicMetaObject FallbackGetMember(DynamicMetaObject target, DynamicMetaObject errorSuggestion)
{ return null; }
}
}
object に指定された名前のプロパティの値を設定する拡張メソッドや、DynamicObject から指定された名前のプロパティの値を取得する拡張メソッドを追加した。
DynamicContainer は、「DynamicObject を使ってみよう」に出てきたのと同様、DynamicObject の派生クラスだ。
あの時と同じように、TrySetMember と TryGetMember をオーバーライドする。
あの時との違いは、target としてのオブジェクトを持ち、値を設定したり取得したりする際はそちらに行うことだ。
従って、Dictionary の中には値を保持する必要がない。
最初に target を受け取った際に、その target の全プロパティの型情報のみ格納しておく
(本当は、各プロパティが public な set と public な get の両方を持つかどうかの情報も保持した方が良いが、今回は省略)。
using System;
using System.Collections.Generic;
using System.Dynamic;
using System.Linq;
using System.Reflection;
// フレームワーク部
// 任意のオブジェクトを格納してプロパティの値の変化を監視
class DynamicContainer : DynamicObject
{
// 対象とするアイテム
object target;
// observableTarget の該当するプロパティ名毎に型を格納
Dictionary<string, Type> types = new Dictionary<string, Type>();
public DynamicContainer(object target)
{
this.target = target;
SetProperties(target.GetType()); // 対象とする型の全プロパティを格納
}
public override bool TrySetMember(SetMemberBinder binder, object value)
{
Type type;
// 該当するプロパティの型を values から取得
if (types.TryGetValue(binder.Name, out type)) {
var valueType = value.GetType();
// もしプロパティに設定しようとしている値の型が、そのプロパティの型もしくはそのサブクラスだったら
if (valueType.Equals(type) || valueType.IsSubclassOf(type)) {
// target のプロパティに値を設定
target.SetPropertyValue(binder.Name, value);
return true;
}
}
return false;
}
public override bool TryGetMember(GetMemberBinder binder, out object result)
{
// target のプロパティから値を取得
bool isSucceeded;
result = target.Eval(binder.Name, out isSucceeded);
return isSucceeded;
}
// 対象とする型の全プロパティを格納
void SetProperties(Type type)
{
type.GetProperties().ToList().ForEach(SetProperty);
}
// 対象とするアイテムのプロパティを格納
void SetProperty(PropertyInfo propertyInfo)
{
types[propertyInfo.Name] = propertyInfo.PropertyType;
}
}
次に、DynamicContainer を継承して DynamicObservable を作る。
前回迄の Observable にあたるクラスだ。
Observable と異なり、抽象クラスではない。
これまでは、イベントを起こす為のメソッド RaiseUpdate をアプリケーション側のモデルで、変更されるプロパティ毎に呼ぶ必要があった。
この DynamicObservable では、TrySetMember をオーバーライドし、もし値が更新された場合は、自ら Update イベントを起こす。
これにより、アプリケーション部のモデルがシンプルになる筈だ。
using System;
using System.Dynamic;
// フレームワーク部
class DynamicObservable : DynamicContainer // 更新を監視される側
{
public event Action<string> Update;
public DynamicObservable(object target) : base(target)
{ }
public override bool TrySetMember(SetMemberBinder binder, object value)
{
object oldValue = this.GetPropertyValue(binder.Name);
if (base.TrySetMember(binder, value)) {
if (!value.Equals(oldValue))
RaiseUpdate(binder.Name);
return true;
}
return false;
}
void RaiseUpdate(string propertyName)
{
if (Update != null)
Update(propertyName);
}
}
フレームワーク部の実装の最後は、DynamicObserver だ。
前回迄の Observer にあたるクラスだ。
このクラスは、DataSource が Observable から DynamicObservable に変化した以外は、変更はない。
using System;
using System.Collections.Generic;
// フレームワーク部
abstract class DynamicObserver // 更新を監視する側
{
Dictionary<string, Action<object>> updateExpressions = new Dictionary<string, Action<object>>();
DynamicObservable dataSource = null;
public DynamicObservable DataSource
{
set {
dataSource = value;
value.Update += Update;
}
}
protected void AddUpdateAction(string propertyName, Action<object> updateAction)
{
updateExpressions[propertyName] = updateAction;
}
void Update(string propertyName)
{
Action<object> updateAction;
if (updateExpressions.TryGetValue(propertyName, out updateAction))
updateAction(dataSource.GetPropertyValue(propertyName));
}
}
次に、アプリケーション部だ。
どんな風にシンプルになっただろう。
Employee は、こうなる。
プロパティが設定される毎に Update イベントを起こさなくて良くなったため、随分シンプルだ。
特定のクラスからの派生が不要な POCO (Plain Old CLR Object) になった。
// アプリケーション部
// Model
class Employee
{
public int Number { get; set; }
public string Name { get; set; }
}
EmployeeView は、こうだ。
第三回から全く変化していない。
using System;
// アプリケーション部
class TextControl // テキスト表示用のUI部品 (ダミー)
{
string text = string.Empty;
public string Text
{
set { text = value; Console.WriteLine("TextControl is updated: {0}", value); }
get { return text; }
}
}
class EmployeeView : DynamicObserver // Employee 用の View
{
TextControl numberTextControl = new TextControl(); // Number 表示用
TextControl nameTextControl = new TextControl(); // Name 表示用
public EmployeeView()
{
AddUpdateAction("Number", number => numberTextControl.Text = number.ToString());
AddUpdateAction("Name", name => nameTextControl.Text = (string)name);
}
}
Main を含んだクラス Program では、少し変更がある。
EmployeeView のデータソースに DynamicObservable を用いるようにする。
using System;
// アプリケーション部
class Program
{
static void Main()
{
var employee = new Employee(); // Model
dynamic observableEmployee = new DynamicObservable(employee); // Observable
var employeeView = new EmployeeView(); // View, Observer
employeeView.DataSource = observableEmployee; // データバインド
observableEmployee.Number = 100; // Number を変更。employeeView に反映されたかな?
observableEmployee.Name = "福井太郎"; // Name を変更。employeeView に反映されたかな?
}
}
勿論、実行結果に変わりはない。
TextControl is updated: 100 TextControl is updated: 福井太郎
矢張り、Number と Name それぞれの更新で、それぞれの TextControl が更新されている。
今回は、もう少し念入りにチェックしてみよう。
using System;
// アプリケーション部
class Program
{
static void Main()
{
var employee = new Employee(); // Model
dynamic observableEmployee = new DynamicObservable(employee); // Observable
var employeeView = new EmployeeView(); // View, Observer
employeeView.DataSource = observableEmployee; // データバインド
observableEmployee.Number = 100; // Number を変更。employeeView に反映されたかな?
observableEmployee.Number = 200; // Number を再度変更。employeeView に反映されたかな?
observableEmployee.Name = "福井太郎"; // Name を変更。employeeView に反映されたかな?
observableEmployee.Name = "山田次郎"; // Name を再度変更。employeeView に反映されたかな?
observableEmployee.Number = 200; // Number を変更していない。employeeView は更新されない筈
observableEmployee.Name = "山田次郎"; // Name を変更していない。employeeView は更新されない筈
// 念の為値を確認
Console.WriteLine("employee - Number:{0}, Name:{1}", employee.Number, employee.Name);
Console.WriteLine("observableEmployee - Number:{0}, Name:{1}", observableEmployee.Number, observableEmployee.Name);
}
}
実行結果は予想通り。
この範囲ではうまく行っているようだ。
TextControl is updated: 100 TextControl is updated: 200 TextControl is updated: 福井太郎 TextControl is updated: 山田次郎 employee - Number:200, Name:山田次郎 observableEmployee - Number:200, Name:山田次郎
今回は、DynamicObject を使ったアプローチを行った。
モデル部分がシンプルに書けるようになった。
大分前のものだが、「Community Open Day 2012 北陸」 (6月9日, 石川工業高等専門学校) での「JavaScript+HTML5 と C#+XAML で作る Windows8 アプリ」も公開。
WPF (や Silverlight、XAML による Windows ストア アプリ、Windows Phone アプリ) での View 周りの設計方法には、大きく分けて次の二種類がある。
このうち、MVVM パターンについて参考となる記事をご紹介する。
今回は、軽めの話題として、関数型プログラミングでお馴染みのカリー化を C# でやってみよう。
先ず、以下のように関数を用意してみる。
// 2つの数の足し算 (引数は2つ) Func<double, double, double> 足し算 = (x, y) => x + y; // 2つの数の平均 (引数は2つ) Func<double, double, double> 平均 = (x, y) => 足し算(x, y) / 2.0;
これらの関数を使ってみると次のようになる。
var 和 = 足し算(1.0, 2.0); // 和の値は 3.0 var 平均値 = 平均(3.0, 4.0); // 平均値は 3.5
これらは2つの引数を取る関数だ。
このような2つの引数を取る関数は、次のように、1つの引数を取り「1つの引数を取る関数」を返す関数に変換することができる。
Func<double, Func<double, double>> 足し算 = x => (y => x + y); Func<double, Func<double, double>> 平均 = x => (y => 足し算(x)(y) / 2.0);
このようにして、複数の引数の関数を「1つの引数の関数」で表現することをカリー化という。
カリー化後の関数を使ってみると次のようになる。
var 和 = 足し算(1.0)(2.0); // 和の値は 3.0 var 平均値 = 平均(3.0)(4.0); // 平均値は 3.5
カリー化された関数は、引数の一部だけを渡すことで、一部の変数だけが適用された別の関数を作り出すことができる。
これを部分適用と云う。
// 部分適用 Func<double, double> 被加数が1の足し算 = 足し算(1.0); // 部分適用された関数を使ってみる var 和 = 被加数が1の足し算(2.0); // 和の値は 3.0
3つ以上の引数を取る関数の場合も同様である。
例えば、次のような3つの引数を取る台形の面積を求める関数があってとして、
Func<double, double, double, double> 台形の面積 = (上辺, 下辺, 高さ) => (上辺 + 下辺) * 高さ / 2.0; // 3つの引数を取る関数を使ってみる var 面積 = 台形の面積(3.0, 5.0, 2.0); // 面積の値は 8.0
これをカリー化していくと、次のようになる。
Func<double, double, Func<double, double>> 台形の面積 = (上辺, 下辺) => (高さ => (上辺 + 下辺) * 高さ / 2.0);
Func<double, Func<double, Func<double, double>>> 台形の面積 = 上辺 => (下辺 => (高さ => (上辺 + 下辺) * 高さ / 2.0));
つまり、「『関数を返す関数』を返す関数」を返せば良いことになる。
順番に部分適用していくと次のようになる。
Func<double, Func<double, double>> 上辺が3の台形の面積 = 台形の面積(3.0); Func<double, double> 上辺が3で下辺が5の台形の面積 = 上辺が3の台形の面積(5.0); var 上辺が3で下辺が5で高さが2の台形の面積 = 上辺が3で下辺が5の台形の面積(2.0);
まとめて書くと次のようになる。
var 面積 = 台形の面積(3.0)(5.0)(2.0); // 面積の値は 8.0
今年も全国 7 箇所 (札幌、仙台、名古屋、石川、大阪、広島、沖縄) で一斉に Community Open Day が開催されます。
東京会場はこちらに詳しいスケジュールがあります。
| Community Open Day 2013 Supported by Microsoft | |
|---|---|
| 日時 | 2013/05/11(土) 10:00 – 16:50 (9:00 受付開始) 懇親会 17:15 – 18:30 |
| 会場 | 日本マイクロソフト株式会社品川本社 31Fセミナールーム (東京都港区港南2-16-3 品川グランドセントラルタワー) |
| 参加費 | 無料 |
| 主催 | マイクロソフト技術コミュニティ supported by Microsoft |
| 詳細/申込み | Community Open Day 2013 Supported by Microsoft |
北陸会場はこちらです。
| Community Open Day 2013 北陸会場 | |
|---|---|
| 日時 | 2013/05/11(土) 10:00 - 18:00 |
| 会場 | 石川工業高等専門学校 (石川県河北郡津幡町北中条タ1) |
| 参加費 | 無料 (懇親会は実費) |
| 主催 | Hokuriku.NET、福井情報技術者協会[FITEA] |
| 詳細/申込み | Community Open Day 2013 北陸会場 : ATND |
先週土曜日 (5月11日) に、全国 7 箇所 (札幌、仙台、名古屋、石川、大阪、広島、沖縄) で一斉に Community Open Day 2013 が開催されました。
私は、北陸のエリア リーダーとして参加しました。
聴きに来てくださった皆様、スピーカーの皆様、スタッフの皆様本当にありがとうございました。
| Community Open Day 2013 北陸会場 | |
|---|---|
| 日時 | 2013/05/11(土) 10:00 - 18:00 |
| 会場 | 石川工業高等専門学校 (石川県河北郡津幡町北中条タ1) |
| 主催 | Hokuriku.NET、福井情報技術者協会[FITEA] |
| 内容 |
|
7名の Microsoft MVP がスピーカーとして登壇し、全体では 32 名の参加者がありました。
|
|
|
|
|
|
|
|
|
|
|
|
私は、鈴木 孝明 氏と共に石野 光仁 氏のセッション「C# MVP に聞くC#アレコレ!」に登壇しました。
両氏と相談して、「折角北信越の C# MVP 3名が集まるのだから、一緒に C# の魅力を語ろう!」ということで、石野さんに企画していただいたものです。
ライブ コーディング対決を行いましたが、お題は本番になるまで本当に何も知らされず、鈴木さんと私はぶっつけ本番でした。
それだけに、ライブ感が出たのではないかと思います。私にとって、とても楽しいセッションでした。
私の分の「C#の好きなところ」の資料を公開します。
今回は、ごく軽く余り役に立たない話題。C# の匿名型に関して。
匿名型は、以下のようなものだ。
var 本1 = new { タイトル = "C#入門", 価格 = 3800 };
これで、"本1" は次のような無名のクラスの参照変数となる、と云うものだ。
// 疑似コード (コンパイルできない)
class <>f__AnonymousType1`2 // 内部的なクラス名
{
public string タイトル { get; set; }
public int 価格 { get; set; }
}
名前が付いている場合、匿名型のプロパティ名は省略できる。
例えば、変数を使用した場合、以下のように書ける:
var タイトル = "C#入門";
var 価格 = 3800;
var 本2 = new { タイトル, 価格 };
これで、"本2" は、先のクラスと同様の匿名クラスの参照変数となる。
まあ、これは大して役に立たないだろう。変数名とプロパティ名では、通常、命名規則が異なる (CamelCase と PascalCase が普通) からだ。
プロパティ名を使うこともできる:
var 出力用本 = new { 本1.タイトル, 価格 = 本1.価格.ToString("C") };
"出力用本" は、次の2つのプロパティを持つ匿名クラスの参照変数だ。
public string タイトル { get; set; }
public string 価格 { get; set; }
こちらは、「同じプロパティ名としたい (先のプロパティ名が変更になったら変更になるようにしたい)」と云う場合等に使えるかもしれない。
今回は、列挙型 (enum) の列挙子の取得などについて。
例えば、次のようなクラス ライブラリー (ClassLibrary.dll) があるとする。
// クラス ライブラリー側: ClassLibrary.dll
namespace ClassLibrary
{
// 列挙型 (enum)
public enum ItemKind
{
Book, Toy, Stationary
}
// クラス
public class Item
{
// アイテムの種類 (型は列挙型の ItemKind)
readonly ItemKind itemType;
// コンストラクター (引数でアイテムの種類を指定)
public Item(ItemKind itemType)
{ this.itemType = itemType; }
}
}
そして、別のプロジェクトで、このクラス ライブラリーを動的に読み込んで使いたいとする。
例.
// クラス ライブラリーを動的に利用する側: Xxx.exe
using System;
using System.Reflection;
class Program
{
static void Main()
{
// アセンブリ名を使ってクラス ライブラリーを動的に読み込み
var assembly = Assembly.Load("ClassLibrary");
// アセンブリ内のクラスの型情報を取得
var itemType = assembly.GetType("ClassLibrary.Item");
// ... 途中省略 ...
}
}
ここで次のように、型情報 itemType を使って、インスタンスを生成させようとしてみる。
// ... 途中省略 ...
// アセンブリ名を使ってクラス ライブラリーを動的に読み込み
var assembly = Assembly.Load("ClassLibrary");
// アセンブリ内のクラスの型情報を取得
var itemType = assembly.GetType("ClassLibrary.Item");
// クラスの型情報を使って、クラスのインスタンスを生成
dynamic item = Activator.CreateInstance(itemType, 0 /* ClassLibrary.ItemKind.Book の値 */);
// ... 途中省略 ...
これは次のように、実行時エラーになる。
コンストラクターの引数の型が合っていないからだ。
勿論、object にキャストしても実行時エラーの儘だ。
だが、クラス ライブラリーを動的に読み込みたい訳なので、ClassLibrary.ItemKind.Book と書く訳にもいかない。
// ... 途中省略 ...
// クラスの型情報を使って、クラスのインスタンスを生成
dynamic item = Activator.CreateInstance(itemType, ClassLibrary.ItemKind.Book); // コンパイル エラー
// ... 途中省略 ...
こういう場合はどうすれば良いだろうか?
これは、次のように、列挙型 (enum) の型情報も取得して、そこから ClassLibrary.ItemKind.Book を生成してやれば良い。
// ... 途中省略 ...
// アセンブリ内の列挙型 (enum) の型情報をアセンブリから取得
var itemKindType = assembly.GetType("ClassLibrary.ItemKind");
// enum の列挙オブジェクト ClassLibrary.ItemKind.Book を生成
var itemKindTypeBook = Enum.Parse(itemKindType, "Book");
// クラスの型情報を使って、クラスのインスタンスを生成
dynamic item = Activator.CreateInstance(itemType, itemKindTypeBook);
// ... 途中省略 ...
Enum.Parse メソッドを使って enum の列挙子名から列挙オブジェクトを生成するのがポイントだ。
列挙型 (enum) の型情報から全ての列挙子の名称を取得することもできる。
例えば、列挙型 (enum) ClassLibrary.ItemKind の全ての列挙子の名称を取得するには次のようにすれば良い。
// ... 途中省略 ...
// 列挙型 (enum) の全ての列挙子の名称を取得
string[] itemKindTypeNames = Enum.GetNames(typeof(ClassLibrary.ItemKind));
// ... 途中省略 ...
列挙子の名称が配列で得られる。即ち、ここから列挙子の数を取得することができることになる。
// ... 途中省略 ...
// 列挙型 (enum) の列挙子の数を取得
int itemKindTypeLength = Enum.GetNames(typeof(ClassLibrary.ItemKind)).Length;
// ... 途中省略 ...
Enum.GetTypes メソッドで、全ての列挙子オブジェクトを配列 (Array) で取得することもできるので、次のようにしても良い。
// ... 途中省略 ...
// 列挙型 (enum) の列挙子の数を取得
int itemKindTypeLength = Enum.GetTypes(typeof(ClassLibrary.ItemKind)).Length;
// ... 途中省略 ...
今回は、動的にイベント ハンドラーの追加を行ってみたい。
例えば、次のようなクラス ライブラリー (ClassLibrary.dll) があるとする。
// クラス ライブラリー側: ClassLibrary.dll
namespace ClassLibrary
{
public class Data : IEnumerable<string>
{
// 更新されると起きるイベント
public event EventHandler Update;
List<string> itemList = new List<string>();
// アイテムの追加
public void Add(string item)
{
itemList.Add(item);
// 更新イベントを起こす
if (Update != null)
Update(this, null);
}
// IEnumerable<string> の実装
public IEnumerator<string> GetEnumerator()
{ return itemList.GetEnumerator(); }
// IEnumerable<string> の実装
IEnumerator IEnumerable.GetEnumerator()
{ return GetEnumerator(); }
}
}
クラス Data は、文字列のコンテナー クラスで、文字列が追加 (Add) されると、Update イベントが起きるようになっている。
別のプロジェクトで、このクラス ライブラリーを動的に読み込んで使いたいとする。
こちらでは、先ず先の Data を表示するための View クラスを用意することにする。
using System;
using System.Collections.Generic;
class View
{
public IEnumerable<string> DataSource { private get; set; }
// Update のイベント ハンドラー用
public void OnUpdate(object sender, EventArgs e)
{
Show();
}
// DataSource の内容を表示
void Show()
{
Console.WriteLine("View:");
if (DataSource == null)
return;
foreach (var item in DataSource)
Console.WriteLine("\t{0}", item);
}
}
表示したい IEnumerable<string> な DataSource を予め設定しておくと、OnUpdate が呼ばれたときにそれを表示する、というクラスだ。
それでは、クラス ライブラリーを動的に読み込み、その中の Data クラスのインスタンスを生成し、それにイベント ハンドラーを追加してみよう。
using System;
using System.Collections.Generic;
using System.Reflection;
class Program
{
static void Main()
{
// アセンブリ名を使ってクラス ライブラリーを動的に読み込み
Assembly assembly = Assembly.Load("ClassLibrary");
// アセンブリ内のクラス Data の型情報を取得
Type dataType = assembly.GetType("ClassLibrary.Data");
// アセンブリ内のクラス Data のインスタンスを生成
var data = Activator.CreateInstance(dataType);
}
}
// ...省略...
class Program
{
static void Main()
{
// ...省略...
// View のインスタンスを生成して DataSource に data を設定
var view = new View { DataSource = data as IEnumerable<string> };
}
}
// ...省略...
class Program
{
static void Main()
{
// ...省略...
// Delegate クラスを利用してイベント ハンドラーである view.OnUpdate からデリゲートを作成
Delegate eventHandlerDelegate = Delegate.CreateDelegate(typeof(EventHandler), view, "OnUpdate");
// アセンブリ内のクラスの Update イベントの EventInfo を取得
EventInfo eventInfo = dataType.GetEvent("Update");
// EventInfo に対してイベント ハンドラーを追加
eventInfo.AddEventHandler(data, eventHandlerDelegate);
}
}
では試してみよう。
data に対して、文字列を追加 (Add) する度に view.OnUpdate が呼ばれれば OK だ。
// ...省略...
class Program
{
static void Main()
{
// ...省略...
// data.Add("Apple"); を動的に呼び出す
dataType.InvokeMember("Add", BindingFlags.InvokeMethod, null, data, new object[] { "Apple" });
// data.Add("Banana"); を動的に呼び出す
dataType.InvokeMember("Add", BindingFlags.InvokeMethod, null, data, new object[] { "Banana" });
}
}
結果コンソールには、次のように表示される。
View:
Apple
View:
Apple
Banana
文字列を追加 (Add) する度にイベント ハンドラー view.OnUpdate が呼ばれているのが判る。
以前「Expression の構造を調べてみる」と云う記事で、Expression の内部のツリー構造を調べた。
その中で、ラムダ式として足し算を行うだけの (x, y) => x + y と云うシンプルなラムダ式を Expression で受け、その構造を調べた。
using System;
using System.Linq.Expressions;
class Program
{
static void Main()
{
Expression<Func<int, int, int>> expression = (x, y) => x + y;
}
}
今回は、逆に Expression でラムダ式を作成し、これをデリゲートとして実行してみよう。
今回のサンプルプログラムは次の通りだ。
using System;
using System.Linq.Expressions;
class Program
{
static void Main()
{
// ラムダ式の Parameter の x
ParameterExpression parameterX = Expression.Parameter(typeof(int), "x");
// ラムダ式の Parameter の y
ParameterExpression parameterY = Expression.Parameter(typeof(int), "y");
// ラムダ式の Body: x + y
BinaryExpression body = Expression.Add(parameterX, parameterY);
// ラムダ式: Parameter の x と y、Body で作成
Expression<Func<int, int, int>> lamda = Expression.Lambda<Func<int, int, int>>(body, parameterX, parameterY);
// ラムダ式をコンパイルしてデリゲートを生成
Func<int, int, int> lamdaDelegate = lamda.Compile();
// 実行してみる: result が 3 になる
int result = lamdaDelegate(1, 2);
}
}
このように、Expression でラムダ式をツリー上に組立て、コンパイルすることで、これをデリゲートとして実行することができる。
「Hokuriku.NET C# メタプログラミング ~リフレクション~」に参加してきた。
| Hokuriku.NET C# メタプログラミング ~リフレクション~ | |
|---|---|
| 日時 | 2013年6月29日 |
| 会場 | 海みらい図書館 (石川県金沢市) |
| 関連記事 | |
その中で話題になったことから、何点かQ&A形式でご紹介したい。
A. 取れる。
例えば System.Collections.Generic.Dictionary の型情報を取ってみよう。
using System;
using System.Collections.Generic;
class Program
{
static void Main()
{
// Dictionary<,> の型情報を取得
Type typeOfDictionary = typeof(Dictionary<,>);
Console.WriteLine(typeOfDictionary);
}
}
実行結果は、次のようになる。
System.Collections.Generic.Dictionary`2[TKey,TValue]
この型情報から、型引数を指定した場合の型情報を得るには、次のように MakeGenericType を使う。
using System;
using System.Collections.Generic;
class Program
{
static void Main()
{
// Dictionary<,> の型情報を取得
Type typeOfDictionary = typeof(Dictionary<,>);
// typeOfDictionary に型引数を指定して型情報を取得
Type typeOfDictionaryOfStringAndInt = typeOfDictionary.MakeGenericType(new Type[] { typeof(string), typeof(int) });
Console.WriteLine(typeOfDictionaryOfStringAndInt);
}
}
実行結果:
System.Collections.Generic.Dictionary`2[System.String,System.Int32]
勿論、始めから型引数を指定した場合の型情報と同じになる。
using System;
using System.Collections.Generic;
class Program
{
static void Main()
{
// Dictionary<string, int> の型情報を取得
Type typeOfDictionaryOfStringAndInt = typeof(Dictionary<string, int>);
Console.WriteLine(typeOfDictionaryOfStringAndInt);
}
}
実行結果:
System.Collections.Generic.Dictionary`2[System.String,System.Int32]
A. その通り。プロパティでもある。
リフレクションを使って、プロパティを持ったクラスのメンバーを調べてみよう。
using System;
using System.Reflection;
// プロパティ Name を持つクラス Person (Object クラスから派生)
class Person // : Object
{
public string Name { get; set; }
}
class Program
{
static void Main()
{
// Person の型情報を取得
Type type = typeof(Person);
// Person の型情報から、Person の全メンバーの情報を取得
MemberInfo[] memberInfos = type.GetMembers();
// 各メンバー情報の名前と種類を表示
foreach (MemberInfo member in memberInfos)
Console.WriteLine("Name: {0}, MemberType: {1}", member.Name, member.MemberType);
}
}
Type.GetMembers メソッドを使い、全メンバーの情報を取得し、各メンバー情報の名前と種類を表示させてみる。
実行してみると、次のように Person とそのベースクラスである Object の、public なメンバーがリストアップされる:
Name: get_Name, MemberType: Method Name: set_Name, MemberType: Method Name: ToString, MemberType: Method Name: Equals, MemberType: Method Name: GetHashCode, MemberType: Method Name: GetType, MemberType: Method Name: .ctor, MemberType: Constructor Name: Name, MemberType: Property
プロパティとして Name、そのアクセサー メソッドとして get_Name と set_Name があるのが判る。
次のように、プロパティ Name から、アクセサー メソッドを取り出してみることもできる。
using System;
using System.Reflection;
// プロパティ Name を持つクラス Person
class Person
{
public string Name { get; set; }
}
class Program
{
static void Main()
{
// Person の型情報を取得
Type type = typeof(Person);
// Person の型情報から、プロパティ Name の情報を取得
PropertyInfo propertyInfo = type.GetProperty("Name");
Console.WriteLine(propertyInfo);
// プロパティ Name の情報から、アクセサー メソッド (getter と setter) の情報を取得
MethodInfo[] methodInfos = propertyInfo.GetAccessors();
// アクセサー メソッド (getter と setter) の情報を表示
foreach (MethodInfo methodInfo in methodInfos)
Console.WriteLine(methodInfo);
}
}
実行結果:
System.String Name System.String get_Name() Void set_Name(System.String)
つまり、プロパティ Name があるときには、内部的にアクセサーとして get_Name と set_Name メソッドを持つことになる。
これは、単に get_Name と set_Name メソッドを持つのとは異なる。Name というプロパティも持つことになる。
class Person
{
public string Name { get; set; } // プロパティ Name (アクセサーとして get_Name と set_Name メソッドも持つことになる)
}
と異なり、
class Person
{
public string get_Name( ) { return null; }
public void set_Name(string name) { }
}
では、get_Name と set_Name メソッドを持つことは同じだが、Name プロパティを持たない。
A. そう、作れない。コンパイル エラーになる。
次のコードをコンパイルしてみると、コンパイル エラーになる。
class Person
{
public string Name { get; set; } // プロパティ Name
public string get_Name( ) { return null; }
public void set_Name(string name) { }
}
コンパイル結果:
A. できる。
先ずは、リフレクションを使って、プロパティに値を設定/取得してみる。
using System;
using System.Reflection;
// プロパティ Name を持つクラス Person
class Person
{
public string Name { get; set; }
}
class Program
{
static void Main()
{
// Person の型情報を取得
Type type = typeof(Person);
var person = new Person();
PropertyInfo propertyInfo = type.GetProperty("Name");
// リフレクションを使って、person の Name の値を設定
propertyInfo.SetValue(person, "明智光秀", null); // .NET 4.5 未満の場合
// .NET 4.5 以上の場合は propertyInfo.SetValue(person, "明智光秀"); でも良い
// リフレクションを使って、person の Name の値を取得
string name = (string)propertyInfo.GetValue(person, null); // .NET 4.5 未満の場合
// .NET 4.5 以上の場合は string name = (string)propertyInfo.GetValue(person); でも良い
Console.WriteLine(name);
}
}
実行結果:
明智光秀
次に、リフレクションを使い、内部的な set_Name メソッドで値を設定し、Name プロパティで値を取得してみる。
using System;
using System.Reflection;
// プロパティ Name を持つクラス Person
class Person
{
public string Name { get; set; }
}
class Program
{
static void Main()
{
// Person の型情報を取得
Type type = typeof(Person);
var person = new Person();
// リフレクションを使って、person の set_Name を呼び出す
MethodInfo methodInfo = type.GetMethod("set_Name");
methodInfo.Invoke(person, new object[] { "織田信長" });
// リフレクションを使って、person の Name の値を取得
PropertyInfo propertyInfo = type.GetProperty("Name");
string name = (string)propertyInfo.GetValue(person, null); // .NET 4.5 未満の場合
// .NET 4.5 以上の場合は string name = (string)propertyInfo.GetValue(person); でも良い
Console.WriteLine(name);
}
}
実行結果:
織田信長
このように、プロパティの内部的なメソッドをリフレクションを使って実行することで、プロパティの値の設定/取得ができる。
勿論、リフレクションを使わずに、直接 set_Name や get_Name を呼び出すことはできない。
using System;
using System.Reflection;
// プロパティ Name を持つクラス Person
class Person
{
public string Name { get; set; }
}
class Program
{
static void Main()
{
var person = new Person();
person.set_Name("石田光成"); // コンパイル エラーになる
}
}
コンパイル結果:
A. できる。
次の static メンバーや private メンバーを持つクラス Singleton で、試してみよう。
using System;
using System.Reflection;
// static メンバーや private メンバーを持つクラス
class Singleton
{
private static Singleton instance = new Singleton();
public static Singleton GetInstance()
{ return instance; }
private Singleton()
{}
}
class Program
{
static void Main()
{
// Singleton の型情報を取得
Type type = typeof(Singleton);
// BindingFlags.NonPublic | BindingFlags.Instance を指定することで、public でも static でもないコンストラクターの情報を取得
ConstructorInfo constructorInfo = type.GetConstructor(BindingFlags.NonPublic | BindingFlags.Instance, null, new Type[] { }, null);
// リフレクションを使うことで、private なコンストラクターが実行できる
Singleton singleton = (Singleton)constructorInfo.Invoke(null);
// BindingFlags.NonPublic | BindingFlags.Static を指定することで、public でなく static なフィールドの情報を取得
FieldInfo fieldInfo = type.GetField("instance", BindingFlags.NonPublic | BindingFlags.Static);
// リフレクションを使うことで、private で static なフィールドにアクセスできる
fieldInfo.SetValue(null, singleton);
}
}
A. const なフィールドは static なフィールドとしてリフレクションで値を取得できるが、書き換えられる訳ではない。
試してみよう:
using System;
using System.Reflection;
class ConstantSample
{
public const double PI = 3.14159265358979 ; // const なフィールド
}
class Program
{
static void Main()
{
// Constant の型情報を取得
Type type = typeof(ConstantSample);
// Constant の型情報から、const なフィールド PI の情報を取得
FieldInfo piFieldInfo = type.GetField("PI");
// インスタンスを指定せずに static フィールドとして値を取得してみる
var pi = (double)piFieldInfo.GetValue(null);
Console.WriteLine(pi);
// インスタンスを指定せずに static フィールドとして値を設定してみる
piFieldInfo.SetValue(null, 3.0);
}
}
実行してみると、次のように、PI の値を取得することはできるが、設定ではエラーになる:
3.14159265358979 ハンドルされていない例外: System.FieldAccessException: 定数フィールドを設定できません。
A. readonly なフィールドの場合は、const なフィールドの場合と異なり、static としてなければインスタンス フィールドになる。また、値を取得するだけでなく設定することもできる。
readonly なフィールドの場合について試してみよう。
using System;
using System.Reflection;
class ReadonlySample
{
public readonly int DayOfYear = DateTime.Now.DayOfYear; // readonly なフィールド
}
class Program
{
static void Main()
{
// Readonly の型情報を取得
Type type = typeof(ReadonlySample);
// Readonly の型情報から、readonly なフィールド DayOfYear の情報を取得
FieldInfo dayOfYearFieldInfo = type.GetField("DayOfYear");
// インスタンスを指定せずに static フィールドとして値を取得してみると、これは実行時エラーとなり取得できない
//var dayOfYear = (int)dayOfYearFieldInfo.GetValue(null); // 実行時エラー
// Readonly のインスタンスを生成
var readonlySample = new ReadonlySample();
// インスタンスを指定してインスタンス フィールドとして値を取得してみる
var dayOfYear = (int)dayOfYearFieldInfo.GetValue(readonlySample);
Console.WriteLine(dayOfYear);
// インスタンスを指定してインスタンス フィールドとして値を設定してみる
dayOfYearFieldInfo.SetValue(readonlySample, 0);
// 設定されたか確認
Console.WriteLine(readonlySample.DayOfYear);
}
}
実行結果は次の通り:
182 0
取得だけでなく、設定も可能だ。
Microsoft MVP (Most Valuable Professional) for Visual C# を再受賞しました。2005年7月からの受賞で、9年目になります。
ここでも述べましたが、オンライン・オフラインで多くの素晴らしいエンジニアの皆様と出会えたおかげです。感謝です。
今後ともどうぞよろしくお願いいたします。

| 『こみゅぷらす Tech Aid 2013』 こみゅぷらすの飲み放題イベント2013!! |
|
|---|---|
| 日時 | 2013年7月27日(土) 11時~18時 |
| 会場 | 新宿居酒屋 |
| 参加費 | 3100円 (ランチ、飲み放題付き) |
| 内容/申込み | http://comuplus.net/CLT2013/ |

| 『Hokuriku.NET Vol.12』 ~今年の夏も福井で~ |
|
|---|---|
| 日時 | 2013年8月3日(土) 10時30分~18時 |
| 会場 | 福井市地域交流プラザ 研修室603 (AOSSA 6階) (福井県福井市手寄1-4-1) |
| 参加費 | 500円 (会場費) |
| 内容/申込み | http://atnd.org/events/40509 |
WPF や Silverlight、Windows 8 や Windows RT の Windows ストア アプリでは、UI の記述に XAML を使うことが多い。
そして、データバインドするために INotifyPropertyChanged をしょっちゅう実装することになる。
これが結構面倒なので、開発者は普通、少しでも楽になるような工夫をしている。
そのような「有り勝ちな工夫」について。
「工夫」する前のコードは、こんな感じだ。
using System.ComponentModel;
class MyViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
int id = 0;
public int Id
{
get { return id; }
set {
if (value != id) {
id = value;
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs("Id"));
}
}
}
string name = string.Empty;
public string Name
{
get { return name; }
set {
if (value != name) {
name = value;
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs("Name"));
}
}
}
}
プロパティ毎の記述が、結構煩雑だ。
上記ではプロパティが2つしかないが、プロパティが多くなれば、この繰り返しはそれに比例して増えることになる。
Expression や Caller Info を使ったヘルパー クラスを用意するともう少し記述が楽になる。
※ 参考:
こんな感じのヘルパー クラスだ。
using System;
using System.ComponentModel;
using System.Linq.Expressions;
using System.Runtime.CompilerServices;
public static class PropertyChangedEventHandlerExtensions
{
// Caller Info を使っているので C# 5.0 以降
public static void Raise(this PropertyChangedEventHandler onPropertyChanged, object sender, [CallerMemberName] string propertyName = "")
{
if (onPropertyChanged != null)
onPropertyChanged(sender, new PropertyChangedEventArgs(propertyName));
}
public static void Raise<PropertyType>(this PropertyChangedEventHandler onPropertyChanged, object sender, Expression<Func<PropertyType>> propertyExpression)
{ onPropertyChanged.Raise(sender, propertyExpression.GetMemberName()); }
static string GetMemberName<MemberType>(this Expression<Func<MemberType>> expression)
{ return ((MemberExpression)expression.Body).Member.Name; }
}
これを使うことで、先のコードは「少しだけ」簡潔になる。また、プロパティ名の変更に対して安全になった。
using System.ComponentModel;
class MyViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
int id = 0;
public int Id
{
get { return id; }
set {
if (value != id) {
id = value;
PropertyChanged.Raise(this);
}
}
}
string name = string.Empty;
public string Name
{
get { return name; }
set {
if (value != name) {
name = value;
PropertyChanged.Raise(this);
}
}
}
}
更に Visual Studio でコード スニペットを利用することで、タイピングの手間を減らすことができる。
このコード スニペットは、例えば次のような XML だ。
<?xml version="1.0" encoding="utf-8" ?>
<CodeSnippets xmlns="http://schemas.microsoft.com/VisualStudio/2005/CodeSnippet">
<CodeSnippet Format="1.0.0">
<Header>
<Title>Property for INotifyPropertyChanged</Title>
<Shortcut>propin</Shortcut>
<Description>Code snippet for property for INotifyPropertyChanged.
Use this class (for C# 5.0 or later):
using System.ComponentModel;
using System.Runtime.CompilerServices;
public static class PropertyChangedEventHandlerExtensions
{
public static void Raise(this PropertyChangedEventHandler onPropertyChanged, object sender, [CallerMemberName] string propertyName = "")
{
if (onPropertyChanged != null)
onPropertyChanged(sender, new PropertyChangedEventArgs(propertyName));
}
}</Description>
<Author></Author>
<SnippetTypes>
<SnippetType>Expansion</SnippetType>
</SnippetTypes>
</Header>
<Snippet>
<Declarations>
<Literal>
<ID>type</ID>
<ToolTip>type name</ToolTip>
<Default>int</Default>
</Literal>
<Literal>
<ID>field</ID>
<ToolTip>field name</ToolTip>
<Default>myField</Default>
</Literal>
<Literal>
<ID>property</ID>
<ToolTip>property name</ToolTip>
<Default>MyProperty</Default>
</Literal>
</Declarations>
<Code Language="csharp"><![CDATA[ $type$ $field$ = default($type$);
public $type$ $property$
{
get { return $field$; }
set {
if (!value.Equals($field$)) {
$field$ = value;
PropertyChanged.Raise(this);
}
}
}
$end$]]></Code>
</Snippet>
</CodeSnippet>
</CodeSnippets>
これを、"propin.snippet" のような名前で、テキスト ファイルとして任意の場所に保存する。
次に、Visual Studio で、メニューから 「ツール」 - 「コード スニペット マネージャー」を開く。
「インポート」ボタンを押す。先ほどのテキスト ファイル "propin.snippet" を選択する。
"My Code Snippets" にチェックをいれて、「完了」ボタンを押す。
これでコード スニペットが使えるようになる。
例えば、先程の MyViewModel クラスの Name プロパティの下にカーソルを合わせて、propin とタイプして[TAB]キーを2回押してみよう。
新しいプロパティが簡単に挿入でき、型名、フィールド名、プロパティ名が楽に設定できるだろう。
※ この内容は、『こみゅぷらす Tech Aid 2013』 (2013-07-27 新宿,東京) にて実際のデモと共に発表予定。
Roslyn は、C# や Visual Basic のコンパイラーの内部の API 等を公開したものだ。"Compiler as a Service" と表現されている。
現在、次の場所や NuGet で入手可能だ。
コンパイラーのコード解析部分等の機能が公開されている。
次にあげる Channel 9 のビデオと資料が参考になるだろう。
Roslyn には次にあげるような機能がある。
今回は、この中の「ソースコード解析機能」を用いて、簡単な Visual Studio のアドインを作ってみたい。
C# のソースコードを解析して、識別子等を数えて、それを WPF で作成したウィンドウに表示させてみよう。
先ず、Visual Studio を用いて、「Visual Studio アドイン」プロジェクトを新規作成する。
次に Roslyn をインストールし、このプロジェクトから参照する。
NuGet を使うことで、簡単にこの作業を行うことができる。
「ソリューション エクスプローラー」でプロジェクト名を右クリックし、「NuGet パッケージの管理...」を選択する。
「NuGet パッケージの管理」ダイアログ ボックスが現れるので、次のように「オンラインの検索」で、"Roslyn" を検索し、"Roslyn" を選択して「インストール」する。
Roslyn の追加後のプロジェクトの参照設定は、次のようになる。
今回は、WPF を使ってウィンドウを出すことにする。
そのウィンドウ用に、次のような ViewModel を用意した。
ソースコード内の識別子や using、クラス等の数を保持するクラスだ。
※ 実装の参考:
using System;
using System.ComponentModel;
using System.Linq.Expressions;
using System.Runtime.CompilerServices;
namespace AddinByRoslyn
{
public static class PropertyChangedEventHandlerExtensions
{
public static void Raise(this PropertyChangedEventHandler onPropertyChanged, object sender, [CallerMemberName] string propertyName = "")
{
if (onPropertyChanged != null)
onPropertyChanged(sender, new PropertyChangedEventArgs(propertyName));
}
public static void Raise<PropertyType>(this PropertyChangedEventHandler onPropertyChanged, object sender, Expression<Func<PropertyType>> propertyExpression)
{ onPropertyChanged.Raise(sender, propertyExpression.GetMemberName()); }
static string GetMemberName<MemberType>(this Expression<Func<MemberType>> expression)
{ return ((MemberExpression)expression.Body).Member.Name; }
}
public class ViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
int identifierCount = 0;
public int IdentifierCount
{
get { return identifierCount; }
set {
if (value != identifierCount) {
identifierCount = value;
PropertyChanged.Raise(this);
RaiseUpdateData();
}
}
}
int usingCount = 0;
public int UsingCount
{
get { return usingCount; }
set {
if (value != usingCount) {
usingCount = value;
PropertyChanged.Raise(this);
RaiseUpdateData();
}
}
}
int classCount = 0;
public int ClassCount
{
get { return classCount; }
set {
if (value != classCount) {
classCount = value;
PropertyChanged.Raise(this);
RaiseUpdateData();
}
}
}
int fieldCount = 0;
public int FieldCount
{
get { return fieldCount; }
set {
if (value != fieldCount) {
fieldCount = value;
PropertyChanged.Raise(this);
RaiseUpdateData();
}
}
}
int propertyCount = 0;
public int PropertyCount
{
get { return propertyCount; }
set {
if (value != propertyCount) {
propertyCount = value;
PropertyChanged.Raise(this);
RaiseUpdateData();
}
}
}
int methodCount = 0;
public int MethodCount
{
get { return methodCount; }
set {
if (value != methodCount) {
methodCount = value;
PropertyChanged.Raise(this);
RaiseUpdateData();
}
}
}
int variableCount = 0;
public int VariableCount
{
get { return variableCount; }
set {
if (value != variableCount) {
variableCount = value;
PropertyChanged.Raise(this);
RaiseUpdateData();
}
}
}
int ifCount = 0;
public int IfCount
{
get { return ifCount; }
set {
if (value != ifCount) {
ifCount = value;
PropertyChanged.Raise(this);
RaiseUpdateData();
}
}
}
int lambdaCount = 0;
public int LambdaCount
{
get { return lambdaCount; }
set {
if (value != lambdaCount) {
lambdaCount = value;
PropertyChanged.Raise(this);
RaiseUpdateData();
}
}
}
public object Data
{
get {
return new [] {
new { 名称 = "識別子の数" , 結果 = IdentifierCount },
new { 名称 = "using の数" , 結果 = UsingCount },
new { 名称 = "クラスの数" , 結果 = ClassCount },
new { 名称 = "フィールドの数", 結果 = FieldCount },
new { 名称 = "プロパティの数", 結果 = PropertyCount },
new { 名称 = "メソッドの数" , 結果 = MethodCount },
new { 名称 = "変数の数" , 結果 = VariableCount },
new { 名称 = "if の数" , 結果 = IfCount },
new { 名称 = "ラムダ式の数" , 結果 = LambdaCount }
};
}
}
string result = string.Empty;
public string Result
{
get { return result; }
set {
if (value != result) {
result = value;
PropertyChanged.Raise(this);
}
}
}
public void Clear()
{
IdentifierCount =
UsingCount =
ClassCount =
FieldCount =
PropertyCount =
MethodCount =
VariableCount =
IfCount =
LambdaCount = 0;
Result = string.Empty;
}
void RaiseUpdateData()
{ PropertyChanged.Raise(this, () => Data); }
}
}
では、Roslyn を用いたコード解析部を作成してみよう。
ここでは、Roslyn にある Roslyn.Compilers.CSharp.SyntaxWalker というクラスを継承して SyntaxCounter というクラスを作成する。
この SyntaxWalker クラスは、Visitor パターンになっていて、Visit メソッドを呼ぶことで、全ノードを辿り、ノードの種類毎の virtual メソッドを呼んでくれる。
例えば、識別子のノードの場合には、VisitIdentifierName という virtual メソッドが呼ばれる。
それぞれの virtual メソッドをオーバーライドすることで、そこに処理を書くことができる訳だ。
SyntaxCounter では、オーバーライドしたメソッドのそれぞれで呼ばれた回数を数えることで、ソースコード内の、識別子や using、クラス等の数を知ることにする。
using Roslyn.Compilers.CSharp;
using System.IO;
using System.Text;
namespace AddinByRoslyn
{
class SyntaxCounter : SyntaxWalker
{
readonly ViewModel viewModel;
StringBuilder stringBuilder; // viewModel.Result 用
public SyntaxCounter(ViewModel viewModel)
{ this.viewModel = viewModel; }
// ソース ファイルの中を解析
public void AnalyzeSourceFile(string sourceFileName)
{
using (var reader = new StreamReader(sourceFileName, false)) {
var sourceCode = reader.ReadToEnd();
Analyze(sourceCode);
}
}
// ソース コードの中を解析
void Analyze(string sourceCode)
{
// Roslyn.Compilers.CSharp.SyntaxTree クラスによるシンタックス ツリーの取得
var tree = SyntaxTree.ParseText(sourceCode);
Analyze(tree.GetRoot()); // シンタックス ツリーのルート要素を解析
}
// ルート要素の中を解析
void Analyze(CompilationUnitSyntax root)
{
viewModel.Clear();
stringBuilder = new StringBuilder();
Visit(root); // Visit メソッドにより、全ノードを辿る (Visitor パターン)
viewModel.Result = stringBuilder.ToString();
}
// 全ノードについて
public override void DefaultVisit(SyntaxNode node)
{
base.DefaultVisit(node);
// ノードの情報を stringBuilder に追加
stringBuilder.AppendLine(string.Format("NodeType: {0}, Node: {1}", node.GetType().Name, node.GetText()));
}
// 識別子のノードの場合
public override void VisitIdentifierName(IdentifierNameSyntax node)
{
base.VisitIdentifierName(node);
viewModel.IdentifierCount++; // 識別子を数える
}
// using のノードの場合
public override void VisitUsingDirective(UsingDirectiveSyntax node)
{
base.VisitUsingDirective(node);
viewModel.UsingCount++; // using を数える
}
// クラスのノードの場合
public override void VisitClassDeclaration(ClassDeclarationSyntax node)
{
base.VisitClassDeclaration(node);
viewModel.ClassCount++; // クラスを数える
}
// フィールドのノードの場合
public override void VisitFieldDeclaration(FieldDeclarationSyntax node)
{
base.VisitFieldDeclaration(node);
viewModel.FieldCount++; // フィールドを数える
}
// プロパティのノードの場合
public override void VisitPropertyDeclaration(PropertyDeclarationSyntax node)
{
base.VisitPropertyDeclaration(node);
viewModel.PropertyCount++; // プロパティを数える
}
// メソッドのノードの場合
public override void VisitMethodDeclaration(MethodDeclarationSyntax node)
{
base.VisitMethodDeclaration(node);
viewModel.MethodCount++; // メソッドを数える
}
// 変数のノードの場合
public override void VisitVariableDeclaration(VariableDeclarationSyntax node)
{
base.VisitVariableDeclaration(node);
viewModel.VariableCount++; // 変数を数える
}
// if のノードの場合
public override void VisitIfStatement(IfStatementSyntax node)
{
base.VisitIfStatement(node);
viewModel.IfCount++; // if を数える
}
// シンプルなラムダ式のノードの場合
public override void VisitSimpleLambdaExpression(SimpleLambdaExpressionSyntax node)
{
base.VisitSimpleLambdaExpression(node);
viewModel.LambdaCount++; // ラムダ式を数える
}
// 括弧付きのラムダ式のノードの場合
public override void VisitParenthesizedLambdaExpression(ParenthesizedLambdaExpressionSyntax node)
{
base.VisitParenthesizedLambdaExpression(node);
viewModel.LambdaCount++; // ラムダ式を数える
}
}
}
次に View を追加する。プロジェクトに "View" という名前の WPF のウィンドウを一つ追加する。
この View は、先の ViewModel を表示するためのウィンドウだ。
プロジェクトに System.Xaml への参照設定の追加が必要になる。
XAML の Grid 内に、ViewModel の "Result" を表示するための TextBox と "Data" を表示するための DataGrid を追加しておく。
<Window x:Class="AddinByRoslyn.View"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="500" d:DesignWidth="500">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<TextBox TextWrapping="Wrap" Text="{Binding Path=Result}" Grid.RowSpan="2" FontFamily="Meiryo" FontSize="14" IsReadOnlyCaretVisible="True" VerticalScrollBarVisibility="Auto" />
<DataGrid Grid.Row="1" ItemsSource="{Binding Path=Data}" FontFamily="Meiryo" FontSize="14" />
</Grid>
</Window>
View の C# 部分では、ViewModel のインスタンスを保持し、それを DataContext とする。
また、コンストラクターで C# のソース ファイル名を受け取り、それを上で作成した SyntaxCounter クラスを使って解析する。
using System.Windows;
namespace AddinByRoslyn
{
public partial class View : Window
{
ViewModel viewModel = new ViewModel();
public View(string sourceFileName)
{
InitializeComponent();
DataContext = viewModel; // DataContext に ViewModel のインスタンスを設定
if (!string.IsNullOrWhiteSpace(sourceFileName))
// SyntaxCounter クラスを用いたソース ファイルの解析
new SyntaxCounter(viewModel).AnalyzeSourceFile(sourceFileName);
}
}
}
最後に、Visual Studio アドイン部のコードをカスタマイズする。
カスタマイズするのは、プロジェクトの新規作成時に自動生成された Connect.cs だ。
Exec というメソッドがあるので、この中で View を作成し、ソースコード ファイル名を渡すようにする。
using EnvDTE;
using EnvDTE80;
using Extensibility;
using Microsoft.VisualStudio.CommandBars;
using System;
namespace AddinByRoslyn
{
public class Connect : IDTExtensibility2, IDTCommandTarget
{
DTE2 _applicationObject = null;
AddIn _addInInstance = null;
public void OnConnection(object application, ext_ConnectMode connectMode, object addInInst, ref Array custom)
{
_applicationObject = (DTE2)application;
_addInInstance = (AddIn)addInInst;
if(connectMode == ext_ConnectMode.ext_cm_UISetup) {
var contextGUIDS = new object[] { };
var commands = (Commands2)_applicationObject.Commands;
var toolsMenuName = "Tools";
var menuBarCommandBar = ((CommandBars)_applicationObject.CommandBars)["MenuBar"];
var toolsControl = menuBarCommandBar.Controls[toolsMenuName];
var toolsPopup = (CommandBarPopup)toolsControl;
try {
var command = commands.AddNamedCommand2(_addInInstance, "AddinByRoslyn", "AddinByRoslyn", "Executes the command for AddinByRoslyn", true, 59, ref contextGUIDS, (int)vsCommandStatus.vsCommandStatusSupported+(int)vsCommandStatus.vsCommandStatusEnabled, (int)vsCommandStyle.vsCommandStylePictAndText, vsCommandControlType.vsCommandControlTypeButton);
if (command != null && toolsPopup != null)
command.AddControl(toolsPopup.CommandBar, 1);
} catch(System.ArgumentException) {
}
}
}
public void OnDisconnection(ext_DisconnectMode disconnectMode, ref Array custom) {}
public void OnAddInsUpdate (ref Array custom) {}
public void OnStartupComplete(ref Array custom) {}
public void OnBeginShutdown (ref Array custom) {}
public void QueryStatus(string commandName, vsCommandStatusTextWanted neededText, ref vsCommandStatus status, ref object commandText)
{
if (neededText == vsCommandStatusTextWanted.vsCommandStatusTextWantedNone &&
commandName == "AddinByRoslyn.Connect.AddinByRoslyn")
status = (vsCommandStatus)vsCommandStatus.vsCommandStatusSupported|vsCommandStatus.vsCommandStatusEnabled;
}
// カスタマイズする Exec メソッド
public void Exec(string commandName, vsCommandExecOption executeOption, ref object varIn, ref object varOut, ref bool handled)
{
if (executeOption == vsCommandExecOption.vsCommandExecOptionDoDefault &&
commandName == "AddinByRoslyn.Connect.AddinByRoslyn") {
var view = new View(GetSourceFileName()); // View のインスタンスにソースファイル名を渡す
view.Show();
handled = true;
} else {
handled = false;
}
}
// ソース ファイル名の取得 (追加する private メソッド)
string GetSourceFileName()
{
var items = _applicationObject.SelectedItems;
if (items.Count == 0)
return null;
var item = items.Item(1);
return item.ProjectItem.get_FileNames(1);
}
}
}
それでは、試してみよう。
Visual Studio から実行してみると、別の Visual Studio が起動する。
新たに起動した Visual Studio で、メニュー から「ツール」 - 「アドイン マネージャー」を開く。
作成したアドインが追加できるようになっているのが分かる。
実際に、新たに起動した Visual Studio で今回作成したプロジェクトを開き、ViewModel.cs を開いた状態で、アドインを実行してみよう。
新たに起動した方の Visual Studio で、メニュー から「ツール」 で、新しいアドインを実行する。
アドインが起動すると、ウィンドウが開き、結果が表示される。
今回は、Roslyn を使って単に何種類かの文法要素を数えただけだが、同様の方法で、より複雑な解析を行ったり、一部を変更したりすることもできる。
また、先に述べたように、Roslyn の機能は C# や Visual Basic のソースコードの解析だけではない。他の機能についても、いずれ解説して行きたい。
C# で Windows 及び Internet Explorer のバージョンを調べる方法を示す。
Windows のバージョンは、System.Environment.OSVersion.Version で取得できる。
例えば、Windows 8 のバージョンは "6.2.9200.0" ですが、メジャー バージョンである最初の 6 だけを得たい場合は、System.Environment.OSVersion.Version.Major で int として取得できる。
ちなみに、マイナー バージョンの 2 の部分の取得には、System.Environment.OSVersion.Version.Minor を使う。
Internet Explorer のバージョンはレジストリで確認することができる。
レジストリの HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Internet Explorer の Version の値や svcVersion の値で確認できる (Internet Explorer 9 以前は Version、Internet Explorer 10 は svcVersion)。
下のサンプル コードでは、Registry.LocalMachine.OpenSubKey メソッドでキーをオープンし、キーの GetValue メソッドで値を取得している。
ちなみに、Internet Explorer のバージョンは次のサイトで確認することができる。
例えば、Windows 8 の Internet Explorer 10 では、"10.0.9200.16384" だ。
using Microsoft.Win32;
using System;
namespace IEVersionChecker
{
public static class VersionChecker
{
public static Version WindowsVersion
{
get { return Environment.OSVersion.Version; }
}
public static int WindowsMajorVersion
{
get { return WindowsVersion.Major; }
}
public static string InternetExplorerVersion // 取得失敗時は null
{
get
{
const string internetExplorerKeyName = @"SOFTWARE\Microsoft\Internet Explorer"; // レジストリのこの場所を確認
const string svcVersionValueName = "svcVersion"; // Internet Explorer 10 からはこれの値でチェック
const string versionValueName = "Version" ; // Internet Explorer 9 以前はこれの値でチェック
try {
using (var key = Registry.LocalMachine.OpenSubKey(internetExplorerKeyName)) {
return (string)(key.GetValue(svcVersionValueName) ?? key.GetValue(versionValueName));
}
} catch (Exception) {
return null;
}
}
}
public static int InternetExplorerMajorVersion // 取得失敗時は 0
{
get
{
var internetExplorerVersion = InternetExplorerVersion;
if (!string.IsNullOrWhiteSpace(internetExplorerVersion)) {
var versionTexts = internetExplorerVersion.Split(new Char[] { '.' });
if (versionTexts.Length > 0) {
int majorVersion;
if (int.TryParse(versionTexts[0], out majorVersion))
return majorVersion;
}
}
return 0;
}
}
}
class Program
{
static void Main()
{
Console.WriteLine("Windows のバージョンは、{0}。", VersionChecker.WindowsVersion);
Console.WriteLine("Windows のメジャー バージョンは、{0}。", VersionChecker.WindowsMajorVersion);
Console.WriteLine("Internet Explorer のバージョンは、{0}。", VersionChecker.InternetExplorerVersion);
Console.WriteLine("Internet Explorer のメジャー バージョンは、{0}。", VersionChecker.InternetExplorerMajorVersion);
}
}
}
Windows のバージョンは、6.2.9200.0。 Windows のメジャー バージョンは、6。 Internet Explorer のバージョンは、10.0.9200.16635。 Internet Explorer のメジャー バージョンは、10。 続行するには何かキーを押してください . . .
文字が数字かどうかを判定する場合、System.Char.IsNumber メソッドを使うことができる。
では、ローマ数字などは数字と判定されるのだろうか?
今回調べた結果、次のようなテストが通ることが分かった。
using Microsoft.VisualStudio.TestTools.UnitTesting;
[TestClass]
public class CommonTest
{
[TestMethod]
public void charのIsNumberのテスト()
{
// ※ 一部に機種依存文字があるので注意
// 指定した文字が数字だと判断されるケース
Assert.IsTrue(char.IsNumber('0'));
Assert.IsTrue(char.IsNumber('1'));
Assert.IsTrue(char.IsNumber('Ⅲ')); // ローマ数字 (大文字) の 3
Assert.IsTrue(char.IsNumber('ⅲ')); // ローマ数字 (小文字) の 3
Assert.IsTrue(char.IsNumber('Ⅰ')); // ローマ数字 (大文字) の 1
Assert.IsTrue(char.IsNumber('①')); // 丸付きの 1
Assert.IsTrue(char.IsNumber('2')); // 所謂全角の 2
// 指定した文字が数字だと判断されないケース
Assert.IsFalse(char.IsNumber('.')); // 小数点
Assert.IsFalse(char.IsNumber('-')); // マイナス記号
Assert.IsFalse(char.IsNumber('I')); // アルファベットの I
Assert.IsFalse(char.IsNumber('一')); // 漢数字
Assert.IsFalse(char.IsNumber('四')); // 漢数字
Assert.IsFalse(char.IsNumber('壱')); // 大字の 1
Assert.IsFalse(char.IsNumber('壹')); // 大字の 1 (壱の舊字體)
}
}
ローマ数字や丸付き数字、所謂全角の数字が数字と見做されていることが分かる。
char.IsDigit だとどうだろう。
using Microsoft.VisualStudio.TestTools.UnitTesting;
[TestMethod]
public void charのIsDigitのテスト()
{
// ※ 一部に機種依存文字があるので注意
// 指定した文字が数字だと判断されるケース
Assert.IsTrue(char.IsDigit('0'));
Assert.IsTrue(char.IsDigit('1'));
Assert.IsTrue(char.IsDigit('2')); // 所謂全角の 2
// 指定した文字が数字だと判断されないケース
Assert.IsFalse(char.IsDigit('.')); // 小数点
Assert.IsFalse(char.IsDigit('-')); // マイナス
Assert.IsFalse(char.IsDigit('①')); // 丸付きの 1
Assert.IsFalse(char.IsDigit('Ⅲ')); // ローマ数字 (大文字) の 3
Assert.IsFalse(char.IsDigit('ⅲ')); // ローマ数字 (小文字) の 3
Assert.IsFalse(char.IsDigit('Ⅰ')); // ローマ数字 (大文字) の 1
Assert.IsFalse(char.IsDigit('I')); // アルファベットの I
Assert.IsFalse(char.IsDigit('一')); // 漢数字
Assert.IsFalse(char.IsDigit('四')); // 漢数字
Assert.IsFalse(char.IsDigit('壱')); // 大字の 1
Assert.IsFalse(char.IsDigit('壹')); // 大字の 1 (壱の舊字體)
}
こちらでは、ローマ数字や丸付き数字が数字だと見做されない。
int.TryParse でもやってみた。
using Microsoft.VisualStudio.TestTools.UnitTesting;
[TestMethod]
public void intのTryParseのテスト()
{
// ※ 一部に機種依存文字があるので注意
int number;
// 指定した文字が数字だと判断されるケース
Assert.IsTrue(int.TryParse("0", out number));
Assert.IsTrue(int.TryParse("1", out number));
Assert.IsTrue(int.TryParse("2", out number)); // 所謂全角の 2
// 指定した文字が数字だと判断されないケース
Assert.IsFalse(int.TryParse(".", out number)); // 小数点
Assert.IsFalse(int.TryParse("-", out number)); // マイナス
Assert.IsFalse(int.TryParse("①", out number)); // 丸付きの 1
Assert.IsFalse(int.TryParse("Ⅲ", out number)); // ローマ数字 (大文字) の 3
Assert.IsFalse(int.TryParse("ⅲ", out number)); // ローマ数字 (小文字) の 3
Assert.IsFalse(int.TryParse("Ⅰ", out number)); // ローマ数字 (大文字) の 1
Assert.IsFalse(int.TryParse("I", out number)); // アルファベットの I
Assert.IsFalse(int.TryParse("一", out number)); // 漢数字
Assert.IsFalse(int.TryParse("四", out number)); // 漢数字
Assert.IsFalse(int.TryParse("壱", out number)); // 大字の 1
Assert.IsFalse(int.TryParse("壹", out number)); // 大字の 1 (壱の舊字體)
}
こちらは、char.IsDigit と同じ結果となった。
.NET アプリケーション (WPF/Windows フォーム) で多重起動を禁止し、単一のプロセスで動作するようにする方法を C# で示す。
WPF アプリケーションや Windows フォーム アプリケーションにおいて、多重起動を禁止してみよう。
ここでは、既にアプリケーションが起動していているときに、そのアプリケーションでドキュメントを開こうとした場合 (*1) に、
という動作を実現する。
(*1) アプリケーションでドキュメントを開くのには、次のようなケースが考えられる。
- 実行ファイルに、ドキュメント ファイルをドラッグ&ドロップ
例. 実行ファイルを a.exe とすると、ファイル エクスプローラーで a.png を a.exe にドラッグ&ドロップ- コマンド プロンプトで、コマンドライン引数にドキュメント ファイルを指定して実行ファイルを起動
例. 実行ファイルのパスを c:\demo\a.exe、ドキュメント ファイル c:\demo\a.png とすると、コマンド プロンプトを開き、c:\demo\a.exe c:\demo\a.png[Enter] と入力- ファイルをアプリケーションに関連付けし、開く。
ここでは Mutex を用いる。詳細はサンプル コードを参照のこと。
ここでは Ipc を用いる。詳細はサンプル コードを参照のこと。
WPF と Windows フォームの両方のサンプルを示そうと思う。
先ずは共通部から。
// 多重起動を禁止する
// - 他のプロセスが起動していなければ、普通にドキュメントを開く。
// - 他のプロセスが既に起動していれば、その起動中のアプリケーションでドキュメントを開く。
// WPF、Windows フォーム非依存
// 参照設定 System.Runtime.Remoting が必要
using System;
using System.Reflection;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Ipc;
using System.Threading;
namespace SingleDocumentSample
{
// .NET で多重起動しないアプリケーションを作成するためのクラス (WPF、Windows フォーム非依存)
public class SingleDocumentHelper
{
// 複数のプロセスで一つしかないことを保証する
class Singleton : IDisposable
{
readonly Mutex mutex = null; // 複数のプロセスから参照するために Mutex を使用
public bool IsExist // 既に存在しているかどうか
{
get
{
return !mutex.WaitOne(0, false); // Mutex が既に作成されていたら true
}
}
public Singleton(string uniqueName)
{
mutex = new Mutex(false, uniqueName); // コンストラクターで、このアプリケーション独自の名前で Mutex を作成
}
public void Dispose()
{ mutex.Dispose(); }
}
// Ipc によって複数のプロセスで共有するアイテムを格納する
class IpcRemoteObject : MarshalByRefObject
{
public object Item { get; set; }
// IpcRemoteObject に複数のプロセスで共有するアイテムを格納
public static void Send(string channelName, string itemName, object item)
{
var channel = new IpcClientChannel(); // IPC (プロセス間通信) クライアント チャンネルの生成
ChannelServices.RegisterChannel(channel, true); // チャンネルを登録
var remoteObject = Activator.GetObject(typeof(IpcRemoteObject), string.Format("ipc://{0}/{1}", channelName, itemName)) as IpcRemoteObject; // リモートオブジェクトを取得
if (remoteObject != null)
remoteObject.Item = item;
}
}
// 指定された時間毎に Tick イベントが起きるオブジェクトのインタフェイス
// アプリケーション側で実装する
public interface ITicker : IDisposable
{
event Action Tick;
void Start();
}
// 指定された時間毎に IpcRemoteObject をチェックし、空でなければ Receive イベントが起きる
class Receiver : IDisposable
{
public event Action<object> Receive;
IpcRemoteObject remoteObject;
readonly ITicker ticker;
public Receiver(string uniqueName, string itemName, ITicker ticker)
{
Initialize(uniqueName, itemName);
this.ticker = ticker;
ticker.Tick += OnTick;
ticker.Start();
}
public void Dispose()
{ ticker.Dispose(); }
void Initialize(string uniqueName, string itemName)
{
var channel = new IpcServerChannel(uniqueName); // このアプリケーション独自の名前で IPC (プロセス間通信) サーバー チャンネルを作成
ChannelServices.RegisterChannel(channel, true); // チャンネルを登録
remoteObject = new IpcRemoteObject(); // リモートオブジェクトを生成
RemotingServices.Marshal(remoteObject, itemName, typeof(IpcRemoteObject)); // リモートオブジェクトを公開
}
void OnTick()
{
if (remoteObject.Item != null) { // リモートオブジェクトがあれば
if (Receive != null)
Receive(remoteObject.Item); // Receive イベントを起こす
remoteObject.Item = null;
}
}
}
// 多重起動しないアプリケーション (他のプロセスが起動済みの場合は、その起動済みのプロセスにドキュメントを開かせ、終了する)
public class Application : IDisposable
{
const string itemName = "DocumentPathName";
Singleton singleton = null;
Receiver receiver = null;
// 初期化 (戻り値が偽の場合は終了)
public bool Initialize(ITicker ticker, string documentPath = "", Action<string> open = null)
{
var applicationProductName = ((AssemblyProductAttribute)Attribute.GetCustomAttribute(Assembly.GetExecutingAssembly(), typeof(AssemblyProductAttribute))).Product;
singleton = new Singleton(applicationProductName);
if (singleton.IsExist) { // 既に他のプロセスが起動していたら
IpcRemoteObject.Send(applicationProductName, itemName, documentPath); // その起動済みのプロセスにドキュメントのパスを送信して
return false; // 終了する
}
receiver = new Receiver(applicationProductName, itemName, ticker);
if (open != null) {
receiver.Receive += item => open((string)item); // Receive イベントが起きたらアイテムを open するように設定
if (!string.IsNullOrWhiteSpace(documentPath)) // documentPath が空でなければ
open(documentPath); // documentPath を開く
}
return true; // 終了しない
}
public void Dispose()
{
if (receiver != null) {
receiver.Dispose();
receiver = null;
}
if (singleton != null) {
singleton.Dispose();
singleton = null;
}
}
}
}
}
上記 SingleDocumentHelper クラスを WPF で使う場合は次のようになる。
App.xaml.cs のアプリケーション クラスのベース クラス。
タイマーに System.Windows.Threading.DispatcherTimer クラスを使う。
using System;
using System.Windows;
using System.Windows.Threading;
namespace SingleDocumentSample.Wpf
{
// WPF 多重起動防止アプリケーション
public class SingleDocumentApplication : Application
{
// WPF 用の ITicker の実装
// 指定された時間毎に Tick イベントが起きる
class Ticker : SingleDocumentHelper.ITicker
{
public event Action Tick;
public const int DefaultInterval = 1000;
readonly DispatcherTimer timer;
public Ticker(int interval = DefaultInterval)
{ timer = new DispatcherTimer { Interval = TimeSpan.FromMilliseconds(interval) }; }
public void Start()
{
// DispatcherTimer を使用して定期的に Tick イベントを起こす
timer.Tick += (sender, e) => RaiseTick();
timer.Start();
}
public void Dispose()
{ timer.Stop(); }
void RaiseTick()
{
if (Tick != null)
Tick();
}
}
public event Action<string> OpenDocument;
readonly SingleDocumentHelper.Application singleDocumentApplication = new SingleDocumentHelper.Application();
string documentPath = null;
protected string DocumentPath
{
get { return documentPath; }
set {
documentPath = value;
if (OpenDocument != null)
OpenDocument(value); // ドキュメントを開く
}
}
public SingleDocumentApplication(Window mainWindow)
{ MainWindow = mainWindow; }
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
if (e.Args.Length > 0) // コマンドライン引数があれば
DocumentPath = e.Args[0]; // ドキュメント ファイルのパスとする
if (singleDocumentApplication.Initialize(new Ticker(), DocumentPath, sourcePath => DocumentPath = sourcePath))
MainWindow.Show();
else
Shutdown(); // 他のプロセスが既に起動していれば終了
}
protected override void OnExit(ExitEventArgs e)
{
singleDocumentApplication.Dispose();
base.OnExit(e);
}
}
}
<l:SingleDocumentApplication x:Class="SingleDocumentSample.Wpf.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:l="clr-namespace:SingleDocumentSample.Wpf" />
上記 SingleDocumentApplication クラスから派生。
using System.Windows;
namespace SingleDocumentSample.Wpf
{
partial class App : SingleDocumentApplication
{
public App() : base(new MainWindow())
{ OpenDocument += sourcePath => ((MainWindow)MainWindow).DocumentPath = sourcePath; }
}
}
WebBrowser コントロールが貼ってあるだけ。
<Window x:Class="SingleDocumentSample.Wpf.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="SingleDocumentSample.Wpf" Height="500" Width="500">
<Grid>
<WebBrowser x:Name="browser" />
</Grid>
</Window>
指定されたパスや URL を WebBrowser コントロールで表示する。
using System;
using System.Windows;
namespace SingleDocumentSample.Wpf
{
partial class MainWindow : Window
{
public string DocumentPath
{
get { return browser.Source == null ? null : browser.Source.AbsolutePath; }
set {
browser.Source = new Uri(value, UriKind.RelativeOrAbsolute);
Title = value;
ToTop();
}
}
public MainWindow()
{ InitializeComponent(); }
// ウィンドウを最前面に表示
void ToTop()
{
if (WindowState == WindowState.Minimized)
WindowState = WindowState.Normal;
Activate();
}
}
}
そして、SingleDocumentHelper クラスを Windows フォームで使う場合は次のようになる。
Main メソッドで使用するためのクラス。
タイマーに System.Windows.Forms.Timer クラスを使う。
using System;
using System.Windows.Forms;
namespace SingleDocumentSample.WinForm
{
class SingleDocumentApplication
{
// ウィンドウズ フォーム用の ITicker の実装
// 指定された時間毎に Tick イベントが起きる
class Ticker : SingleDocumentHelper.ITicker
{
public event Action Tick;
public const int DefaultInterval = 1000;
readonly Timer timer;
public Ticker(int interval = DefaultInterval)
{ timer = new Timer { Interval = interval }; }
public void Start()
{
// Timer を使用して定期的に Tick イベントを起こす
timer.Tick += (sender, e) => RaiseTick();
timer.Start();
}
public void Dispose()
{ timer.Dispose(); }
void RaiseTick()
{
if (Tick != null)
Tick();
}
}
public event Action<string> OpenDocument;
string documentPath = null;
protected string DocumentPath
{
get { return documentPath; }
set {
documentPath = value;
if (OpenDocument != null)
OpenDocument(value); // ドキュメントを開く
}
}
public bool Run(Form mainForm)
{
var commandLineArgs = Environment.GetCommandLineArgs();
if (commandLineArgs.Length > 1) // コマンドライン引数があれば
DocumentPath = commandLineArgs[1]; // ドキュメント ファイルのパスとする
using (var singleDocumentApplication = new SingleDocumentHelper.Application()) {
if (singleDocumentApplication.Initialize(new Ticker(), DocumentPath, sourcePath => DocumentPath = sourcePath)) {
Application.Run(mainForm);
return true;
}
return false; // 他のプロセスが既に起動していれば終了
}
}
}
}
Main メソッド。
using System;
namespace SingleDocumentSample.WinForm
{
static class Program
{
[STAThread]
static void Main()
{
var application = new SingleDocumentApplication();
var mainForm = new MainForm();
application.OpenDocument += documentPath => mainForm.DocumentPath = documentPath;
application.Run(mainForm);
}
}
}
WebBrowser コントロールが貼ってある。
指定されたパスや URL を WebBrowser コントロールで表示する。
using System;
using System.Drawing;
using System.Windows.Forms;
namespace SingleDocumentSample.WinForm
{
class MainForm : Form
{
readonly WebBrowser browser;
public string DocumentPath
{
set {
browser.Navigate(new Uri(value, UriKind.RelativeOrAbsolute));
Text = value;
ToTop();
}
}
public MainForm()
{
SuspendLayout();
ClientSize = new Size(500, 500);
Text = "SingleDocumentSampleWinForm";
browser = new WebBrowser { Dock = DockStyle.Fill };
Controls.Add(browser);
ResumeLayout(false);
}
protected override void Dispose(bool disposing)
{
if (disposing)
browser.Dispose();
base.Dispose(disposing);
}
// ウィンドウを最前面に表示
void ToTop()
{
if (WindowState == FormWindowState.Minimized)
WindowState = FormWindowState.Normal;
Activate();
}
}
}
数回に渡って、C#/.NET によるメタプログラミングを紹介して行きたい。
先ずは概要から。
メタ (meta) は、「高次な-」「超-」等の意味の接頭語で、ギリシャ語から来ている。
プログラミングでは、メタプログラミングの他、メタクラス (=クラスがインスタンスとなるクラス)、メタデータ (データが持つそのデータ自身についての付加的なデータ) 等で用いられている。
メタプログラミングは、「高次なプログラミング」ということで、「プログラムを操作したり出力したりするプログラムを書くこと」だ。
プログラムでプログラムを出力する (メタプログラミング) 方が、手でプログラムを書くよりも効率的な場合がある。
プログラミングでは、「DRY (Don't repeat yourself) の原則」というものが重視される。「繰り返しを避けるべし」というものだ。
オブジェクト指向プログラミングやジェネリック プログラミング等では、抽象化を行うことで、或る程度コードの重複を避けることができる。
だが、「銀の弾などない」という言葉が示すように、オブジェクト指向を採用すれば、或いは、ジェネリック プログラミングを用いれば、それで常にうまく行く、という訳には行かない。何か一つのパラダイムで全てが解決する、というような特効薬などなく、状況に合った幾つかの方法を組み合わせて用いる必要がある。
メタプログラミングもそうしたものの一つだ。
例えば、似たようなプログラムが繰り返し必要な場合に、メタプログラミング、即ち、「プログラムを操作したり出力したりするプログラムを書くこと」で、プログラミングの手書きソースコードの重複や手書き作業の重複を避けられる場合がある。
メタプログラミングが有効な例として、次のようなものがある。
C#/.NET で、「プログラムを操作したり出力したりするプログラムを書く」為には、次のような技術が用意されている。
これらの他に、Visual Studio で T4 (Text Template Transformation Toolkit) Template という機能を使って、コードを生成させる方法もある。
次回から、これらの技術について、紹介して行こうと思う。
※ 「[C#][.NET] メタプログラミング入門 - はじめに」の続き。
前回は、C#/.NET でメタプログラミングを行う方法について述べた。
これから数回に渡って、それぞれの方法について紹介していきたい。
今回は、Reflection.Emit によるメソッドの動的生成だ。
簡単な例として int の足し算を行うだけのメソッドを作ってみたい。
次のようなものだ。
// 普通の静的な Add メソッド
static int Add(int x, int y)
{
return x + y;
}
Reflection.Emit では、CIL (Common Intermediate Language: 共通中間言語) を生成して、メソッド等を作成することができる。
先ずは、この Add メソッドの IL (Intermediate Language) がどのようなものかをツールを使って見てみよう。
アセンブリの IL は、.NET Reflector や ILSpy といったツールを使うことで見ることができる。
ILSpy は、無償で SourceForge.net の ILSpy - SharpDevelop からダウンロードして使うことができる。
例えば、次のようなコンソール アプリをビルドし、出来上がったアセンブリを ILSpy.exe で開いてみよう。
static class Program
{
// 普通の静的な Add メソッド
static int Add(int x, int y)
{
return x + y;
}
static void Main()
{}
}
これを Reflection.Emit を用いて生成してみよう。
実際にやってみると次のようになる。
using System;
using System.Reflection;
using System.Reflection.Emit;
static class Program
{
// Reflection.Emit の DynamicMethod による Add メソッドの生成
static Func<int, int, int> AddByEmit()
{
// DynamicMethod
var method = new DynamicMethod(
name : "add" ,
returnType : typeof(int) ,
parameterTypes: new[] { typeof(int), typeof(int)}
);
// 引数 x 生成用
var x = method.DefineParameter(position: 1, attributes: ParameterAttributes.In, parameterName: "x");
// 引数 y 生成用
var y = method.DefineParameter(position: 2, attributes: ParameterAttributes.In, parameterName: "y");
// ILGenerator
var generator = method.GetILGenerator();
// 生成したい IL
// IL_0000: ldarg.0
// IL_0001: ldarg.1
// IL_0002: add
// IL_0003: ret
// 「最初の引数をスタックにプッシュする」コードを生成
generator.Emit(opcode: OpCodes.Ldarg_0);
// 「二つ目の引数をスタックにプッシュ」コードを生成
generator.Emit(opcode: OpCodes.Ldarg_1);
// 「二つの値を加算する」コードを生成
generator.Emit(opcode: OpCodes.Add );
// 「リターンする」コードを生成
generator.Emit(opcode: OpCodes.Ret );
// 動的にデリゲートを生成
return (Func<int, int, int>)method.CreateDelegate(delegateType: typeof(Func<int, int, int>));
}
static void Main()
{
var addByEmit = AddByEmit(); // デリゲートを動的に生成
var answerByEmit = addByEmit(1, 2); // 生成したデリゲートの呼び出し
Console.WriteLine("answerByEmit: {0}", answerByEmit);
}
}
実行してみると、次のように正しく動作するのが分かるだろう。
answerByEmit: 3
今回は、Reflection.Emit を用いて、動的にメソッドを生成するプログラムを作成した。
次回は、他の方法も試してみよう。
※ 「[C#][.NET] メタプログラミング入門 - Reflection.Emit による Add メソッドの動的生成」の続き。
前回は、Reflection.Emit を用いて Add メソッドを動的生成するプログラムを作成した。
今回は、式木によるメソッドの動的生成だ。
今回も次の Add メソッドを生成する。
// 普通の静的な Add メソッド
static int Add(int x, int y)
{
return x + y;
}
前回は、ILSpy で IL を調べ、それを参考にしたた。
今回は式木として生成するため、先ずは Add メソッドにあたる式を作り、その構造を見て参考にしよう。
以前、「Expression の構造を調べてみる」で行ったように、
using System;
using System.Linq.Expressions;
class Program
{
static void Main()
{
Expression<Func<int, int, int>> add = (x, y) => x + y;
}
}
のようなプログラムを作成し、デバッグ実行で、add の構造を調べてみよう。
※ Visual Studio のデバッガーの「クイックウォッチ」の表示から一部抜粋
これを見ると、次のような構造をしていることが分かる。
これを式木を用いて生成してみよう。
実際にやってみると次のようになる。
using System;
using System.Linq.Expressions;
static class Program
{
// Expression (式) による Add メソッドの生成
static Func<int, int, int> AddByExpression()
{
// 生成したい式
// (int x, int y) => x + y
var x = Expression.Parameter(type: typeof(int)); // 引数 x の式
var y = Expression.Parameter(type: typeof(int)); // 引数 y の式
var add = Expression.Add (left: x, right: y); // x + y の式
var lambda = Expression.Lambda (add, x, y ); // (x, y) => x + y の式
// ラムダ式をコンパイルしてデリゲートとして返す
return (Func<int, int, int>)lambda.Compile();
}
static void Main()
{
var addByExpression = AddByExpression(); // デリゲートを動的に生成
var answerByExpression = addByExpression(1, 2); // 生成したデリゲートの呼び出し
Console.WriteLine("answerByExpression: {0}", answerByExpression);
}
}
Reflection.Emit を使った場合と比較すると、やや簡潔に書けるのが分かるだろう。
実行してみると、次のように正しく動作する。
answerByExpression: 3
今回は、式木を用いて、動的にメソッドを生成するプログラムを作成した。
次回は、更に他の方法も試してみよう。
※ 「[C#][.NET] メタプログラミング入門 - 式木による Add メソッドの動的生成」の続き。
前回は、式木を用いて Add メソッドを動的生成するプログラムを作成した。
今回は、Roslyn によるメソッドの動的生成だ。
Roslyn は、「Roslyn による Visual Studio のアドイン」で紹介したが、C# や Visual Basic のコンパイラーの内部の API 等を公開したものだ。
本稿執筆時点では CTP (Community Technology Preview) と呼ばれる評価版だが、これを用いて、C# のソースコードから、プログラムを生成することができる。
先ずは、Roslyn をインストールしよう。
Visual Studio の「ソリューション エクスプローラー」でプロジェクト名を右クリックし、「Nuget パッケージの管理...」を選ぶ。
「Nuget パッケージの管理」ダイアログボックスが開くので、右上の「オンラインの検索」エディット ボックスに「Roslyn」と入力し、検索する。
暫く待って表示されたリストの中から「Roslyn」を選び、「インストール」する。
今回も次の Add メソッドを生成する。
// 普通の静的な Add メソッド
static int Add(int x, int y)
{
return x + y;
}
Roslyn を使うソースコードは次のようになる。
using Roslyn.Scripting.CSharp;
using System;
static class Program
{
// Roslyn による Add メソッドの生成
static Func<int, int, int> AddByRoslyn()
{
var engine = new ScriptEngine(); // C# のスクリプトエンジン
var session = engine.CreateSession();
session.ImportNamespace("System"); // System 名前空間のインポート
return (Func<int, int, int>)session.Execute(code: "(Func<int, int, int>)((x, y) => x + y)");
}
static void Main()
{
var addByRoslyn = AddByRoslyn(); // デリゲートを動的に生成
var answerByRoslyn = addByRoslyn(1, 2); // 生成したメソッドの呼び出し
Console.WriteLine("answerByRoslyn: {0}", answerByRoslyn);
}
}
C# のソースコードを直接扱えるので、Reflection.Emit を使った場合や式木を使った場合と 比較して、とても簡潔に書ける。
実行してみると、これも次のように正しく動作する。
answerByRoslyn: 3
今回は、Roslyn を用いて、動的にメソッドを生成するプログラムを作成した。
これで三通りの方法を試したことになる。次回は、これらのパフォーマンスを比較してみよう。
※ 「[C#][.NET] メタプログラミング入門 - Roslyn による Add メソッドの動的生成」の続き。
前回まで、C# によるメタプログラミングで Add メソッドを動的生成するプログラムを作成してきた。
今回は、それぞれの手法における実行速度を測ってみよう。
それぞれの実行に掛かった時間を測る為、次のようなクラスを用意することにした。
using System;
using System.Diagnostics;
using System.Linq.Expressions;
public static class パフォーマンステスター
{
public static void テスト(Expression<Action> 処理式, int 回数, Action<string> output)
{
// 処理でなく処理式として受け取っているのは、文字列として出力する為
var 処理 = 処理式.Compile();
var 時間 = 計測(処理, 回数).TotalMilliseconds; // 回数分の処理に掛かったミリ秒数
// 一回当たり何秒掛かったかを出力
output(string.Format("{0,70}: {1,10:F}/{2} 秒", 処理式.Body.ToString(), 時間, 回数 * 1000));
}
static TimeSpan 計測(Action 処理, int 回数)
{
var stopwatch = new Stopwatch(); // 時間計測用
stopwatch.Start();
回数.回(処理);
stopwatch.Stop();
return stopwatch.Elapsed;
}
static void 回(this int @this, Action 処理)
{
for (var カウンター = 0; カウンター < @this; カウンター++)
処理();
}
}
それでは実際に測ってみよう。
先ずは、デリゲートの動的生成に掛かる時間。
これまでの三種類のコードを呼び出し、それぞれがデリゲートを作成するまでに掛かる時間を測ってみる。
using Roslyn.Scripting.CSharp;
using System;
using System.Linq.Expressions;
using System.Reflection;
using System.Reflection.Emit;
static class Program
{
// Reflection.Emit の DynamicMethod による Add メソッドの生成
static Func<int, int, int> AddByEmit()
{
// DynamicMethod
var method = new DynamicMethod(
name : "add",
returnType : typeof(int),
parameterTypes: new[] { typeof(int), typeof(int) }
);
// 引数 x 生成用
var x = method.DefineParameter(position: 1, attributes: ParameterAttributes.In, parameterName: "x");
// 引数 y 生成用
var y = method.DefineParameter(position: 2, attributes: ParameterAttributes.In, parameterName: "y");
// ILGenerator
var generator = method.GetILGenerator();
// 生成したい IL
// IL_0000: ldarg.0
// IL_0001: ldarg.1
// IL_0002: add
// IL_0003: ret
// 「最初の引数をスタックにプッシュする」コードを生成
generator.Emit(opcode: OpCodes.Ldarg_0);
// 「二つ目の引数をスタックにプッシュ」コードを生成
generator.Emit(opcode: OpCodes.Ldarg_1);
// 「二つの値を加算する」コードを生成
generator.Emit(opcode: OpCodes.Add );
// 「リターンする」コードを生成
generator.Emit(opcode: OpCodes.Ret );
// 動的にデリゲートを生成
return (Func<int, int, int>)method.CreateDelegate(delegateType: typeof(Func<int, int, int>));
}
// Expression (式) による Add メソッドの生成
static Func<int, int, int> AddByExpression()
{
// 生成したい式
// (int x, int y) => x + y
var x = Expression.Parameter(type: typeof(int)); // 引数 x の式
var y = Expression.Parameter(type: typeof(int)); // 引数 y の式
var add = Expression.Add (left: x, right: y); // x + y の式
var lambda = Expression.Lambda (add, x, y ); // (x, y) => x + y の式
// ラムダ式をコンパイルしてデリゲートとして返す
return (Func<int, int, int>)lambda.Compile();
}
// Roslyn による Add メソッドの生成
static Func<int, int, int> AddByRoslyn()
{
var engine = new ScriptEngine(); // C# のスクリプトエンジン
var session = engine.CreateSession();
session.ImportNamespace("System"); // System 名前空間のインポート
return (Func<int, int, int>)session.Execute(code: "(Func<int, int, int>)((x, y) => x + y)");
}
static void Main()
{
生成のパフォーマンステスト();
}
static void 生成のパフォーマンステスト()
{
Console.WriteLine("【{0}】", MethodBase.GetCurrentMethod().Name); // メソッド名を表示
const int 回数 = 1000;
パフォーマンステスト(() => AddByEmit (), 回数); // Reflectin.Emit による生成
パフォーマンステスト(() => AddByExpression(), 回数); // 式木による生成
パフォーマンステスト(() => AddByRoslyn (), 回数); // Roslyn による生成
}
static void パフォーマンステスト(Expression<Action> 処理式, int 回数)
{
パフォーマンステスター.テスト(処理式, 回数, Console.WriteLine);
}
}
実行してみよう。
【生成のパフォーマンステスト】
AddByEmit(): 7.43/1000000 秒
AddByExpression(): 77.82/1000000 秒
AddByRoslyn(): 3088.51/1000000 秒
速い順に並べてみよう。
| 順位 | 方法 | 時間 (マイクロ秒) |
|---|---|---|
| 1 | Reflection.Emit を使って動的にメソッドを生成した場合 | 7.43 |
| 2 | 式木を使って動的にメソッドを生成した場合 | 77.82 |
| 3 | Roslyn を使って動的にメソッドを生成した場合 | 3088.51 |
手間が掛からない方法程時間が掛かっているのが分かる。Roslyn は特に時間が掛かる。3088.51/1000000 秒ということは、約 0.003 秒も掛かっていることになる。
次は、それぞれの動的生成済みのデリゲートを実行する時間だ。
今度のコードでは、デリゲートを動的生成する迄の時間は測らず、生成後のデリゲートの実行時間を測る。
using Roslyn.Scripting.CSharp;
using System;
using System.Linq.Expressions;
using System.Reflection;
using System.Reflection.Emit;
static class Program
{
// 普通の静的な Add メソッド
static int Add(int x, int y)
{
return x + y;
}
// Reflection.Emit の DynamicMethod による Add メソッドの生成
static Func<int, int, int> AddByEmit()
{
// DynamicMethod
var method = new DynamicMethod(
name : "add",
returnType : typeof(int),
parameterTypes: new[] { typeof(int), typeof(int) }
);
// 引数 x 生成用
var x = method.DefineParameter(position: 1, attributes: ParameterAttributes.In, parameterName: "x");
// 引数 y 生成用
var y = method.DefineParameter(position: 2, attributes: ParameterAttributes.In, parameterName: "y");
// ILGenerator
var generator = method.GetILGenerator();
// 生成したい IL
// IL_0000: ldarg.0
// IL_0001: ldarg.1
// IL_0002: add
// IL_0003: ret
// 「最初の引数をスタックにプッシュする」コードを生成
generator.Emit(opcode: OpCodes.Ldarg_0);
// 「二つ目の引数をスタックにプッシュ」コードを生成
generator.Emit(opcode: OpCodes.Ldarg_1);
// 「二つの値を加算する」コードを生成
generator.Emit(opcode: OpCodes.Add );
// 「リターンする」コードを生成
generator.Emit(opcode: OpCodes.Ret );
// 動的にデリゲートを生成
return (Func<int, int, int>)method.CreateDelegate(delegateType: typeof(Func<int, int, int>));
}
// Expression (式) による Add メソッドの生成
static Func<int, int, int> AddByExpression()
{
var x = Expression.Parameter(type: typeof(int)); // 引数 x の式
var y = Expression.Parameter(type: typeof(int)); // 引数 y の式
var add = Expression.Add (left: x, right: y); // x + y の式
var lambda = Expression.Lambda (add, x, y ); // (x, y) => x + y の式
// ラムダ式をコンパイルしてデリゲートとして返す
return (Func<int, int, int>)lambda.Compile();
}
// Roslyn による Add メソッドの生成
static Func<int, int, int> AddByRoslyn()
{
var engine = new ScriptEngine();
var session = engine.CreateSession();
session.ImportNamespace("System"); // System 名前空間のインポート
return (Func<int, int, int>)session.Execute(code: "(Func<int, int, int>)((x, y) => x + y)");
}
static void Main()
{
実行のパフォーマンステスト();
}
static void 実行のパフォーマンステスト()
{
Console.WriteLine("【{0}】", MethodBase.GetCurrentMethod().Name); // メソッド名を表示
// それぞれのデリゲートを準備
Func<int, int, int> add = Add ;
var addByEmit = AddByEmit (); // Reflectin.Emit による生成
var addByExpression = AddByExpression(); // 式木による生成
var addByRoslyn = AddByRoslyn (); // Roslyn による生成
const int 回数 = 1000000;
パフォーマンステスト(() => Add (1, 2), 回数); // 静的な Add を直接呼ぶ
パフォーマンステスト(() => add (1, 2), 回数); // 静的な Add をデリゲートに入れて呼ぶ
パフォーマンステスト(() => addByEmit (1, 2), 回数); // Reflectin.Emit によって生成済みのデリゲートを呼ぶ
パフォーマンステスト(() => addByExpression(1, 2), 回数); // 式木によって生成済みのデリゲートを呼ぶ
パフォーマンステスト(() => addByRoslyn (1, 2), 回数); // Roslyn によって生成済みのデリゲートを呼ぶ
}
static void パフォーマンステスト(Expression<Action> 処理式, int 回数)
{
パフォーマンステスター.テスト(処理式, 回数, Console.WriteLine);
}
}
実行してみよう。
【実行のパフォーマンステスト】
Add(1, 2): 12.64/1000000000 秒
Invoke(value(Program+<>c__DisplayClass0).add, 1, 2): 6.72/1000000000 秒
Invoke(value(Program+<>c__DisplayClass0).addEmit, 1, 2): 6.68/1000000000 秒
Invoke(value(Program+<>c__DisplayClass0).addExpression, 1, 2): 4.45/1000000000 秒
Invoke(value(Program+<>c__DisplayClass0).addRoslyn, 1, 2): 6.55/1000000000 秒
速い順に並べてみよう。
| 順位 | 方法 | 時間 (ナノ秒) |
|---|---|---|
| 1 | 式木によって生成済みのデリゲートを呼ぶ | 4.45 |
| 2 | Roslyn によって生成済みのデリゲートを呼ぶ | 6.55 |
| 3 | Reflectin.Emit によって生成済みのデリゲートを呼ぶ | 6.68 |
| 4 | 静的な Add をデリゲートに入れて呼ぶ | 6.72 |
| 5 | 静的な Add を直接呼ぶ | 12.64 |
今度は、どの結果も大差ない。毎回メソッドを直接呼ぶよりは寧ろ速いことが分かる。
方法にも因るが、動的生成自体はそこそこハイコストであることが分かった。
従って、実行の都度、動的生成を行うのではなく、一度生成したデリゲートはキャッシュしておくのが有効だと思われる。
一度生成すれば、リフレクション等を用いた動的なコードとは異なり、通常のデリゲートなので、実行自体に時間が掛かる訳ではない。
次回からは、別のケースでキャッシュを用いた場合について検証していく。
※ 「[C#][.NET] メタプログラミング入門 - Add メソッドのパフォーマンスの比較」の続き。
前回は、C# によるメタプログラミングで Add メソッドを動的生成した場合の実行速度を測定した。
今回も同様に、メソッドを動的生成した場合の実行速度を測定しよう。今回は、メソッド呼び出しのパフォーマンスを検証する。
次の順番で進めていく。
先ずは、それぞれの実際の呼び出しの例を示そう。
先ず、試しに呼び出すメソッドを準備しよう。
// 検証用のクラス
public class Something
{
// 検証用のメソッド
public string DoSomething()
{
return "Hello!";
}
}
この Something クラスのインスタンスの DoSomething() メソッドを呼び出すこととする。
順次、それぞれの方法での呼び出しを書いて行こう。
// 普通の静的なメソッド呼び出し
static string Call(Something item)
{
// 静的なメソッド呼び出し
return item.DoSomething();
}
詳しくは以前の記事等を参考にしてほしい。
// リフレクションを使ったメソッド呼び出し
static object CallByReflection(object item)
{
// リフレクションを使って動的にメソッド情報を取得
var doSomething = item.GetType().GetMethod("DoSomething");
// メソッドの動的呼び出し
return doSomething.Invoke(item, null);
}
詳しくは以前の記事等を参考にしてほしい。
// dynamic を使ったメソッド呼び出し
static dynamic CallByDynamic(dynamic item)
{
// dynamic による動的なメソッド呼び出し
return item.DoSomething();
}
「[C#][.NET] メタプログラミング入門 - Reflection.Emit による Add メソッドの動的生成」でやったようにやってみよう。
生成したいコードを先ず C# で書いてビルドし、ILSpy で IL (Intermediate Language) を調べてみよう。
次のような C# のソースコードをビルドし、
// 検証用のクラス
public class Something
{
// 検証用のメソッド
public string DoSomething()
{
return "Hello!";
}
}
static class Program
{
// 普通の静的なメソッド呼び出し
static string Call(Something item)
{
return item.DoSomething();
}
static void Main()
{}
}
Call メソッドの部分を ILSpy で見る。
参考にして、Reflection.Emit でメソッド生成を行ってみよう。
// using namespace 省略
// Reflection.Emit の DynamicMethod によるメソッド呼び出しメソッドの生成
static Func<T, TResult> CallByEmit<T, TResult>(string methodName)
{
// DynamicMethod
var method = new DynamicMethod(
name : "call" ,
returnType : typeof(string) ,
parameterTypes: new[] { typeof(T) }
);
// 引数 item 生成用
var item = method.DefineParameter(position: 1, attributes: ParameterAttributes.In, parameterName: "item");
// ILGenerator
var generator = method.GetILGenerator();
// 生成したい IL
// IL_0000: ldarg.0
// IL_0001: callvirt instance void Something::DoSomething()
// IL_0006: ret
// 「引数をスタックにプッシュする」コードを生成
generator.Emit(opcode: OpCodes.Ldarg_0);
// 「指定された名前のメソッドを呼ぶ」コードを生成
generator.Emit(opcode: OpCodes.Callvirt, meth: typeof(T).GetMethod(name: methodName, types: Type.EmptyTypes));
// 「リターンする」コードを生成
generator.Emit(opcode: OpCodes.Ret);
// 動的にデリゲートを生成
return (Func<T, TResult>)method.CreateDelegate(delegateType: typeof(Func<T, TResult>));
}
「[C#][.NET][式木] メタプログラミング入門 - 式木による Add メソッドの動的生成」でやったようにやってみよう。
var item = new Something();
Expression<Func<Something, string>> call = item => item.DoSomething();
のようなコードを実行して、Visual Studio のデバッガーの「クイックウォッチ」等で call の構造を調べてみる。
これを参考にして、式木によるメソッド生成を行ってみよう。
// using namespace 省略
// Expression (式) によるメソッド呼び出しメソッドの生成
static Func<T, TResult> CallByExpression<T, TResult>(string methodName)
{
// 生成したい式の例:
// (T item) => item.methodName()
// 引数 item の式
var parameterExpression = Expression.Parameter(type: typeof(T), name: "item");
// item.methodName() の式
var callExpression = Expression.Call(
instance: parameterExpression,
method : typeof(T).GetMethod(methodName, Type.EmptyTypes)
);
// item => item.methodName() の式
var lambda = Expression.Lambda(callExpression, parameterExpression);
// ラムダ式をコンパイルしてデリゲートとして返す
return (Func<T, TResult>)lambda.Compile();
}
「[C#][.NET][Roslyn] メタプログラミング入門 - Roslyn による Add メソッドの動的生成」でやったようにやってみよう。
// using namespace 省略
// Roslyn によるメソッド呼び出しメソッドの生成
static Func<T, TResult> CallByRoslyn<T, TResult>(string methodName)
{
var engine = new ScriptEngine(); // C# のスクリプトエンジン
engine.AddReference(typeof(T).Assembly); // 型 T のメソッドを参照するのに必要
engine.ImportNamespace("System"); // System 名前空間のインポート
var session = engine.CreateSession();
// 作りたいソースコードの例
// (Func<Something, string>)(item => item.DoSomething())
// ソースコードを文字列として作成
var code = string.Format("(Func<{0}, {1}>)(item => item.{2}())", typeof(T).FullName, typeof(TResult).FullName, methodName);
// ソースコードをコンパイルしてデリゲートを生成して返す
return (Func<T, TResult>)session.Execute(code: code);
}
まとめると次のようになる。
using Roslyn.Scripting.CSharp;
using System;
using System.Linq.Expressions;
using System.Reflection;
using System.Reflection.Emit;
// 検証用のクラス
public class Something
{
// 検証用のメソッド
public string DoSomething()
{
return "Hello!";
}
}
static class Program
{
// 普通の静的なメソッド呼び出し
static string Call(Something item)
{
// 静的なメソッド呼び出し
return item.DoSomething();
}
// リフレクションを使ったメソッド呼び出し
static object CallByReflection(object item)
{
// リフレクションを使って動的にメソッド情報を取得
var doSomething = item.GetType().GetMethod("DoSomething");
// メソッドの動的呼び出し
return doSomething.Invoke(item, null);
}
// dynamic を使ったメソッド呼び出し
static dynamic CallByDynamic(dynamic item)
{
// dynamic による動的なメソッド呼び出し
return item.DoSomething();
}
// Reflection.Emit の DynamicMethod によるメソッド呼び出しメソッドの生成
static Func<T, TResult> CallByEmit<T, TResult>(string methodName)
{
// DynamicMethod
var method = new DynamicMethod(
name : "call" ,
returnType : typeof(string) ,
parameterTypes: new[] { typeof(T) }
);
// 引数 item 生成用
var item = method.DefineParameter(position: 1, attributes: ParameterAttributes.In, parameterName: "item");
// ILGenerator
var generator = method.GetILGenerator();
// 生成したい IL
// IL_0000: ldarg.0
// IL_0001: callvirt instance void Something::DoSomething()
// IL_0006: ret
// 「引数をスタックにプッシュする」コードを生成
generator.Emit(opcode: OpCodes.Ldarg_0);
// 「指定された名前のメソッドを呼ぶ」コードを生成
generator.Emit(opcode: OpCodes.Callvirt, meth: typeof(T).GetMethod(name: methodName, types: Type.EmptyTypes));
// 「リターンする」コードを生成
generator.Emit(opcode: OpCodes.Ret);
// 動的にデリゲートを生成
return (Func<T, TResult>)method.CreateDelegate(delegateType: typeof(Func<T, TResult>));
}
// Expression (式) によるメソッド呼び出しメソッドの生成
static Func<T, TResult> CallByExpression<T, TResult>(string methodName)
{
// 生成したい式の例:
// (T item) => item.methodName()
// 引数 item の式
var parameterExpression = Expression.Parameter(type: typeof(T), name: "item");
// item.methodName() の式
var callExpression = Expression.Call(
instance: parameterExpression,
method : typeof(T).GetMethod(methodName, Type.EmptyTypes)
);
// item => item.methodName() の式
var lambda = Expression.Lambda(callExpression, parameterExpression);
// ラムダ式をコンパイルしてデリゲートとして返す
return (Func<T, TResult>)lambda.Compile();
}
// Roslyn によるメソッド呼び出しメソッドの生成
static Func<T, TResult> CallByRoslyn<T, TResult>(string methodName)
{
var engine = new ScriptEngine(); // C# のスクリプトエンジン
engine.AddReference(typeof(T).Assembly); // 型 T のメソッドを参照するのに必要
engine.ImportNamespace("System"); // System 名前空間のインポート
var session = engine.CreateSession();
// 作りたいソースコードの例
// (Func<Something, string>)(item => item.DoSomething())
// ソースコードを文字列として作成
var code = string.Format("(Func<{0}, {1}>)(item => item.{2}())", typeof(T).FullName, typeof(TResult).FullName, methodName);
// ソースコードをコンパイルしてデリゲートを生成して返す
return (Func<T, TResult>)session.Execute(code: code);
}
static void Main()
{
実行のテスト();
}
static void 実行のテスト()
{
Console.WriteLine("【{0}】", MethodBase.GetCurrentMethod().Name); // メソッド名を表示
// テスト用のアイテム
var item = new Something();
// 普通の静的なメソッド呼び出し
var answer = Call (item);
Console.WriteLine("answer : {0}", answer );
// リフレクションによる動的なメソッド呼び出し
var answerByReflection = CallByReflection(item);
Console.WriteLine("answerByReflection: {0}", answerByReflection);
// dynamic による動的なメソッド呼び出し
var answerByDynamic = CallByDynamic (item);
Console.WriteLine("answerByDynamic : {0}", answerByDynamic );
// Reflection.Emit の DynamicMethod によるメソッド呼び出しメソッドの生成
var callByEmit = CallByEmit <Something, string>("DoSomething");
var answerByEmit = callByEmit (item); // 生成したメソッドの呼び出し
Console.WriteLine("answerByEmit : {0}", answerByEmit );
// Expression (式) によるメソッド呼び出しメソッドの生成
var callByExpression = CallByExpression<Something, string>("DoSomething");
var answerByExpression = callByExpression(item); // 生成したメソッドの呼び出し
Console.WriteLine("answerByExpression: {0}", answerByExpression);
// Roslyn によるメソッド呼び出しメソッドの生成
var callByRoslyn = CallByRoslyn <Something, string>("DoSomething");
var answerByRoslyn = callByRoslyn (item); // 生成したメソッドの呼び出し
Console.WriteLine("answerByRoslyn : {0}", answerByRoslyn );
}
}
実行してみると、次のようになる。
【実行のテスト】 answer : Hello! answerByReflection: Hello! answerByDynamic : Hello! answerByEmit : Hello! answerByExpression: Hello! answerByRoslyn : Hello!
いずれの場合も、ちゃんとテスト用のアイテム item のメソッド DoSomething() を呼び出せていることが分かる。
実行に掛かった時間を測る為、今回も、次のクラスを用意する。
using System;
using System.Diagnostics;
using System.Linq.Expressions;
public static class パフォーマンステスター
{
public static void テスト(Expression<Action> 処理式, int 回数, Action<string> output)
{
// 処理でなく処理式として受け取っているのは、文字列として出力する為
var 処理 = 処理式.Compile();
var 時間 = 計測(処理, 回数).TotalMilliseconds; // 回数分の処理に掛かったミリ秒数
// 一回当たり何秒掛かったかを出力
output(string.Format("{0,70}: {1,8:F}/{2} 秒", 処理式.Body.ToString(), 時間, 回数 * 1000));
}
static TimeSpan 計測(Action 処理, int 回数)
{
var stopwatch = new Stopwatch(); // 時間計測用
stopwatch.Start();
回数.回(処理);
stopwatch.Stop();
return stopwatch.Elapsed;
}
static void 回(this int @this, Action 処理)
{
for (var カウンター = 0; カウンター < @this; カウンター++)
処理();
}
}
それでは測っていこう。
では、デリゲートの動的生成に掛かる時間を測ってみよう。
前回同様、デリゲートを動的生成する三種類のコードを呼び出し、それぞれがデリゲートを作成するまでに掛かる時間を測ってみる。
using Roslyn.Scripting.CSharp;
using System;
using System.Linq.Expressions;
using System.Reflection;
using System.Reflection.Emit;
// 検証用のクラス
public class Something
{
// 検証用のメソッド
public string DoSomething()
{
return "Hello!";
}
}
static class Program
{
// Reflection.Emit の DynamicMethod によるメソッド呼び出しメソッドの生成
static Func<T, TResult> CallByEmit<T, TResult>(string methodName)
{
// DynamicMethod
var method = new DynamicMethod(
name : "call" ,
returnType : typeof(string) ,
parameterTypes: new[] { typeof(T) }
);
// 引数 item 生成用
var item = method.DefineParameter(position: 1, attributes: ParameterAttributes.In, parameterName: "item");
// ILGenerator
var generator = method.GetILGenerator();
// 生成したい IL
// IL_0000: ldarg.0
// IL_0001: callvirt instance void Something::DoSomething()
// IL_0006: ret
// 「引数をスタックにプッシュする」コードを生成
generator.Emit(opcode: OpCodes.Ldarg_0);
// 「指定された名前のメソッドを呼ぶ」コードを生成
generator.Emit(opcode: OpCodes.Callvirt, meth: typeof(T).GetMethod(name: methodName, types: Type.EmptyTypes));
// 「リターンする」コードを生成
generator.Emit(opcode: OpCodes.Ret);
// 動的にデリゲートを生成
return (Func<T, TResult>)method.CreateDelegate(delegateType: typeof(Func<T, TResult>));
}
// Expression (式) によるメソッド呼び出しメソッドの生成
static Func<T, TResult> CallByExpression<T, TResult>(string methodName)
{
// 引数 item の式
var parameterExpression = Expression.Parameter(type: typeof(T), name: "item");
// item.methodName() の式
var callExpression = Expression.Call(
instance: parameterExpression,
method : typeof(T).GetMethod(methodName, Type.EmptyTypes)
);
// item => item.methodName() の式
var lambda = Expression.Lambda(callExpression, parameterExpression);
// ラムダ式をコンパイルしてデリゲートとして返す
return (Func<T, TResult>)lambda.Compile();
}
// Roslyn によるメソッド呼び出しメソッドの生成
static Func<T, TResult> CallByRoslyn<T, TResult>(string methodName)
{
var engine = new ScriptEngine();
engine.AddReference(typeof(T).Assembly); // 型 T のメソッドを参照するのに必要
engine.ImportNamespace("System"); // System 名前空間のインポート
var session = engine.CreateSession();
// ソースコードを文字列として作成
var code = string.Format("(Func<{0}, {1}>)(item => item.{2}())", typeof(T).FullName, typeof(TResult).FullName, methodName);
// ソースコードをコンパイルしてデリゲートを生成して返す
return (Func<T, TResult>)session.Execute(code: code);
}
static void Main()
{
生成のパフォーマンステスト();
}
static void 生成のパフォーマンステスト()
{
Console.WriteLine("【{0}】", MethodBase.GetCurrentMethod().Name); // メソッド名を表示
const int 回数 = 1000;
// Reflectin.Emit による生成
パフォーマンステスト(() => CallByEmit <Something, string>("DoSomething"), 回数);
// 式木による生成
パフォーマンステスト(() => CallByExpression<Something, string>("DoSomething"), 回数);
// Roslyn による生成
パフォーマンステスト(() => CallByRoslyn <Something, string>("DoSomething"), 回数);
}
static void パフォーマンステスト(Expression<Action> 処理式, int 回数)
{
パフォーマンステスター.テスト(処理式, 回数, Console.WriteLine);
}
}
実行してみよう。
【生成のパフォーマンステスト】
CallByEmit("DoSomething"): 5.66/1000000 秒
CallByExpression("DoSomething"): 106.06/1000000 秒
CallByRoslyn("DoSomething"): 3474.70/1000000 秒
速い順に並べてみよう。
| 順位 | 方法 | 時間 (マイクロ秒) |
|---|---|---|
| 1 | Reflection.Emit を使って動的にメソッドを生成した場合 | 5.66 |
| 2 | 式木を使って動的にメソッドを生成した場合 | 106.06 |
| 3 | Roslyn を使って動的にメソッドを生成した場合 | 3474.70 |
前回同様、手間が掛からない方法程生成に時間が掛かっている。
次は、それぞれの方法によるデリゲートを実行する時間を測ろう。
デリゲートを動的生成場合は、動的生成する迄の時間は測らず、生成後のデリゲートの実行時間を測る。
using Roslyn.Scripting.CSharp;
using System;
using System.Linq.Expressions;
using System.Reflection;
using System.Reflection.Emit;
// 検証用のクラス
public class Something
{
// 検証用のメソッド
public string DoSomething()
{
return "Hello!";
}
}
static class Program
{
// 普通の静的なメソッド呼び出し
static string Call(Something item)
{
// 静的なメソッド呼び出し
return item.DoSomething();
}
// リフレクションを使ったメソッド呼び出し
static object CallByReflection(object item)
{
// リフレクションを使って動的にメソッド情報を取得
var doSomething = item.GetType().GetMethod("DoSomething");
// メソッドの動的呼び出し
return doSomething.Invoke(item, null);
}
// dynamic を使ったメソッド呼び出し
static dynamic CallByDynamic(dynamic item)
{
// dynamic による動的なメソッド呼び出し
return item.DoSomething();
}
// Reflection.Emit の DynamicMethod によるメソッド呼び出しメソッドの生成
static Func<T, TResult> CallByEmit<T, TResult>(string methodName)
{
// DynamicMethod
var method = new DynamicMethod(
name : "call" ,
returnType : typeof(string) ,
parameterTypes: new[] { typeof(T) }
);
// 引数 item 生成用
var item = method.DefineParameter(position: 1, attributes: ParameterAttributes.In, parameterName: "item");
// ILGenerator
var generator = method.GetILGenerator();
// 生成したい IL
// IL_0000: ldarg.0
// IL_0001: callvirt instance void Something::DoSomething()
// IL_0006: ret
// 「引数をスタックにプッシュする」コードを生成
generator.Emit(opcode: OpCodes.Ldarg_0);
// 「指定された名前のメソッドを呼ぶ」コードを生成
generator.Emit(opcode: OpCodes.Callvirt, meth: typeof(T).GetMethod(name: methodName, types: Type.EmptyTypes));
// 「リターンする」コードを生成
generator.Emit(opcode: OpCodes.Ret);
// 動的にデリゲートを生成
return (Func<T, TResult>)method.CreateDelegate(delegateType: typeof(Func<T, TResult>));
}
// Expression (式) によるメソッド呼び出しメソッドの生成
static Func<T, TResult> CallByExpression<T, TResult>(string methodName)
{
// 引数 item の式
var parameterExpression = Expression.Parameter(type: typeof(T), name: "item");
// item.methodName() の式
var callExpression = Expression.Call(
instance: parameterExpression,
method : typeof(T).GetMethod(methodName, Type.EmptyTypes)
);
// item => item.methodName() の式
var lambda = Expression.Lambda(callExpression, parameterExpression);
// ラムダ式をコンパイルしてデリゲートとして返す
return (Func<T, TResult>)lambda.Compile();
}
// Roslyn によるメソッド呼び出しメソッドの生成
static Func<T, TResult> CallByRoslyn<T, TResult>(string methodName)
{
var engine = new ScriptEngine();
engine.AddReference(typeof(T).Assembly); // 型 T のメソッドを参照するのに必要
engine.ImportNamespace("System"); // System 名前空間のインポート
var session = engine.CreateSession();
// ソースコードを文字列として作成
var code = string.Format("(Func<{0}, {1}>)(item => item.{2}())", typeof(T).FullName, typeof(TResult).FullName, methodName);
// ソースコードをコンパイルしてデリゲートを生成して返す
return (Func<T, TResult>)session.Execute(code: code);
}
static void Main()
{
実行のパフォーマンステスト();
}
static void 実行のパフォーマンステスト()
{
Console.WriteLine("【{0}】", MethodBase.GetCurrentMethod().Name); // メソッド名を表示
// 検証用のアイテム
var item = new Something();
// それぞれのデリゲートを準備
Func<Something, string > call = Call ;
Func<object , object > callByReflection = CallByReflection ;
Func<dynamic , dynamic> callByDynamic = CallByDynamic ;
var callByEmit = CallByEmit <Something, string>("DoSomething");
var callByExpression = CallByExpression<Something, string>("DoSomething");
var callByRoslyn = CallByRoslyn <Something, string>("DoSomething");
const int 回数 = 1000000;
// デリゲートの動的生成を行わない場合
パフォーマンステスト(() => Call (item), 回数); // 静的メソッドを直接呼ぶ場合
パフォーマンステスト(() => call (item), 回数); // 静的メソッドをデリゲートに入れてから呼ぶ場合
パフォーマンステスト(() => callByReflection(item), 回数); // リフレクションによる動的呼び出し
パフォーマンステスト(() => callByDynamic (item), 回数); // dynamic による動的呼び出し
// 生成済みのデリゲートを使って呼ぶ場合
パフォーマンステスト(() => callByEmit (item), 回数); // Reflectin.Emit による生成
パフォーマンステスト(() => callByExpression(item), 回数); // 式木による生成
パフォーマンステスト(() => callByRoslyn (item), 回数); // Roslyn による生成
}
static void パフォーマンステスト(Expression<Action> 処理式, int 回数)
{
パフォーマンステスター.テスト(処理式, 回数, Console.WriteLine);
}
}
実行してみよう。
【実行のパフォーマンステスト】
Call(value(Program+<>c__DisplayClass2).item): 16.03/1000000000 秒
Invoke(value(Program+<>c__DisplayClass2).call, value(Program+<>c__DisplayClass2).item): 10.74/1000000000 秒
Invoke(value(Program+<>c__DisplayClass2).callByReflection, value(Program+<>c__DisplayClass2).item): 316.62/1000000000 秒
Invoke(value(Program+<>c__DisplayClass2).callByDynamic, value(Program+<>c__DisplayClass2).item): 57.93/1000000000 秒
Invoke(value(Program+<>c__DisplayClass2).callByEmit, value(Program+<>c__DisplayClass2).item): 20.66/1000000000 秒
Invoke(value(Program+<>c__DisplayClass2).callByExpression, value(Program+<>c__DisplayClass2).item): 18.06/1000000000 秒
Invoke(value(Program+<>c__DisplayClass2).callByRoslyn, value(Program+<>c__DisplayClass2).item): 8.01/1000000000 秒
速い順に並べてみよう。
| 順位 | 方法 | 時間 (ナノ秒) |
|---|---|---|
| 1 | Roslyn を使って動的にメソッドを生成した場合 | 8.01 |
| 2 | 静的メソッドをデリゲートに入れてから呼ぶ場合 | 10.74 |
| 3 | 静的メソッドを直接呼ぶ場合 | 16.03 |
| 4 | 式木を使って動的にメソッドを生成した場合 | 18.06 |
| 5 | Reflection.Emit を使って動的にメソッドを生成した場合 | 20.66 |
| 6 | dynamic を使った動的なメソッド呼び出し | 57.93 |
| 7 | リフレクションを使った動的なメソッド呼び出し | 316.62 |
動的生成が終わってしまえば、その後のデリゲートの実行は通常のデリゲートと変わらない。
動的なメソッド呼び出しは呼び出す度に遅い (特にリフレクション) ので、場合によっては動的生成にパフォーマンス上のメリットがある。
つまり、動的にメソッドを生成する場合でも、生成済みのメソッドをうまくキャッシュしてやれば、パフォーマンスが大きく向上することになる。
キャッシュを使った場合と使わないで実行の度にメソッドを生成した場合の速度を測ってみよう。
先ずは、生成済みデリゲートをキャッシュして呼び出すクラスを作ろう。
using System;
using System.Collections.Generic;
using System.Text;
// デリゲートのキャッシュ
public static class DelegateCache
{
// 生成したデリゲートのキャッシュ
static readonly Dictionary<string, Delegate> methods = new Dictionary<string, Delegate>();
// メソッド呼び出し
public static TResult Call<T, TResult>(this T @this, string methodName, Func<string, Func<T, TResult>> generator)
{
var targetType = @this.GetType();
var key = ToKey(targetType, methodName);
ToCache<T, TResult>(key: key, item: @this, methodName: methodName, generator: generator); // キャッシュする
// キャッシュ内のデリゲートを呼ぶ
return ((Func<T, TResult>)methods[key: key])(@this);
}
// キャッシュに入れる
static void ToCache<T, TResult>(string key, T item, string methodName, Func<string, Func<T, TResult>> generator)
{
if (!methods.ContainsKey(key: key)) { // キャッシュに無い場合は
var method = generator(methodName); // 動的にデリゲートを生成して
methods.Add(key: key, value: method); // キャッシュに格納
}
}
// キーに変換
static string ToKey(Type type, string methodName)
{
return new StringBuilder().Append(type.FullName).Append(".").Append(methodName).ToString();
}
}
それでは、実際に測ってみよう。
using Roslyn.Scripting.CSharp;
using System;
using System.Linq.Expressions;
using System.Reflection;
using System.Reflection.Emit;
// 検証用のクラス
public class Something
{
// 検証用のメソッド
public string DoSomething()
{
return "Hello!";
}
}
static class Program
{
// 普通の静的なメソッド呼び出し
static string Call(Something item)
{
// 静的なメソッド呼び出し
return item.DoSomething();
}
// リフレクションを使ったメソッド呼び出し
static object CallByReflection(object item)
{
// リフレクションを使って動的にメソッド情報を取得
var doSomething = item.GetType().GetMethod("DoSomething");
// メソッドの動的呼び出し
return doSomething.Invoke(item, null);
}
// dynamic を使ったメソッド呼び出し
static dynamic CallByDynamic(dynamic item)
{
// dynamic による動的なメソッド呼び出し
return item.DoSomething();
}
// Reflection.Emit の DynamicMethod によるメソッド呼び出しメソッドの生成
static Func<T, TResult> CallByEmit<T, TResult>(string methodName)
{
// DynamicMethod
var method = new DynamicMethod(
name : "call" ,
returnType : typeof(string) ,
parameterTypes: new[] { typeof(T) }
);
// 引数 item 生成用
var item = method.DefineParameter(position: 1, attributes: ParameterAttributes.In, parameterName: "item");
// ILGenerator
var generator = method.GetILGenerator();
// 生成したい IL
// IL_0000: ldarg.0
// IL_0001: callvirt instance void Something::DoSomething()
// IL_0006: ret
// 「引数をスタックにプッシュする」コードを生成
generator.Emit(opcode: OpCodes.Ldarg_0);
// 「指定された名前のメソッドを呼ぶ」コードを生成
generator.Emit(opcode: OpCodes.Callvirt, meth: typeof(T).GetMethod(name: methodName, types: Type.EmptyTypes));
// 「リターンする」コードを生成
generator.Emit(opcode: OpCodes.Ret);
// 動的にデリゲートを生成
return (Func<T, TResult>)method.CreateDelegate(delegateType: typeof(Func<T, TResult>));
}
// Expression (式) によるメソッド呼び出しメソッドの生成
static Func<T, TResult> CallByExpression<T, TResult>(string methodName)
{
// 引数 item の式
var parameterExpression = Expression.Parameter(type: typeof(T), name: "item");
// item.methodName() の式
var callExpression = Expression.Call(
instance: parameterExpression,
method : typeof(T).GetMethod(methodName, Type.EmptyTypes)
);
// item => item.methodName() の式
var lambda = Expression.Lambda(callExpression, parameterExpression);
// ラムダ式をコンパイルしてデリゲートとして返す
return (Func<T, TResult>)lambda.Compile();
}
// Roslyn によるメソッド呼び出しメソッドの生成
static Func<T, TResult> CallByRoslyn<T, TResult>(string methodName)
{
var engine = new ScriptEngine();
engine.AddReference(typeof(T).Assembly); // 型 T のメソッドを参照するのに必要
engine.ImportNamespace("System"); // System 名前空間のインポート
var session = engine.CreateSession();
// ソースコードを文字列として作成
var code = string.Format("(Func<{0}, {1}>)(item => item.{2}())", typeof(T).FullName, typeof(TResult).FullName, methodName);
// ソースコードをコンパイルしてデリゲートを生成して返す
return (Func<T, TResult>)session.Execute(code: code);
}
static void Main()
{
キャッシュによる実行のパフォーマンステスト();
}
static void キャッシュによる実行のパフォーマンステスト()
{
Console.WriteLine("【{0}】", MethodBase.GetCurrentMethod().Name); // メソッド名を表示
// 検証用のアイテム
var item = new Something();
const int 回数 = 1000;
// コード生成しない場合
パフォーマンステスト(() => Call (item), 回数); // 静的メソッド
パフォーマンステスト(() => CallByReflection(item), 回数); // リフレクションによる動的呼び出し
パフォーマンステスト(() => CallByDynamic (item), 回数); // dynamic による動的呼び出し
// コード生成しつつ呼ぶ場合 (Reflection.Emit、式木、Roslyn の順)
パフォーマンステスト(() => CallByEmit <Something, string>("DoSomething")(item), 回数);
パフォーマンステスト(() => CallByExpression<Something, string>("DoSomething")(item), 回数);
パフォーマンステスト(() => CallByRoslyn <Something, string>("DoSomething")(item), 回数);
// コード生成にキャッシュを効かせた場合 (Reflection.Emit、式木、Roslyn の順)
パフォーマンステスト(() => item.Call<Something, string>("DoSomething", methodName => CallByEmit <Something, string>(methodName)), 回数);
パフォーマンステスト(() => item.Call<Something, string>("DoSomething", methodName => CallByExpression<Something, string>(methodName)), 回数);
パフォーマンステスト(() => item.Call<Something, string>("DoSomething", methodName => CallByRoslyn <Something, string>(methodName)), 回数);
}
static void パフォーマンステスト(Expression<Action> 処理式, int 回数)
{
パフォーマンステスター.テスト(処理式, 回数, Console.WriteLine);
}
}
実行結果は次の通り。
【キャッシュによる実行のパフォーマンステスト】
Call(value(Program+<>c__DisplayClass6).item): 0.02/1000000 秒
CallByReflection(value(Program+<>c__DisplayClass6).item): 0.41/1000000 秒
CallByDynamic(value(Program+<>c__DisplayClass6).item): 0.05/1000000 秒
Invoke(CallByEmit("DoSomething"), value(Program+<>c__DisplayClass6).item): 52.71/1000000 秒
Invoke(CallByExpression("DoSomething"), value(Program+<>c__DisplayClass6).item): 97.02/1000000 秒
Invoke(CallByRoslyn("DoSomething"), value(Program+<>c__DisplayClass6).item): 2349.83/1000000 秒
value(Program+<>c__DisplayClass6).item.Call("DoSomething", methodName => CallByEmit(methodName)): 6.88/1000000 秒
value(Program+<>c__DisplayClass6).item.Call("DoSomething", methodName => CallByExpression(methodName)): 3.36/1000000 秒
value(Program+<>c__DisplayClass6).item.Call("DoSomething", methodName => CallByRoslyn(methodName)): 3.49/1000000 秒
こちらも速い順に並べてみよう。
| 順位 | 方法 | 時間 (マイクロ秒) |
|---|---|---|
| 1 | 静的メソッド | 0.02 |
| 2 | dynamic による動的呼び出し | 0.05 |
| 3 | リフレクションによる動的呼び出し | 0.41 |
| 4 | 式木 (キャッシュ有り) | 3.36 |
| 5 | Roslyn (キャッシュ有り) | 3.49 |
| 6 | Reflection.Emit (キャッシュ有り) | 6.88 |
| 7 | Reflection.Emit (キャッシュ無し) | 52.71 |
| 8 | 式木 (キャッシュ無し) | 97.02 |
| 9 | Roslyn (キャッシュ無し) | 2349.83 |
キャッシュ テーブルのオーバーヘッドはあるが、動的生成を行う場合は、キャッシュがとても有効であることが分かる。
今回は、とても簡単なメソッド呼び出しの例で、静的な例、動的な例、動的にコードを生成する三通りの例の比較を行った。また、動的にコードを生成する場合にはキャッシュが有効であることを示した。
次回からは、もう少し応用例をあげていきたい。
※ 「[C#][.NET] メタプログラミング入門 - メソッド呼び出しのパフォーマンスの比較」の続き。
前回は、コード生成を行ってメソッド呼び出しを行う3通りの方法と静的なメソッド呼び出しや動的なメソッド呼び出しのパフォーマンスを比較した。
今回から、少しずつ応用に入っていきたい。
「[C#][.NET] メタプログラミング入門 - はじめに」で、メタプログラミングが有効な例として次のようなものがあると述べた。
メタプログラミングが有効な例
コンパイラー/インタープリター
ホスト言語のソースコードから動的に対象言語のプログラムを生成O/R マッパー
クラスやオブジェクトから動的に SQL を生成XML や JSON の入出力
クラスやオブジェクト等から動的に XML や JSON を生成/XML や JSON から動的にクラスやオブジェクト等を生成
(生成するプログラムをプログラムで生成)モック (mock) オブジェクト
モック (ユニットテストで用いられる代用のオブジェクト) を動的に生成
(生成するプログラムをプログラムで生成)Web アプリケーション
クライアント側で動作するプログラム (HTML、JavaScript 等) をサーバー側で動的に生成
この中から、今回は、「クラスやオブジェクト等から動的に文字列を生成する例」として、次にあげる例を見てみることにしよう。
これらについてメタプログラミング (動的コード生成) の例を示す前に、メタプログラミングによらない静的な方法とリフレクションによる動的な方法を見てみよう。
先ずは、基本である静的な方法だ。
文字列に変換する対象のクラスとして、次の単純な Book クラスを用意した。
// テスト用のクラス
public sealed class Book
{
public string Title { get; set; }
public int Price { get; set; }
}
この Book クラスに対して、次の3通りを行うメソッドをそれぞれ作成しよう。
これは、単純に文字列に変換するメソッドだ。Book クラスで ToString() メソッドをオーバーライドすることにする。
using System.Text;
// テスト用のクラス
public sealed class Book
{
public string Title { get; set; }
public int Price { get; set; }
// 文字列へ変換するメソッド ToString() をオーバーライド
public override string ToString()
{
return new StringBuilder()
.Append("Title: ").Append(Title)
.Append(", " )
.Append("Price: ").Append(Price)
.ToString();
// または:
//return string.Format("Title: {0}, Price: {1}", Title, Price);
}
}
次は、XML や HTML に変換するメソッドだ。
XML への変換の拡張メソッドと HTML の table への変換の拡張メソッドを、それぞれ作成してみる。
using System.Collections.Generic;
using System.Text;
public static class BookExtensions
{
// XML へ変換
public static string ToXml(this IEnumerable<Book> @this)
{
var stringBuilder = new StringBuilder();
stringBuilder.Append("<?xml version=\"1.0\"?>");
@this.ForEach(
book =>
stringBuilder.Append("<Book>" )
.Append("<Title>").Append(book.Title).Append("</Title>")
.Append("<Price>").Append(book.Price).Append("</Price>")
.Append("</Book>")
);
return stringBuilder.ToString();
}
// HTML の table へ変換
public static string ToHtmlTable(this IEnumerable<Book> @this)
{
var stringBuilder = new StringBuilder();
stringBuilder.Append("<table><thead><tr><th>Title</th><th>Price</th></tr></thead><tbody>");
@this.ForEach(
book => stringBuilder.Append("<tr><td>").Append(book.Title).Append("</td>" )
.Append( "<td>").Append(book.Price).Append("</td></tr>"));
return stringBuilder.Append("</tbody></table>").ToString();
}
}
上のコードで呼び出している ForEach メソッドは別クラスに拡張メソッドとして用意した。
using System;
using System.Collections.Generic;
// ForEach 用
public static class EnumerableExtensions
{
public static void ForEach<T>(this IEnumerable<T> @this, Action<T> action)
{
foreach (var item in @this)
action(item);
}
}
それでは、これらのメソッドを呼び出してみよう。
using System;
using System.Reflection;
static class Program
{
static void Main()
{
文字列への変換のテスト();
XMLへの変換のテスト ();
HTMLへの変換のテスト ();
}
static void 文字列への変換のテスト()
{
Console.WriteLine("【{0}】", MethodBase.GetCurrentMethod().Name); // メソッド名を表示
var book = new Book { Title = "Metaprogramming C#", Price = 3200 };
Console.WriteLine(book.ToString());
}
static void XMLへの変換のテスト()
{
Console.WriteLine("【{0}】", MethodBase.GetCurrentMethod().Name); // メソッド名を表示
var books = new[] {
new Book { Title = "Metaprogramming C#", Price = 3200 },
new Book { Title = "Metaprogramming VB", Price = 2100 },
new Book { Title = "Metaprogramming F#", Price = 4300 }
};
Console.WriteLine(books.ToXml());
}
static void HTMLへの変換のテスト()
{
Console.WriteLine("【{0}】", MethodBase.GetCurrentMethod().Name); // メソッド名を表示
var books = new[] {
new Book { Title = "Metaprogramming C#", Price = 3200 },
new Book { Title = "Metaprogramming VB", Price = 2100 },
new Book { Title = "Metaprogramming F#", Price = 4300 }
};
Console.WriteLine(books.ToHtmlTable());
}
}
実行結果は次のようになる。
【文字列への変換のテスト】 Title: Metaprogramming C#, Price: 3200 【XMLへの変換のテスト】 <?xml version="1.0"?><Book><Title>Metaprogramming C#</Title><Price>3200</Price></Book><Book><Title>Metaprogramming VB</Title><Pri ce>2100</Price></Book><Book><Title>Metaprogramming F#</Title><Price>4300</Price></Book> 【HTMLへの変換のテスト】 <table><thead><tr><th>Title</th><th>Price</th></tr></thead><tbody><tr><td>Metaprogramming C#</td><td>3200</td></tr><tr><td>Metapr ogramming VB</td><td>2100</td></tr><tr><td>Metaprogramming F#</td><td>4300</td></tr></tbody></table>
単純なデバッグ用の文字列と XML、HTML が出力されている。
上記の静的な方法には、大きな問題点がある。これでは、対象とする型ごとに手でコードを書かなければならない。
上では Book クラスを用いて試しているが、Book クラス以外のクラスを対象とする場合には、また同様の3つのメソッドを作る必要があるだろう。
そこで、そのようなことにならないように、今度は、型情報から動的に処理を行うようにしてみよう。リフレクションを使ってみる。
実際のコードは次のようになるだろう。静的な場合と比べてやや複雑になる。
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
// リフレクションによる型によらない各種文字列への変換 (リフレクション版)
public static class ToStringByReflectionExtensions
{
// 文字列へ変換 (リフレクション版)
public static string ToStringByReflection<T>(this T @this)
{
// リフレクションで public なインスタンス プロパティの情報を全て取得
var properties = @this.GetType().GetProperties(bindingAttr: BindingFlags.Instance | BindingFlags.Public);
// LINQ でプロパティの情報の集まりから、読み込み可能なものに絞り込み、名前と値から作った文字列の集まりにする
var textCollection = properties
.Where(property => property.CanRead)
.Select(property => string.Format("{0}: {1}", property.Name, property.GetValue(@this, null)));
// string.Join で連結
return string.Join(separator: ", ", values: textCollection);
}
// XML へ変換 (リフレクション版)
public static string ToXmlByReflection<T>(this IEnumerable<T> @this)
{
var stringBuilder = new StringBuilder();
stringBuilder.Append("<?xml version=\"1.0\"?>");
var itemType = typeof(T);
// 要素の型のプロパティの一覧を取得
var properties = itemType.GetProperties(BindingFlags.Instance | BindingFlags.Public)
.Where(property => property.CanRead);
@this.ForEach(
// 要素毎の XML
item => {
stringBuilder.Append("<" ).Append(itemType.Name).Append(">");
properties.ForEach(
// 要素のプロパティ毎の XML
property => stringBuilder.Append("<" ).Append(property.Name).Append(">")
.Append(property.GetValue(item))
.Append("</").Append(property.Name).Append(">")
);
stringBuilder.Append("</").Append(itemType.Name).Append(">");
}
);
return stringBuilder.ToString();
}
// HTML の table へ変換 (リフレクション版)
public static string ToHtmlTableByReflection<T>(this IEnumerable<T> @this)
{
var itemType = typeof(T);
// 要素の型のプロパティの一覧を取得
var properties = itemType.GetProperties(BindingFlags.Instance | BindingFlags.Public)
.Where(property => property.CanRead);
if (properties.Count() == 0)
return string.Empty;
var stringBuilder = new StringBuilder();
// table のヘッダー部の追加
stringBuilder.Append("<table><thead><tr>");
properties.ForEach(property => stringBuilder.Append("<th>").Append(property.Name).Append("</th>"));
stringBuilder.Append("</tr></thead>");
// table の本体を追加
stringBuilder.Append("<tbody>");
@this.ForEach(
item => {
stringBuilder.Append("<tr>");
properties.Where(property => property.CanRead)
.ForEach(
property =>
stringBuilder.Append("<td>").Append(property.GetValue(item)).Append("</td>")
);
stringBuilder.Append("</tr>");
}
);
return stringBuilder.Append("</tbody></table>").ToString();
}
}
コードでは、ジェネリック プログラミングによって型への依存を無くしている。また、リフレクションで動的に型情報を利用することでも対象とするオブジェクトの型に依存しない処理を実現している。
こうすることにより、Book 以外のクラスのオブジェクトにも用いることができ、対象とするオブジェクトの型ごとにコードを書かなくて済む訳だ。
こちらも呼び出してみよう。先の Program クラスを少し書き換えてリフレクション版のメソッドを呼ぶようにしてみる。
using System;
using System.Reflection;
static class Program
{
static void Main()
{
文字列への変換のテスト();
XMLへの変換のテスト ();
HTMLへの変換のテスト ();
}
static void 文字列への変換のテスト()
{
Console.WriteLine("【{0}】", MethodBase.GetCurrentMethod().Name); // メソッド名を表示
var book = new Book { Title = "Metaprogramming C#", Price = 3200 };
Console.WriteLine(book.ToStringByReflection());
}
static void XMLへの変換のテスト()
{
Console.WriteLine("【{0}】", MethodBase.GetCurrentMethod().Name); // メソッド名を表示
var books = new[] {
new Book { Title = "Metaprogramming C#", Price = 3200 },
new Book { Title = "Metaprogramming VB", Price = 2100 },
new Book { Title = "Metaprogramming F#", Price = 4300 }
};
Console.WriteLine(books.ToXmlByReflection());
}
static void HTMLへの変換のテスト()
{
Console.WriteLine("【{0}】", MethodBase.GetCurrentMethod().Name); // メソッド名を表示
var books = new[] {
new Book { Title = "Metaprogramming C#", Price = 3200 },
new Book { Title = "Metaprogramming VB", Price = 2100 },
new Book { Title = "Metaprogramming F#", Price = 4300 }
};
Console.WriteLine(books.ToHtmlTableByReflection());
}
}
実行してみると、結果は次のように静的な場合と変わらない。
【文字列への変換のテスト】 Title: Metaprogramming C#, Price: 3200 【XMLへの変換のテスト】 <?xml version="1.0"?><Book><Title>Metaprogramming C#</Title><Price>3200</Price></Book><Book><Title>Metaprogramming VB</Title><Pri ce>2100</Price></Book><Book><Title>Metaprogramming F#</Title><Price>4300</Price></Book> 【HTMLへの変換のテスト】 <table><thead><tr><th>Title</th><th>Price</th></tr></thead><tbody><tr><td>Metaprogramming C#</td><td>3200</td></tr><tr><td>Metapr ogramming VB</td><td>2100</td></tr><tr><td>Metaprogramming F#</td><td>4300</td></tr></tbody></table>
ここまで、オブジェクトを文字列に変換する静的な方法と動的な方法を示した。動的な方法では、リフレクションを使うことにより柔軟な処理を行うことができた。
しかし、リフレクションには、前回試したように実行速度が遅い、という欠点がある。
次回からは、メタプログラミングを行い、動的にコードを生成する方法を試していきたい。
※ 「[C#][.NET] メタプログラミング入門 - 応用編 - オブジェクトの文字列変換を静的/動的に行う」の続き。
前回は、メタプログラミングによらない静的な方法とリフレクションによる動的な方法で、オブジェクトを文字列に変換した。
今回から、メタプログラミングによる動的コード生成を試していきたい。
メタプログラミングによる動的コード生成を試す前に、前回試した2つの方法の問題点を再確認しておこう。
前回は、Book クラスをデバッグ用文字列、XML、HTML に変換する静的なプログラミングの例をあげた。
対象とする型ごとに手でコードを書かなければならない、というのが問題点だった。
前回は、ジェネリック プログラミングとリフレクションによって対象とするオブジェクトの型への依存を無くすことで、汎用的なプログラムを書くことができた。
だが、リフレクションは、動的に型情報を取得して利用する為に、実行速度が遅いという問題があった。
そこで考えられるアプローチが、メタプログラミングだ。
つまり、静的なプログラミングの「対象とする型ごとに手でコードを書かなければならない」の「手で」の部分を、プログラムで生成してしまえば良いのではないか、ということだ。 「対象とする型ごとにプログラムでコードを生成し、それを用いる」のだ。
コード生成自体は、対象とするオブジェクトの型に依存せずに行う。ジェネリック プログラミングとリフレクションも用いることになるだろう。
しかし、一旦生成してしまえば、それを実行するのは静的なコードと変わらない。
「[C#][.NET] メタプログラミング入門 - Add メソッドのパフォーマンスの比較」で調べたように、文字列変換の度にコード生成していたのでは、とても時間が掛かってしまう。「[C#][.NET] メタプログラミング入門 - メソッド呼び出しのパフォーマンスの比較」で行ったように、キャッシュを用いる必要があるだろう。生成したコードを対象の型毎にキャッシュにしまうことにしよう。
次回から、これまで行った3つの動的コード生成の方法で、オブジェクトの文字列への変換をやっていく。また、実行時のパフォーマンスに関しても調べることにしたい。
※ 「[C#][.NET] メタプログラミング入門 - 応用編 - オブジェクトの文字列変換のメタプログラミング」の続き。
それでは、Reflection.Emit を用いて文字列生成を行う例を見ていこう。
題材は 「[C#][.NET] メタプログラミング入門 - 応用編 - オブジェクトの文字列変換を静的/動的に行う」の中の「(デバッグ用の) 文字列に変換」だ。
今回も同様に、例えば、次のようなクラスのオブジェクトを文字列に変換する。
// テスト用のクラス
public sealed class Book
{
public string Title { get; set; }
public int Price { get; set; }
}
この Book クラスの場合、プログラムによって生成したいプログラムは、例えば、次のようなものだ。
// 動的に作りたいコードの例:
public static string ToString(Book book)
{
StringBuilder stringBuilder;
stringBuilder = new StringBuilder();
stringBuilder.Append("Title: ");
var title = book.Title;
stringBuilder.Append(title);
stringBuilder.Append(", ");
stringBuilder.Append("Price: ");
var price = book.Price;
stringBuilder.Append(price);
return stringBuilder.ToString();
}
今回も、「[C#][.NET] メタプログラミング入門 - Reflection.Emit による Add メソッドの動的生成」でやったように、ILSpy を使ってこのコードの IL (Intermediate Language) を表示し、参考にしよう。
上記 ToString(Book book) メソッドを含むプログラムをビルドし (Release ビルド)、ILSpy で開くと次のように表示される。
これを参考に IL を生成するコードを書いていこう。
これから書くプログラムは生成する方で、このプログラムで生成されるプログラムと混同しないように注意する必要がある。例えば、このプログラム生成プログラムは、Book クラスに依存しないように書く必要がある。動的で汎用的なものだ。これに対して、生成されるコードの方は、対象とするオブジェクトのクラス (例えば Book クラス) 専用の静的で高速なものだ。
表にしてみよう。
| プログラム | 手書き/生成 | 汎用性 | プログラムの動作 | 動作速度 |
|---|---|---|---|---|
| 文字列変換プログラムを生成するプログラム | 手書き | 対象とするオブジェクトのクラス (例えば Book クラス) に依存せず汎用的 | 動的 | 遅い |
| 文字列変換プログラム | プログラムによって生成 | 対象とするオブジェクトのクラス (例えば Book クラス) 専用 | 静的 | 速い |
では、IL を参考にプログラム生成プログラムを作ろう。
using System;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using System.Text;
// ToString メソッド生成器 (Reflection.Emit 版)
public static class ToStringGeneratorByEmit
{
// メソッドを生成
public static Func<T, string> Generate<T>()
{
// DynamicMethod
var method = new DynamicMethod(
name : "ToString" ,
returnType : typeof(string) ,
parameterTypes: new[] { typeof(T) }
);
// 引数 item 生成用
var item = method.DefineParameter(position: 1, attributes: ParameterAttributes.In, parameterName: "item");
// ILGenerator
var generator = method.GetILGenerator();
Generate(generator, typeof(T));
// 動的にデリゲートを生成
return (Func<T, string>)method.CreateDelegate(delegateType: typeof(Func<T, string>));
}
// メソッドを生成
static void Generate(ILGenerator ilGenerator, Type targetType)
{
// 対象とする型の全プロパティ情報を取得
var properties = targetType.GetProperties(bindingAttr: BindingFlags.Public | BindingFlags.Instance).Where(property => property.CanRead).ToArray();
if (properties.Length > 0) {
// 動的に作りたいコードの例 (実際のコードは targetType による):
//public static string ToText(Book book)
//{
// StringBuilder stringBuilder;
// stringBuilder = new StringBuilder();
// stringBuilder.Append("Title: ");
// var title = book.Title;
// stringBuilder.Append(title);
// stringBuilder.Append(", ");
// stringBuilder.Append("Price: ");
// var price = book.Price;
// stringBuilder.Append(price);
// return stringBuilder.ToString();
//}
// 動的に作りたい IL:
// newobj instance void [mscorlib]System.Text.StringBuilder::.ctor()
// stloc.0
// ldloc.0
var stringBuilderType = typeof(StringBuilder);
// 「ローカルに StringBuilder を宣言する」コードを追加
ilGenerator.DeclareLocal(localType: stringBuilderType);
// 「StringBuilder のコンストラクターを使ってインスタンスを new する」コードを追加
ilGenerator.Emit(opcode: OpCodes.Newobj, con: stringBuilderType.GetConstructor(Type.EmptyTypes));
// 「現在の値 (StringBuilder のインスタンスへの参照) をスタックからポップし、ローカル変数に格納する」コードを追加
ilGenerator.Emit(opcode: OpCodes.Stloc_0);
// 「ローカル変数 (StringBuilder のインスタンスへの参照) をスタックに読み込む」コードを追加
ilGenerator.Emit(opcode: OpCodes.Ldloc_0);
// プロパティ毎に文字列に変換するコードを生成
for (var index = 0; index < properties.Length; index++)
GenerateForEachProperty(ilGenerator: ilGenerator, property: properties[index], needsSeparator: index < properties.Length - 1);
// 「スタックからポップする」コードを追加
ilGenerator.Emit(opcode: OpCodes.Pop);
// 「ローカル変数 (StringBuilder のインスタンスへの参照) をスタックに読み込む」コードを追加
ilGenerator.Emit(opcode: OpCodes.Ldloc_0);
// 「StringBuilder のバーチャル メソッド ToString を呼ぶ」コードを追加
ilGenerator.Emit(opcode: OpCodes.Callvirt, meth: stringBuilderType.GetMethod(name: "ToString", types: Type.EmptyTypes));
} else {
// 動的に作りたい IL:
// ldstr ""
// 「空の文字列をプッシュする」コードを追加
ilGenerator.Emit(opcode: OpCodes.Ldstr, str: string.Empty);
}
// 動的に作りたい IL:
// ret
// 「リターンする」コードを追加
ilGenerator.Emit(opcode: OpCodes.Ret);
}
// 文字列が引数の StringBuilder.Append メソッドが何度も使われるため static メンバーに
static readonly MethodInfo appendMethod = typeof(StringBuilder).GetMethod(name: "Append", types: new[] { typeof(string) });
// プロパティ毎に文字列に変換するコードを生成
static void GenerateForEachProperty(ILGenerator ilGenerator, PropertyInfo property, bool needsSeparator)
{
// 動的に作りたいコードの例 (実際のコードは property による):
// stringBuilder.Append("Title: ");
// var title = item.Title;
// stringBuilder.Append(title);
// 動的に作りたい IL の例:
// ldstr "Title: "
// callvirt instance class [mscorlib]System.Text.StringBuilder [mscorlib]System.Text.StringBuilder::Append(string)
// ldarg.0
// callvirt instance string Book::get_Title()
// callvirt instance class [mscorlib]System.Text.StringBuilder [mscorlib]System.Text.StringBuilder::Append(string)
// 「プロパティ名 + ": " をプッシュする」コードを追加
ilGenerator.Emit(opcode: OpCodes.Ldstr, str: property.Name + ": ");
// 「文字列が引数の StringBuilder.Append を呼ぶ」コードを追加
ilGenerator.Emit(opcode: OpCodes.Callvirt, meth: appendMethod);
// 「インスタンスへの参照をプッシュする」コードを追加
ilGenerator.Emit(opcode: OpCodes.Ldarg_0);
var propertyGetMethod = property.GetGetMethod(); // 渡されたプロパティの get メソッド
// 「渡されたプロパティの get メソッドを呼ぶ」コードを追加
ilGenerator.Emit(propertyGetMethod.IsVirtual ? OpCodes.Callvirt : OpCodes.Call, propertyGetMethod);
var propertyGetMethodReturnType = propertyGetMethod.ReturnType; // 渡されたプロパティの get メソッドの戻り値の型
// 渡されたプロパティの get メソッドの戻り値の型が引数の StringBuilder.Append メソッド
var typedAppendMethod = typeof(StringBuilder).GetMethod(name: "Append", types: new[] { propertyGetMethodReturnType });
// 型が違っていて、値型だった場合はボクシングするコードを追加
if (typedAppendMethod.GetParameters()[0].ParameterType != propertyGetMethodReturnType &&
propertyGetMethodReturnType.IsValueType)
ilGenerator.Emit(opcode: OpCodes.Box, cls: propertyGetMethodReturnType);
// 「渡されたプロパティの get メソッドの戻り値の型が引数の StringBuilder.Append メソッドを呼ぶ」コードを追加
ilGenerator.Emit(opcode: OpCodes.Callvirt, meth: typedAppendMethod);
if (needsSeparator) {
// 動的に作りたいコード:
// stringBuilder.Append(", ");
// 動的に作りたい IL:
// ldstr ", "
// callvirt instance class [mscorlib]System.Text.StringBuilder [mscorlib]System.Text.StringBuilder::Append(string)
// 「", " をプッシュする」コードを追加
ilGenerator.Emit(opcode: OpCodes.Ldstr, str: ", ");
// 「文字列が引数の StringBuilder.Append メソッドを呼ぶ」コードを追加
ilGenerator.Emit(opcode: OpCodes.Callvirt, meth: appendMethod);
}
}
}
それでは、これを使って変換メソッドを作ろう。先ずは、単純な「メソッドをキャッシュをせず、毎回コード生成するもの」から。
// 改良前 (メソッドのキャッシュ無し)
public static class ToStringByEmitExtensions初期型
{
// ToString に代わる拡張メソッド (Reflection.Emit 版)
public static string ToStringByEmit初期型<T>(this T @this)
{
// 動的にメソッドを生成し、それを実行
return ToStringGeneratorByEmit.Generate<T>()(@this);
}
}
次のような簡単なプログラムで動作させてみよう。
using System;
static class Program
{
static void Main()
{
var book = new Book { Title = "Metaprogramming C#", Price = 3200 };
Console.WriteLine(book.ToStringByEmit初期型());
}
}
実行結果は次のようになる。
Title: Metaprogramming C#, Price: 3200
正しく動作する。
上の "ToStringByEmit初期型" メソッドでは、呼び出される度にメソッドを生成している。 これでは、その度に時間が掛かる。 一度生成したメソッドは、キャッシュしておくことにしたい。
型毎に1つずつメソッドが必要なので、型毎に最初にメソッドが必要になったときにだけ生成し、それをキャッシュしておくことにしよう。 2回目からは、キャッシュにあるメソッドを使用するのだ。
この為、先にメソッド キャッシュ クラスを作成しよう。 次のようなものだ。Dictionary の中に型情報をキーとしてメソッドを格納する。 このクラスでは、ジェネリックを使い型に依存しないようにする。
using System;
using System.Collections.Generic;
// 生成したメソッド用のキャッシュ
public class MethodCache<TResult>
{
// メソッド格納用
readonly Dictionary<Type, Delegate> methods = new Dictionary<Type, Delegate>();
// メソッドの呼び出し (メソッド生成用のメソッドを引数 generator として受け取る)
public TResult Call<T>(T item, Func<Func<T, TResult>> generator)
{
return Get<T>(generator)(item); // キャッシュにあるメソッドを呼び出す
}
// メソッドをキャッシュを介して取得 (メソッド生成用のメソッドを引数 generator として受け取る)
Func<T, TResult> Get<T>(Func<Func<T, TResult>> generator)
{
var targetType = typeof(T);
Delegate method;
if (!methods.TryGetValue(key: targetType, value: out method)) { // キャッシュに無い場合は
method = generator(); // 動的にメソッドを生成して
methods.Add(key: targetType, value: method); // キャッシュに格納
}
return (Func<T, TResult>)method;
}
}
では、キャッシュを行う「オブジェクトの文字列への変換」を作成しよう。
上のメソッド キャッシュ クラス MethodCache を利用して、次のようにする。
// 改良後 (メソッドのキャッシュ有り)
public static class ToStringByEmitExtensions改
{
// 生成したメソッドのキャッシュ
static readonly MethodCache<string> toStringCache = new MethodCache<string>();
// ToString に代わる拡張メソッド (Reflection.Emit 版)
public static string ToStringByEmit改<T>(this T @this)
{
// キャッシュを利用してメソッドを呼ぶ
return toStringCache.Call(item: @this, generator: ToStringGeneratorByEmit.Generate<T>);
}
}
こちらも動作させてみよう。
using System;
static class Program
{
static void Main()
{
var book = new Book { Title = "Metaprogramming C#", Price = 3200 };
Console.WriteLine(book.ToStringByEmit改());
}
}
勿論、実行結果は同じだ。
Title: Metaprogramming C#, Price: 3200
今回は、Reflection.Emit を用いて、動的に「オブジェクトを文字列に変換する」メソッドを生成するプログラムを作成した。
次回は、式木を使って同様のことを行う。
※ 「[C#][.NET] メタプログラミング入門 - 応用編 - オブジェクトの文字列変換のメタプログラミング (Reflection.Emit 編)」の続き。
Reflection.Emit の次は、式木によって文字列生成を行う例を見ていこう。
題材は同じく 「[C#][.NET] メタプログラミング入門 - 応用編 - オブジェクトの文字列変換を静的/動的に行う」の中の「(デバッグ用の) 文字列に変換」だ。
今回もまた同様に、例えば、次のようなクラスのオブジェクトを文字列に変換する。
// テスト用のクラス
public sealed class Book
{
public string Title { get; set; }
public int Price { get; set; }
}
この Book クラスの場合、プログラムによって生成したい「文字列変換を行うラムダ式」は、例えば、次のようなものだ。
// 動的に作りたいラムダ式の例 (実際のコードは targetType による):
item => new StringBuilder().Append("Title: ").Append(item.Title)
.Append(", ")
.Append("Price: ").Append(item.Price)
.ToString()
このようなラムダ式を、これまで何度か行ったように、動的に組み立てることにしよう。
但し、上のラムダ式は、Book クラスの場合の例で、対象とするオブジェクトのクラスによって、必要なラムダ式は異なる。
そのため、ラムダ式を生成する部分は、リフレクションを用いて動的に行うことにする。 また、ジェネリックを用いて、型に依存しないようにする。
生成したラムダ式は、コンパイルしてデリゲートにする。 デリゲートになってしまえば、それは静的なコードと同様に動作させることができる。
そして、今回もデリゲートをキャッシュしておくことで、文字列変換を行う度に毎回デリゲートを動的に生成しなくても良いようにしよう。
では、上のようなラムダ式を生成し、コンパイルしてデリゲートとするプログラムを作ろう。
using System;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Text;
// ToString メソッド生成器 (式木版)
public static class ToStringGeneratorByExpression
{
// メソッドを生成
public static Func<T, string> Generate<T>()
{
// 対象とする型の全プロパティ情報を取得
var properties = typeof(T).GetProperties(bindingAttr: BindingFlags.Public | BindingFlags.Instance)
.Where(property => property.CanRead)
.ToArray();
var parameterExpression = Expression.Parameter(typeof(T), "item");
LambdaExpression lambdaExpression;
if (properties.Length > 0) {
// 動的に作りたいラムダ式の例 (実際のコードは targetType による):
// item => new StringBuilder().Append("Title: ").Append(item.Title)
// .Append(", ")
// .Append("Price: ").Append(item.Price)
// .ToString()
var callGetTypeOfItemExpression = Expression.Call(
instance: parameterExpression ,
method : typeof(T).GetMethod("GetType")
);
Expression stringBuilderExpression = Expression.New(type: typeof(StringBuilder));
// プロパティ毎に文字列に変換する式を生成
for (var index = 0; index < properties.Length; index++)
stringBuilderExpression = GenerateForEachProperty(
property : properties[index] ,
expression : stringBuilderExpression ,
parameterExpression : parameterExpression ,
callGetTypeOfItemExpression: callGetTypeOfItemExpression ,
needsSeparator : index < properties.Length - 1
);
stringBuilderExpression = stringBuilderExpression.CallMethod(typeof(StringBuilder).GetMethod("ToString", Type.EmptyTypes));
lambdaExpression = Expression.Lambda(stringBuilderExpression, parameterExpression);
} else {
// 動的に作りたいラムダ式の例:
// item => string.Empty
lambdaExpression = Expression.Lambda(Expression.Constant(string.Empty, typeof(string)),
parameterExpression);
}
// ラムダ式をコンパイルしてデリゲートとして返す
return (Func<T, string>)lambdaExpression.Compile();
}
// 何度も使われるメソッドは static メンバーに
static readonly MethodInfo stringBuilderAppendStringMethod = typeof(StringBuilder).GetMethod("Append" , new[] { typeof(string) });
static readonly MethodInfo stringBuilderAppendObjectMethod = typeof(StringBuilder).GetMethod("Append" , new[] { typeof(object) });
static readonly MethodInfo typeGetPropertyMethod = typeof(Type ).GetMethod("GetProperty", new[] { typeof(string) });
static readonly MethodInfo propertyInfoGetValueMethod = typeof(PropertyInfo ).GetMethod("GetValue" , new[] { typeof(object) });
// プロパティ毎に文字列に変換する式を生成
static Expression GenerateForEachProperty(PropertyInfo property, Expression expression, ParameterExpression parameterExpression, MethodCallExpression callGetTypeOfItemExpression, bool needsSeparator)
{
// 例えば、item.Title の式を生成 (実際のコードは property による)
var callGetValueExpression = callGetTypeOfItemExpression
.CallMethod(typeGetPropertyMethod , Expression.Constant(property.Name) )
.CallMethod(propertyInfoGetValueMethod , parameterExpression );
// 例えば、stringBuilder.Append("Title: ").Append(item.Title) の式を生成 (実際のコードは property による)
expression = expression
.CallMethod(stringBuilderAppendStringMethod, Expression.Constant(property.Name + ": "))
.CallMethod(stringBuilderAppendObjectMethod, callGetValueExpression );
// 必要なら、stringBuilder.Append(", ") の式を生成
if (needsSeparator)
expression = expression.CallMethod(stringBuilderAppendStringMethod, Expression.Constant(", "));
return expression;
}
// Expression.Call をメソッドチェーンにするための拡張メソッド
static Expression CallMethod(this Expression @this, MethodInfo method, params Expression[] arguments)
{
return Expression.Call(@this, method, arguments);
}
}
それでは、これを使って変換メソッドを作ろう。先ずは、単純な「メソッドをキャッシュをせず、毎回コード生成するもの」から。
// 改良前 (メソッドのキャッシュ無し)
public static class ToStringByExpressionExtensions初期型
{
// ToString に代わる拡張メソッド (式木版)
public static string ToStringByExpression初期型<T>(this T @this)
{
// 動的にメソッドを生成し、それを実行
return ToStringGeneratorByExpression.Generate<T>()(@this);
}
}
今回も、次のような簡単なプログラムで動作させてみよう。
using System;
static class Program
{
static void Main()
{
var book = new Book { Title = "Metaprogramming C#", Price = 3200 };
Console.WriteLine(book.ToStringByExpression初期型());
}
}
実行結果は次のようになる。
Title: Metaprogramming C#, Price: 3200
正しく動作する。
前回同様、キャッシュを利用してみよう。
前回作成したメソッド キャッシュ クラスは次のようなものだった。
using System;
using System.Collections.Generic;
// 生成したメソッド用のキャッシュ
public class MethodCache<TResult>
{
// メソッド格納用
readonly Dictionary<Type, Delegate> methods = new Dictionary<Type, Delegate>();
// メソッドの呼び出し (メソッド生成用のメソッドを引数 generator として受け取る)
public TResult Call<T>(T item, Func<Func<T, TResult>> generator)
{
return Get<T>(generator)(item); // キャッシュにあるメソッドを呼び出す
}
// メソッドをキャッシュを介して取得 (メソッド生成用のメソッドを引数 generator として受け取る)
Func<T, TResult> Get<T>(Func<Func<T, TResult>> generator)
{
var targetType = typeof(T);
Delegate method;
if (!methods.TryGetValue(key: targetType, value: out method)) { // キャッシュに無い場合は
method = generator(); // 動的にメソッドを生成して
methods.Add(key: targetType, value: method); // キャッシュに格納
}
return (Func<T, TResult>)method;
}
}
では、キャッシュを行う「オブジェクトの文字列への変換」を作成しよう。
上のメソッドキャッシュ クラス MethodCache を利用して、次のようにする。
// 改良後 (メソッドのキャッシュ有り)
public static class ToStringByExpressionExtensions改
{
// 生成したメソッドのキャッシュ
static readonly MethodCache<string> toStringCache = new MethodCache<string>();
// ToString に代わる拡張メソッド (式木版)
public static string ToStringByExpression改<T>(this T @this)
{
// キャッシュを利用してメソッドを呼ぶ
return toStringCache.Call(item: @this, generator: ToStringGeneratorByExpression.Generate<T>);
}
}
こちらも動作させてみよう。
using System;
static class Program
{
static void Main()
{
var book = new Book { Title = "Metaprogramming C#", Price = 3200 };
Console.WriteLine(book.ToStringByExpression改());
}
}
実行結果は同じだ。
Title: Metaprogramming C#, Price: 3200
今回は、前回の Reflection.Emit を用いた方法に続き、式木を使って動的に「オブジェクトを文字列に変換する」メソッドを生成するプログラムを作成した。
次回は、Roslyn による方法だ。
※ 「[C#][.NET][式木] メタプログラミング入門 - 応用編 - オブジェクトの文字列変換のメタプログラミング (式木編)」の続き。
Roslyn によるメタプログラミングに関しては、以前、次にあげる記事で扱った。参考にしてほしい。
今回も、題材は同じく 「[C#][.NET] メタプログラミング入門 - 応用編 - オブジェクトの文字列変換を静的/動的に行う」の中の「(デバッグ用の) 文字列に変換」だ。
例えば、次のようなクラスのオブジェクトを文字列に変換する。
// テスト用のクラス
public sealed class Book
{
public string Title { get; set; }
public int Price { get; set; }
}
前回は、式木でラムダ式を組み立て、それをコンパイルすることにより、デリゲートを生成した。
Book クラスの場合を例にあげ、プログラムによって生成したい「文字列変換を行うラムダ式」として次のものを想定したのだった。
// 動的に作りたいラムダ式の例 (実際のコードは targetType による):
item => new StringBuilder().Append("Title: ").Append(item.Title)
.Append(", ")
.Append("Price: ").Append(item.Price)
.ToString()
対象とするオブジェクトのクラスによってラムダ式が異なるため、式木は動的に生成した。
今回は、「文字列変換を行うラムダ式」の C# のソースコードを動的に作成し、そのソースコードから Roslyn を用いてデリゲートを生成することにしよう。
先ず、Roslyn を使う前に、上のようなラムダ式の C# のソースコードを、リフレクションを用いて動的に作成する。
対象とするオブジェクトとその型から、C# のソースコードを文字列として作成するメソッドを書く。 この時、リフレクションとジェネリックを用いて、型に依存しないようにする。
// ToString メソッド生成器 (Roslyn 版)
public static class ToStringGeneratorByRoslyn
{
// ToString() メソッドの C# のソースコードを作成する
public static string CreateCodeOfToStringByRoslyn<T>()
{
// 動的に作りたい C# のソースコードの例 (実際のコードは typeof(T) による):
// item => new StringBuilder().Append("Title: ").Append(item.Title)
// .Append(", ")
// .Append("Price: ").Append(item.Price)
// .ToString()
var bodyCode = "new StringBuilder()" +
string.Join(".Append(\", \")",
typeof(T).GetProperties(BindingFlags.Instance | BindingFlags.Public)
.Where (property => property.CanRead)
.Select(property => string.Format(".Append(\"{0}: \").Append(item.{0})", property.Name))) +
".ToString()";
return string.Format("(Func<{0}, string>)(item => {1})", typeof(T).FullName, bodyCode);
}
}
これで正しい「文字列変換を行うラムダ式」の C# のソースコードが文字列として作成されるか、Book クラスの場合で試してみよう。
using System;
static class Program
{
static void Main()
{
var code = ToStringGeneratorByRoslyn.CreateCodeOfToStringByRoslyn<Book>();
Console.WriteLine(code);
}
}
実行してみよう。
(Func<Book, string>)(item => new StringBuilder().Append("Title: ").Append(item.Title).Append(", ").Append("Price: ").Append(item.
Price).ToString())
目的とするソースコードができているようだ。ここまでは、まだ Roslyn は使用していない。
この C# のソースコードから Roslyn を使ってデリゲートを生成しよう。このやり方は、「Roslyn による Add メソッドの動的生成」や「メソッド呼び出しのパフォーマンスの比較」で行ったのと同様だ。
次のようになる。
using Roslyn.Scripting.CSharp;
using System;
using System.Linq;
using System.Reflection;
// ToString メソッド生成器 (Roslyn 版)
public static class ToStringGeneratorByRoslyn
{
// メソッドを生成
public static Func<T, string> Generate<T>()
{
var code = CreateCodeOfToStringByRoslyn<T>(); // C# のソースコードを生成
return Generate<T>(code: code);
}
// Roslyn でメソッドを生成
static Func<T, string> Generate<T>(string code)
{
var engine = CreateEngine(); // スクリプトエンジン
var session = engine.CreateSession(); // 実行するには Session が必要
return (Func<T, string>)session.Execute(code: code); // コードの生成
}
// Roslyn のスクリプトエンジンを作成する
static ScriptEngine CreateEngine()
{
var engine = new ScriptEngine(); // Roslyn のスクリプトエンジン
engine.ImportNamespace(@namespace: "System" ); // System 名前空間を using
engine.ImportNamespace(@namespace: "System.Text"); // System.Text 名前空間を using
engine.AddReference(typeof(ToStringGeneratorByRoslyn).Assembly); // このアセンブリ内のクラスを使用する為に参照
return engine;
}
// ToString() メソッドの C# のソースコードを作成する
static string CreateCodeOfToStringByRoslyn<T>()
{
// 動的に作りたい C# のソースコードの例 (実際のコードは typeof(T) による):
// item => new StringBuilder().Append("Title: ").Append(item.Title)
// .Append(", ")
// .Append("Price: ").Append(item.Price)
// .ToString()
var bodyCode = "new StringBuilder()" +
string.Join(".Append(\", \")",
typeof(T).GetProperties(BindingFlags.Instance | BindingFlags.Public)
.Where(property => property.CanRead)
.Select(property => string.Format(".Append(\"{0}: \").Append(item.{0})", property.Name))) +
".ToString()";
return string.Format("(Func<{0}, string>)(item => {1})", typeof(T).FullName, bodyCode);
}
}
これを使って、これまでと同様、先ずはキャッシュ無しの変換メソッドを作ろう。 次のプログラムでは、呼ばれる度に毎回コードを生成する。
// 改良前 (メソッドのキャッシュ無し)
public static class ToStringByRoslynExtensions初期型
{
// ToString に代わる拡張メソッド (Roslyn 版)
public static string ToStringByRoslyn初期型<T>(this T @this)
{
return ToStringGeneratorByRoslyn.Generate<T>()(@this);
}
}
次のような簡単なプログラムで動作させてみよう。
using System;
static class Program
{
static void Main()
{
var book = new Book { Title = "Metaprogramming C#", Price = 3200 };
Console.WriteLine(book.ToStringByRoslyn初期型());
}
}
実行結果は次のようになり、正しく動作する。
Title: Metaprogramming C#, Price: 3200
では、キャッシュを利用してみよう。
今回もメソッド キャッシュ クラスを使う。
using System;
using System.Collections.Generic;
// 生成したメソッド用のキャッシュ
public class MethodCache<TResult>
{
// メソッド格納用
readonly Dictionary<Type, Delegate> methods = new Dictionary<Type, Delegate>();
// メソッドの呼び出し (メソッド生成用のメソッドを引数 generator として受け取る)
public TResult Call<T>(T item, Func<Func<T, TResult>> generator)
{
return Get<T>(generator)(item); // キャッシュにあるメソッドを呼び出す
}
// メソッドをキャッシュを介して取得 (メソッド生成用のメソッドを引数 generator として受け取る)
Func<T, TResult> Get<T>(Func<Func<T, TResult>> generator)
{
var targetType = typeof(T);
Delegate method;
if (!methods.TryGetValue(key: targetType, value: out method)) { // キャッシュに無い場合は
method = generator(); // 動的にメソッドを生成して
methods.Add(key: targetType, value: method); // キャッシュに格納
}
return (Func<T, TResult>)method;
}
}
では、キャッシュを行う「オブジェクトの文字列への変換」を作成しよう。
上のメソッドキャッシュ クラス MethodCache を利用して、次のようにする。
// 改良後 (メソッドのキャッシュ有り)
public static class ToStringByRoslynExtensions改
{
// 生成したメソッドのキャッシュ
static readonly MethodCache<string> toStringCache = new MethodCache<string>();
// ToString に代わる拡張メソッド (Roslyn 版)
public static string ToStringByRoslyn改<T>(this T @this)
{
// キャッシュを利用してメソッドを呼ぶ
return toStringCache.Call(item: @this, generator: ToStringGeneratorByRoslyn.Generate<T>);
}
}
こちらも動作させてみよう。
using System;
static class Program
{
static void Main()
{
var book = new Book { Title = "Metaprogramming C#", Price = 3200 };
Console.WriteLine(book.ToStringByRoslyn改());
}
}
やはり、実行結果は同じだ。
Title: Metaprogramming C#, Price: 3200
今回は、前回の式木を用いた方法に続き、Roslyn を使って動的に「オブジェクトを文字列に変換する」メソッドを生成するプログラムを作成した。
次回は、メタプログラミングによる文字列変換のまとめとして、それぞれの方法でのパフォーマンスの比較を行う。
※ 「[C#][.NET][Roslyn] メタプログラミング入門 - 応用編 - オブジェクトの文字列変換のメタプログラミング (Roslyn 編)」の続き。
ここまで、8通りの文字列変換メソッドを見てきた。
これらの方法について実行速度を比較していこう。
静的なメソッドに比べ、リフレクションによる動的なメソッドでは実行速度が低下する筈だ。また、メソッドの動的生成自体は遅いが、キャッシュがうまく効けば、リフレクションよりも速度面で有利な筈だ。
「Add メソッドのパフォーマンスの比較」や「メソッド呼び出しのパフォーマンスの比較」で行ったように、実行速度を測ってみよう。
先ず、パフォーマンス測定用のクラスを用意する。
using System;
using System.Diagnostics;
using System.Linq.Expressions;
public static class パフォーマンステスター
{
public static void テスト(Expression<Action> 処理式, int 回数, Action<string> output)
{
// 処理でなく処理式として受け取っているのは、文字列として出力する為
var 処理 = 処理式.Compile();
var 時間 = 計測(処理, 回数).TotalMilliseconds; // 回数分の処理に掛かったミリ秒数
// 一回当たり何秒掛かったかを出力
output(string.Format("{0,64}: {1,8:F}/{2} 秒", 処理式.Body.ToString(), 時間, 回数 * 1000));
}
static TimeSpan 計測(Action 処理, int 回数)
{
var stopwatch = new Stopwatch(); // 時間計測用
stopwatch.Start();
回数.回(処理);
stopwatch.Stop();
return stopwatch.Elapsed;
}
static void 回(this int @this, Action 処理)
{
for (var カウンター = 0; カウンター < @this; カウンター++)
処理();
}
}
テスト用のクラスは、これまでと同じ Book クラスだ。
// テスト用のクラス
public sealed class Book
{
public string Title { get; set; }
public int Price { get; set; }
}
では、計ってみよう。
次のプログラムを走らせてみる。
using System;
using System.Linq.Expressions;
using System.Reflection;
static class Program
{
static void Main()
{
パフォーマンステスト();
}
static void パフォーマンステスト()
{
Console.WriteLine("【{0}】", MethodBase.GetCurrentMethod().Name); // メソッド名を表示
var book = new Book { Title = "メタプログラミング C#", Price = 3800 };
パフォーマンステスト(() => book.ToString ());
パフォーマンステスト(() => book.ToStringByReflection ());
パフォーマンステスト(() => book.ToStringByEmit初期型 ());
パフォーマンステスト(() => book.ToStringByEmit改 ());
パフォーマンステスト(() => book.ToStringByExpression初期型());
パフォーマンステスト(() => book.ToStringByExpression改 ());
パフォーマンステスト(() => book.ToStringByRoslyn初期型 ());
パフォーマンステスト(() => book.ToStringByRoslyn改 ());
}
static void パフォーマンステスト(Expression<Action> 処理式)
{
const int 回数 = 1000;
パフォーマンステスター.テスト(処理式, 回数, Console.WriteLine);
}
}
次のようになった。勿論、動作環境などによって結果は異なる。
【パフォーマンステスト】
value(Program+<>c__DisplayClass1).book.ToString(): 1.11/1000000 秒
value(Program+<>c__DisplayClass1).book.ToStringByReflection(): 20.06/1000000 秒
value(Program+<>c__DisplayClass1).book.ToStringByEmit初期型(): 716.75/1000000 秒
value(Program+<>c__DisplayClass1).book.ToStringByEmit改(): 4.84/1000000 秒
value(Program+<>c__DisplayClass1).book.ToStringByExpression初期型(): 466.80/1000000 秒
value(Program+<>c__DisplayClass1).book.ToStringByExpression改(): 2.65/1000000 秒
value(Program+<>c__DisplayClass1).book.ToStringByRoslyn初期型(): 4311.85/1000000 秒
value(Program+<>c__DisplayClass1).book.ToStringByRoslyn改(): 6.02/1000000 秒
速い順に並べてみよう。
| 順位 | 方法 | 時間 (マイクロ秒) |
|---|---|---|
| 1 | ToString() - ToString() をオーバーライドした静的なメソッド | 1.11 |
| 2 | ToStringByExpressionExtensions改() - キャッシュ有り | 2.65 |
| 3 | ToStringByEmit改() - キャッシュ有り | 4.84 |
| 4 | ToStringByRoslyn改() - キャッシュ有り | 6.02 |
| 5 | ToStringByReflection() - リフレクションによる動的なメソッド | 20.06 |
| 6 | ToStringByExpressionExtensions初期型() - キャッシュ無し | 466.80 |
| 7 | ToStringByEmit初期型() - キャッシュ無し | 716.75 |
| 8 | ToStringByRoslyn初期型() - キャッシュ無し | 4311.85 |
ほぼ予想通りの結果となった。
静的なメソッドが最速だ。メソッドの動的生成はかなり遅い。キャッシュを行うことでこれらを大きく改善でき、場合によるが、リフレクションに対しても速度上のメリットがある。
今回は、メタプログラミングによる文字列変換のまとめとして、パフォーマンスの比較を行った。
明日から、Microsoft MVP Global Summit に参加予定。今年は二回目。
世界中から、招待された数千人の Microsoft MVP が集まり、米国ワシントン州のマイクロソフト本社及びその周辺で、製品チームのマイクロソフトの社員と共に、沢山のマイクロソフトのテクノロジーに関するセッションが行われる。
セッションの内容も素晴らしいが、毎晩のように開催されるパーティなどでの、各国の MVP やマイクロソフトの技術者との交流の機会もとても貴重なものだ。
※ 「November 2013 MVP Global Summit - 出発前」の続き。
Microsoft MVP Global Summit に参加してきた。9回めの参加。
C# の Microsoft MVP として、多くの技術セッションに参加することができた。
セッションを含めて英語漬けの毎日だったが、1人で他国の人達のテーブルに混ざって雑談する等、英語でのコミュニケーションにも少しは慣れてきた。
NDA (Non-disclosure agreement: 秘密保持契約) の為セッション内容等は公開できず、観光や食事、パーティの光景ばかりになるが、一部紹介したい。
時差対策もあり、Global Summit の一日前に着いて、隣国カナダのバンクーバーにドライブしてみることにした。 シアトル (Seattle) には何度も行っているが、カナダは初めて行った。
時差は17時間あり、この日は長い一日となる。
Global Summit の一日目。
Global Summit の二日目。 レドモンドのマイクロソフト キャンパスでの技術セッションが始まった。
Global Summit の三日目。 この日もレドモンドのマイクロソフト キャンパスでの技術セッション。
Global Summit の四日目。 この日もレドモンドのマイクロソフト キャンパスでの技術セッション。
Global Summit は、もう一日続いたが、そちらには参加せず帰った。
今回も、技術的にも多くの知識を得られた。
そして、マイクロソフトの方や多くの MVP の方と沢山の交流ができたのが何よりの財産だ。
参加の度に強く感じることだが、このような機会が得られたことは、エンジニアとしてとても幸せなことだと思う。
※ 「[C#][.NET] メタプログラミング入門 - 応用編 - オブジェクトの文字列変換のメタプログラミング (パフォーマンスのテスト)」の続き。
これまで、Reflection.Emit、式木、Roslyn による動的コード生成を試してきた。これらは、比較的新しい方法だが、実は .NET Framework には初期の頃から動的コード生成を行う仕組みが備わっていた。
それが、CodeDOM (Code Document Object Model) だ。
System.CodeDom 名前空間や System.CodeDom.Compiler 名前空間にあるクラスを使うことで、C# や Visual Basic.NET 等のコードを生成することができる。
今回は、CodeDOM を使って Hello world! を表示する簡単なプログラムを生成してみよう。
CodeDOM を使って、次の手順でプログラムを組み立てていく。
では、やってみよう。 System.CodeDom 名前空間を使用する。
using System.CodeDom;
class Program
{
// Hello world プログラムの CodeDOM
static CodeNamespace HelloWorldCodeDom()
{
// CodeDomHelloWorldDemo 名前空間
var nameSpace = new CodeNamespace(name: "CodeDomHelloWorldDemo");
// System 名前空間のインポート
nameSpace.Imports.Add(new CodeNamespaceImport(nameSpace: "System"));
// Program クラス
var programClass = new CodeTypeDeclaration(name: "Program");
// Program クラスの CodeDomHelloWorldDemo 名前空間への追加
nameSpace.Types.Add(programClass);
// Main メソッド
var mainMethod = new CodeMemberMethod { Attributes = MemberAttributes.Static, Name = "Main" };
// Main メソッドの中身に文を追加
mainMethod.Statements.Add(
new CodeMethodInvokeExpression( // 関数呼び出し式
targetObject: new CodeSnippetExpression("Console") , // オブジェクト名: Console.
methodName : "WriteLine" , // メソッド名 : WriteLine
parameters : new CodePrimitiveExpression("Hello world!") // 引数 : ("Hello world!")
)
);
// Main メソッドの中身に文を追加
mainMethod.Statements.Add(
new CodeMethodInvokeExpression( // 関数呼び出し式
targetObject: new CodeSnippetExpression("Console"), // オブジェクト名: Console.
methodName : "ReadKey" // メソッド名 : ReadKey()
)
);
// Program クラスに Main メソッドを追加
programClass.Members.Add(mainMethod);
return nameSpace;
}
static void Main()
{
// Hello world プログラムの名前空間を生成
var helloWorldCodeDom = HelloWorldCodeDom();
}
}
この HelloWorldCodeDom メソッドで、Hello world! を表示する Main メソッドを含む名前空間を生成したことになる。
次に、上で作った名前空間から CodeDOM を使って C# のソースコードを生成してみる。 System.CodeDom.Compiler 名前空間を使用する。
using System;
using System.CodeDom;
using System.CodeDom.Compiler;
using System.IO;
using System.Text;
class Program
{
// Hello world プログラムの CodeDOM
static CodeNamespace HelloWorldCodeDom()
{
…… 同じなので省略 ……
}
// 名前空間からソースコードを生成
static string GenerateCode(CodeNamespace codeNamespace)
{
// コンパイル オプション
var compilerOptions = new CodeGeneratorOptions { IndentString = " ", BracingStyle = "C" };
var codeText = new StringBuilder();
using (var codeWriter = new StringWriter(codeText)) {
// 名前空間からソースコードを生成
CodeDomProvider.CreateProvider("C#").GenerateCodeFromNamespace(codeNamespace, codeWriter, compilerOptions);
}
return codeText.ToString(); // 生成されたソースコード
}
static void Main()
{
// Hello world プログラムの名前空間を生成
var helloWorldCodeDom = HelloWorldCodeDom();
// Hello world プログラムのソースコードを生成
var code = GenerateCode(helloWorldCodeDom);
Console.WriteLine(code); // 表示
}
}
実行してみると、次のように C# のソースコードが表示される。
namespace CodeDomHelloWorldDemo
{
using System;
public class Program
{
static void Main()
{
Console.WriteLine("Hello world!");
Console.ReadKey();
}
}
}
ちなみに、上記 GenerateCode メソッド中の "C#" とある部分を "VB" と置き換えて実行してみると、次のように Visual Basic.NET のソースコードとなる。
Imports System
Namespace CodeDomHelloWorldDemo
Public Class Program
Shared Sub Main()
Console.WriteLine("Hello world!")
Console.ReadKey
End Sub
End Class
End Namespace
"C#" を "JScript" に置き換えた場合は、次のようになる。
//@cc_on
//@set @debug(off)
import System;
package CodeDomHelloWorldDemo
{
public class Program
{
private static function Main()
{
Console.WriteLine("Hello world!");
Console.ReadKey();
}
}
}
では次に、CodeDOM で生成した名前空間をコンパイルしてアセンブリを生成してみよう。
先程までのプログラムに、更に書き足して、次のようにする。
using System;
using System.CodeDom;
using System.CodeDom.Compiler;
using System.Diagnostics;
using System.IO;
using System.Text;
class Program
{
// Hello world プログラムの CodeDOM
static CodeNamespace HelloWorldCodeDom()
{
…… 同じなので省略 ……
}
// 名前空間からソースコードを生成
static string GenerateCode(CodeNamespace codeNamespace)
{
…… 同じなので省略 ……
}
// 名前空間を実行可能アセンブリへコンパイル
static void CompileExecutableAssembly(CodeNamespace codeNamespace, string outputAssemblyName)
{
var codeCompileUnit = new CodeCompileUnit(); // コンパイル単位
codeCompileUnit.Namespaces.Add(codeNamespace); // コンパイル単位に名前空間を追加
// 実行可能アセンブリへコンパイル
CodeDomProvider.CreateProvider("C#").CompileAssemblyFromDom(
options : new CompilerParameters { // コンパイル オプション
OutputAssembly = outputAssemblyName, // 出力アセンブリのファイル名
GenerateExecutable = true // 実行可能なアセンブリを生成
},
compilationUnits: codeCompileUnit // コンパイル単位
);
}
static void Main()
{
// Hello world プログラムの名前空間を生成
var helloWorldCodeDom = HelloWorldCodeDom();
// Hello world プログラムのソースコードを生成
var code = GenerateCode(helloWorldCodeDom);
Console.WriteLine(code); // 表示
// Hello world プログラムを、実行可能アセンブリとしてコンパイル
const string outputAssemblyName = "CodeDomHelloWorldDemo.exe";
CompileExecutableAssembly(codeNamespace: HelloWorldCodeDom(), outputAssemblyName: outputAssemblyName);
Process.Start(fileName: outputAssemblyName); // 生成された実行可能アセンブリを実行
}
}
実行してみると、"CodeDomHelloWorldDemo.exe" が立ち上がり、Hello world! と表示される。
Hello world!
今回は、CodeDOM を使った動的コード生成を行ってみた。 次回も CodeDOM を使ってみよう。
※ 「[C#][.NET][CodeDOM] メタプログラミング入門 - CodeDOM による Hello world!」の続き。
前回は、CodeDOM を使って Hello world! を表示するプログラムを動的に生成した。
もう少し CodeDOM を使ってみよう。 今回は、CodeDOM を使ってクラスを作ってみる。
次のような Item クラスを動的に作ることにする。
namespace CodeDomClassDemo
{
public class Item
{
private int price;
public int Price
{
get
{
return this.price;
}
set
{
this.price = value;
}
}
public override string ToString()
{
return (this.Price + "円");
}
}
}
次のような手順だ。
では、やってみよう。 前回同様、System.CodeDom 名前空間を使用する。
using System.CodeDom;
class Program
{
// Item クラスの Code DOM
static CodeNamespace ItemClassCodeDom()
{
// CodeDomHelloWorldDemo 名前空間
var nameSpace = new CodeNamespace(name: "CodeDomClassDemo");
//// System 名前空間のインポート
//nameSpace.Imports.Add(new CodeNamespaceImport(nameSpace: "System"));
// Item クラス
var itemClass = new CodeTypeDeclaration(name: "Item");
// CodeDomHelloWorldDemo 名前空間に Item クラスを追加
nameSpace.Types.Add(itemClass);
// price フィールド
var priceField = new CodeMemberField(type: typeof(int), name: "price") {
Attributes = MemberAttributes.Private // private
};
// Item クラスに price フィールドを追加
itemClass.Members.Add(priceField);
// Price プロパティ
var priceProperty = new CodeMemberProperty {
Name = "Price" , // 名前は、Price
Attributes = MemberAttributes.Public | MemberAttributes.Final, // public で virtual じゃない
Type = new CodeTypeReference(typeof(int)) // 型は int
};
// price フィールドへの参照
var priceFieldReference = new CodeFieldReferenceExpression(
targetObject: new CodeThisReferenceExpression(), // this.
fieldName : "price" // price
);
// Price プロパティの Get を追加
priceProperty.GetStatements.Add(
new CodeMethodReturnStatement(priceFieldReference) // price フィールドへの参照を return する文
);
// Price プロパティの Set を追加
priceProperty.SetStatements.Add(
new CodeAssignStatement( // 代入文
left : priceFieldReference , // 左辺は、price フィールドへの参照
right: new CodePropertySetValueReferenceExpression() // 右辺は、value
)
);
// Item クラスに Price プロパティを追加
itemClass.Members.Add(priceProperty);
// ToString メソッド
var toStringMethod = new CodeMemberMethod {
Name = "ToString" , // 名前は、ToString
Attributes = MemberAttributes.Public | MemberAttributes.Override, // public で override
ReturnType = new CodeTypeReference(typeof(string)) // 戻り値の型は、string
};
// Price プロパティへの参照
var pricePropertyReference = new CodePropertyReferenceExpression(
targetObject: new CodeThisReferenceExpression(), // this.
propertyName: "Price" // Price
);
// ToString メソッドの中身
toStringMethod.Statements.Add(
new CodeMethodReturnStatement( // return 文
new CodeBinaryOperatorExpression( // 二項演算子の式
left : pricePropertyReference , // Price プロパティへの参照
op : CodeBinaryOperatorType.Add , // +
right: new CodePrimitiveExpression("円") // "円"
)
)
);
// Item クラスに ToString メソッドを追加
itemClass.Members.Add(toStringMethod);
return nameSpace;
}
static void Main()
{
// Item クラスを含む名前空間を CodeDOM で生成
var itemClassCodeDom = ItemClassCodeDom();
}
}
この ItemClassCodeDom メソッドで、Item クラスを含む名前空間を生成したことになる。
前回同様、上で作った名前空間から CodeDOM を使って C# のソースコードを生成してみる。 手順は前回と全く同じで、System.CodeDom.Compiler 名前空間を使用する。
using System;
using System.CodeDom;
using System.CodeDom.Compiler;
using System.IO;
using System.Text;
class Program
{
// Item クラスの Code DOM
static CodeNamespace ItemClassCodeDom()
{
…… 同じなので省略 ……
}
// 名前空間からソースコードを生成
static string GenerateCode(CodeNamespace codeNamespace)
{
// コンパイル オプション
var compilerOptions = new CodeGeneratorOptions { IndentString = " ", BracingStyle = "C" };
var codeText = new StringBuilder();
using (var codeWriter = new StringWriter(codeText)) {
// 名前空間からソースコードを生成
CodeDomProvider.CreateProvider("C#").GenerateCodeFromNamespace(codeNamespace, codeWriter, compilerOptions);
}
return codeText.ToString(); // 生成されたソースコード
}
static void Main()
{
// Item クラスを含む名前空間を CodeDOM で生成
var itemClassCodeDom = ItemClassCodeDom();
// Item クラスのソースコードを生成
var code = GenerateCode(itemClassCodeDom);
Console.WriteLine(code);
}
}
実行してみると、次のように C# のソースコードが表示される。
namespace CodeDomClassDemo
{
public class Item
{
private int price;
public int Price
{
get
{
return this.price;
}
set
{
this.price = value;
}
}
public override string ToString()
{
return (this.Price + "円");
}
}
}
GenerateCode メソッド中の "C#" の部分を "VB" に置き換えて実行した場合は次の通り。
Namespace CodeDomClassDemo
Public Class Item
Private price As Integer
Public Property Price() As Integer
Get
Return Me.price
End Get
Set
Me.price = value
End Set
End Property
Public Overrides Function ToString() As String
Return (Me.Price + "円")
End Function
End Class
End Namespace
次に、CodeDOM で生成した名前空間をコンパイルしてアセンブリを生成してみよう。
前回は、アセンブリをファイルに出力したが、今回はオンメモリで生成してみる。
生成したアセンブリの中から Item クラスを取り出してインスタンスを生成し、Price プロパティや ToString メソッドを使ってみよう。
using System;
using System.CodeDom;
using System.CodeDom.Compiler;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
class Program
{
// Item クラスの Code DOM
static CodeNamespace ItemClassCodeDom()
{
…… 同じなので省略 ……
}
// 名前空間からソースコードを生成
static string GenerateCode(CodeNamespace codeNamespace)
{
…… 同じなので省略 ……
}
// 名前空間をアセンブリへコンパイル
static Assembly CompileAssembly(CodeNamespace codeNamespace)
{
var codeCompileUnit = new CodeCompileUnit(); // コンパイル単位
codeCompileUnit.Namespaces.Add(codeNamespace); // コンパイル単位に名前空間を追加
// アセンブリへコンパイル
var compilerResults = CodeDomProvider.CreateProvider("C#").CompileAssemblyFromDom(
options : new CompilerParameters { // コンパイル オプション
GenerateInMemory = true // アセンブリをメモリ内で生成
},
compilationUnits: codeCompileUnit // コンパイル単位
);
return compilerResults.CompiledAssembly; // コンパイルされたアセンブリを返す
}
static void Main()
{
// Item クラスを含む名前空間を CodeDOM で生成
var itemClassCodeDom = ItemClassCodeDom();
// Item クラスのソースコードを生成
var code = GenerateCode(itemClassCodeDom);
Console.WriteLine(code);
// Item クラスを、アセンブリとしてコンパイル
var itemAssembly = CompileAssembly(codeNamespace: itemClassCodeDom);
// Item クラス アセンブリのテスト
TestItemAssembly(itemAssembly);
}
// Item クラス アセンブリのテスト
static void TestItemAssembly(Assembly itemAssembly)
{
// Item クラスを、アセンブリから取得
var itemType = itemAssembly.GetTypes().First();
// Item クラスのインスタンスを動的に生成
dynamic item = Activator.CreateInstance(itemType);
// Item クラスの Price プロパティのテスト
item.Price = 2980;
Console.WriteLine(item.Price);
// Item クラスの ToString メソッドのテスト
Console.WriteLine(item);
}
}
実行してみると、次のように表示され、Price プロパティとToString メソッドが正常に使えるのが分かる。
2980 2980円
今回は、CodeDOM を使った動的にクラスを生成した。CodeDOM を使うことで、クラスを実行時に作ることができる。
この記事は、「C# Advent Calendar 2013」の 12 月 12 日分。
※ 「[C#][.NET][CodeDOM] メタプログラミング入門 - CodeDOM によるクラスの生成」の続き。
Roslyn は、C# や Visual Basic のコンパイラーの内部の API 等を公開したものだ。"Compiler as a Service" と表現されている。
Roslyn に関しては、以前、次にあげる記事で扱った。参考にしてほしい。
先ず、「Roslyn による Visual Studio のアドイン」で行った Roslyn を使った C# のソースコードの解析を、もっと簡単な例でやってみたい。
次の手順でコード解析のサンプルを準備する。
このプロジェクトの Main メソッド内に、単純な C# のソースコードを文字列として準備する。 「Program クラスの中に空の Main メソッドがあるだけ」のソースコードだ。
class Program
{
static void Main()
{
// 解析する C# のソースコード
var sourceCode = @"
using System;
class Program
{
static void Main()
{}
}
";
}
}
この単純な C# のソースコードの文字列を、Roslyn でパースしてシンタックス ツリーに変換し、簡単な解析をしてみよう。
それには、Roslyn.Compilers.CSharp 名前空間の SyntaxWalker クラスを用いる。
このクラスは、Visitor パターンになっていて、これを継承し、各種メソッドをオーバーライドすることで、様々な種類のノードやトークンを辿ることができるようになっている。
例えば、次のような SyntaxWalker の派生クラスを用意し、各ノードを Visit するメソッドをオーバーライドすると、ソースコードの構成要素であるノードを全部辿ることができる。
using Roslyn.Compilers.CSharp;
using System;
class Walker : SyntaxWalker // Visitor パターンでソースコードを解析
{
public override void Visit(SyntaxNode node) // 各ノードを Visit
{
if (node != null)
Console.WriteLine("[Node - Type: {0}, Kind: {1}]\n{2}\n", node.GetType().Name, node.Kind, node);
base.Visit(node);
}
}
この Walker クラスで、先程の単純な C# のソースコードを解析してみる。
using Roslyn.Compilers.CSharp;
class Program
{
static void Main()
{
// 解析する C# のソースコード
var sourceCode = @"
using System;
class Program
{
static void Main()
{}
}
";
var syntaxTree = SyntaxTree.ParseText(sourceCode); // ソースコードをパースしてシンタックス ツリーに
var rootNode = syntaxTree.GetRoot(); // ルートのノードを取得
new Walker().Visit(rootNode); // 解析
}
}
実行してみよう。
[Node - Type: CompilationUnitSyntax, Kind: CompilationUnit]
using System;
class Program
{
static void Main()
{}
}
[Node - Type: UsingDirectiveSyntax, Kind: UsingDirective]
using System;
[Node - Type: IdentifierNameSyntax, Kind: IdentifierName]
System
[Node - Type: ClassDeclarationSyntax, Kind: ClassDeclaration]
class Program
{
static void Main()
{}
}
[Node - Type: MethodDeclarationSyntax, Kind: MethodDeclaration]
static void Main()
{}
[Node - Type: PredefinedTypeSyntax, Kind: PredefinedType]
void
[Node - Type: ParameterListSyntax, Kind: ParameterList]
()
[Node - Type: BlockSyntax, Kind: Block]
{}
各ノードの情報が表示される。 ノードは入れ子になっているのが分かる。
次に、Walker クラスを少し変更して、ノードでなく、より細かいソースコードの構成要素であるトークンを表示してみる。 今度は、各トークンを Visit する VisitToken メソッドをオーバーライドして、全トークンを辿ってみる。
class Walker : SyntaxWalker // Visitor パターンでソースコードを解析
{
public Walker() : base(depth: SyntaxWalkerDepth.Token) // トークンの深さまで Visit
{}
public override void VisitToken(SyntaxToken token) // 各トークンを Visit
{
if (token != null)
Console.WriteLine("[Token - Type: {0}, Kind: {1}]\n{2}\n", token.GetType().Name, token.Kind, token);
base.VisitToken(token);
}
}
実行してみよう。
[Token - Type: SyntaxToken, Kind: UsingKeyword]
using
[Token - Type: SyntaxToken, Kind: IdentifierToken]
System
[Token - Type: SyntaxToken, Kind: SemicolonToken]
;
[Token - Type: SyntaxToken, Kind: ClassKeyword]
class
[Token - Type: SyntaxToken, Kind: IdentifierToken]
Program
[Token - Type: SyntaxToken, Kind: OpenBraceToken]
{
[Token - Type: SyntaxToken, Kind: StaticKeyword]
static
[Token - Type: SyntaxToken, Kind: VoidKeyword]
void
[Token - Type: SyntaxToken, Kind: IdentifierToken]
Main
[Token - Type: SyntaxToken, Kind: OpenParenToken]
(
[Token - Type: SyntaxToken, Kind: CloseParenToken]
)
[Token - Type: SyntaxToken, Kind: OpenBraceToken]
{
[Token - Type: SyntaxToken, Kind: CloseBraceToken]
}
[Token - Type: SyntaxToken, Kind: CloseBraceToken]
}
[Token - Type: SyntaxToken, Kind: EndOfFileToken]
今度は、より細かく "using"、"System"、";"、"class" 等の各トークンの情報が表示される。 ノードと異なり入れ子にはなっていない。
Roslyn では、コードを単に解析するだけでなく、改変することも可能だ。
試しに先程の Program クラスの中に空の Main メソッドがあるだけの C# のソースコードの Main の中を、"Hello world!" を表示するコードに変更してみよう。 こんな感じだ。
実装してみると、次のようになる。
using Roslyn.Compilers;
using Roslyn.Compilers.CSharp;
using System;
using System.Linq;
class Program
{
// Roslyn.Compilers.CSharp.Syntax クラスを用いた Console.WriteLine("Hello world!"); が入ったブロックの作成
static BlockSyntax CreateHelloWorldBlock()
{
var invocationExpression = Syntax.InvocationExpression( // Console.WriteLine("Hello world!");
expression: Syntax.MemberAccessExpression( // Console.WriteLine というメンバー アクセス
kind : SyntaxKind.MemberAccessExpression,
expression: Syntax.IdentifierName("Console" ),
name : Syntax.IdentifierName("WriteLine")
),
argumentList: Syntax.ArgumentList( // 引数リスト
arguments: Syntax.SeparatedList<ArgumentSyntax>(
node: Syntax.Argument( // "Hello world!"
expression: Syntax.LiteralExpression(
kind : SyntaxKind.StringLiteralExpression,
token: Syntax.Literal("Hello world!")
)
)
)
)
);
var statement = Syntax.ExpressionStatement(expression: invocationExpression);
return Syntax.Block(statement);
}
static void Main()
{
// 改変する C# のソースコード
var sourceCode = @"
using System;
class Program
{
static void Main()
{}
}
";
var syntaxTree = SyntaxTree.ParseText(sourceCode); // ソースコードをパースしてシンタックス ツリーに
var rootNode = syntaxTree.GetRoot(); // ルートのノードを取得
// Main メソッドのブロックを取得
var block = rootNode.DescendantNodes().First(node => node.Kind == SyntaxKind.Block);
var newNode = rootNode.ReplaceNode( // ノードの置き換え
oldNode: block , // 元の空のブロック
newNode: CreateHelloWorldBlock() // Console.WriteLine("Hello world!"); が入ったブロック
);
Console.WriteLine(newNode.NormalizeWhitespace()); // 整形して表示
}
}
実行してみよう。
using System;
class Program
{
static void Main()
{
Console.WriteLine(@"Hello world!");
}
}
Main メソッドの空だったブロックが、Console.WriteLine(@"Hello world!"); 入りのブロックに変更されたのが分かる。
上では ReplaceNode 拡張メソッドを使ったが、Roslyn.Compilers.CSharp.SyntaxRewriter を使ってもコードを変更することができる。
こちらの方は、ノード内に一斉に同じ変更を行うのに向いている。
SyntaxRewriter クラスは、上の方でソースコードの解析に用いた SyntaxWalker クラスと同様に、継承することで Visitor パターンによって、様々な種類のノードやトークンを辿ることができるクラスだ。
SyntaxRewriter クラスでは、適宜メソッドをオーバーライドすることで、ノードやトークンを書き換えることができる。
今回は、SyntaxRewriter を使い、ソースコード中の邪魔な #region と #endregion を消してみよう。
先ず、C# のソースコードの文字列として、次のように #region と #endregion が入ったものを用意する。
class Program
{
static void Main()
{
// 改変する #region と #endregion 入の C# のソースコード
var sourceCode = @"
public class MyViewModel : INotifyPropertyChanged
{
#region INotifyPropertyChanged メンバー
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string name)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
#endregion // INotifyPropertyChanged メンバー
}
";
}
}
次に #region と #endregion を除去するためのクラス RemoveRegionRewriter を用意する。 SyntaxRewriter クラスからの派生クラスだ。
using Roslyn.Compilers.CSharp;
// #region と #endregion を除去するクラス
class RemoveRegionRewriter : SyntaxRewriter
{
public RemoveRegionRewriter() : base(visitIntoStructuredTrivia: true) // true にすることで #region や #endregion まで辿れる
{}
// #region を Visit
public override SyntaxNode VisitRegionDirectiveTrivia(RegionDirectiveTriviaSyntax node)
{
return Syntax.SkippedTokensTrivia(); // スキップする
}
// #endregion を Visit
public override SyntaxNode VisitEndRegionDirectiveTrivia(EndRegionDirectiveTriviaSyntax node)
{
return Syntax.SkippedTokensTrivia(); // スキップする
}
}
では、このクラスを使って先程の #region と #endregion 入の C# のソースコードから #region と #endregion を除いてみよう。
using Roslyn.Compilers.CSharp;
using System;
class Program
{
static void Main()
{
// 改変する C# のソースコード
var sourceCode = @"
public class MyViewModel : INotifyPropertyChanged
{
#region INotifyPropertyChanged メンバー
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string name)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
#endregion // INotifyPropertyChanged メンバー
}
";
var syntaxTree = SyntaxTree.ParseText(sourceCode); // ソースコードをパースしてシンタックス ツリーに
var rootNode = syntaxTree.GetRoot(); // ルートのノードを取得
var newNode = new RemoveRegionRewriter().Visit(node: rootNode); // #region と #endregion の除去
Console.WriteLine(newNode.NormalizeWhitespace()); // 整形して表示
}
}
実行してみよう。
public class MyViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string name)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
}
#region と #endregion の行が除去されているのが分かるだろう。
今回は、Roslyn を使い、簡単な C# ソースコードの解析と変更を行った。
「C# Advent Calendar 2013」の明日は yone64 さん。
Microsoft Virtual Academy は、マイクロソフトの無償のオンライン トレーニングです。
入門者向けの内容が多いのが特徴です。
スライドと動画を観ながら講義を受けられます。
(英語のコンテンツもありますが、字幕が出るものが多くあります)
マイクロソフト アカウントでサインアップして参加することで、学習の進み具合も記録され、継続的な学習ができます。
C# と XAML によるアプリケーション開発について学びたい人向けのコンテンツの一覧は次の場所にあります。
例えば、これから Windows ストア アプリの C# での開発方法を学習したい人は、次の順に進めていくこ良いでしょう。