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 言語合戦』 のために集めたネタ (とイベント中で使われたネタ)。
C++ にはなかった新しいキーワードとして、C# では interface というものが出てくる。 interface は、Java ではおなじみのキーワードだ。
例.interface では公開されているメソッドとプロパティの外見 (名前、パラメータ、戻り値) だけが宣言されていて、実装部分が定義されていない。実装部分は、その interface を実装するクラスによって定義される。interface ICloneable { object Clone(); }
例.interface は 抽象クラス (abstract class) と機能的には似ている。 抽象クラスも、中身のないメソッド (abstract method) の宣言を持つことができ、そこから派生したクラスで、そのメソッドの実装を定義する。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); } }
例.機能上の大きな違いとしては、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. */ } }
・抽象クラスを使った場合の例: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 を使った場合の例:さて、State/Strategy パターンを使う場合、はたしてどちらが標準的なのだろうか。 「State/Strategy パターンを構成している部分だけ」を見ると、或る振る舞いに関する制約が付けられれば十分なので、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 パターン」を使おう → じゃ 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(); } }
続きは「[C#] Tips: interface と partial class で横断的関心事を分離」。
今まで新人向けのオブジェクト指向の研修で、「継承」というのは、
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# も作ってみた。
福井で 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 さん。
TypeScript に関する参考資料を集めてみた。
Microsoft Virtual Academy は、マイクロソフトの無償のオンライン トレーニングです。
入門者向けの内容が多いのが特徴です。
スライドと動画を観ながら講義を受けられます。
(英語のコンテンツもありますが、字幕が出るものが多くあります)
マイクロソフト アカウントでサインアップして参加することで、学習の進み具合も記録され、継続的な学習ができます。
C# と XAML によるアプリケーション開発について学びたい人向けのコンテンツの一覧は次の場所にあります。
例えば、これから Windows ストア アプリの C# での開発方法を学習したい人は、次の順に進めていくこ良いでしょう。
『Hokuriku.NET Vol.13 in 富山』に参加してきた。
『Hokuriku.NET Vol.13 in 富山』 | |
---|---|
開催日 | 2014/01/25 |
会場 | 富山県民会館 |
詳細 | http://atnd.org/events/46157 |
C# のような静的型付き言語に慣れているプログラマーにとって、JavaScript は多少違和感がある。
「クラスがない」などは、ちょっと何言ってるか分からないかも知れない。
そこで TypeScript の登場だ。
次のような特長がある。
また、開発に便利な機能が多くある。
TypeScript にあって JavaScript にないものは例えば次のようなものだ (抜粋)。
これらの機能を Visual Studio 上で使うことで、効率よく開発を行うことができる。
尚、Visual Studio では、TypeScript 上にブレークポイントを置いてデバッグ実行することが可能だ。
TypeScript は次の場所でダウンロードでき、Visual Studio 2012 や 2013 にインストールすることができる。
TypeScript コンパイラーは、TypeScript へのコードを JavaScript へとコンパイルする。
出来上がった JavaScript を HTML などに組み込んで使う訳だ。
実際にどのようにコンパイルされるか見てみよう。
TypeScript をインストールした Visual Studio 2012 または 2013 で、メニュー「ファイル」 - 「新規作成」 - 「プロジェクト」から「他の言語」の中の「TypeScript」 - 「TypeScript を使用した HTML アプリケーション」を選択し、プロジェクトを新規に作成する。
このプロジェクトに TypeScript ファイルを新規に追加してコンパイルしてみよう。
注意点が一点。TypeScript ファイルの文字コードが「シフトJIS」になっていると、作られた JavaScript で日本語の文字が文字化けを起こすことがある。
例えば、次のような感じだ。
TypeScript ファイルの文字コードを Unicode に変更しておこう。
TypeScript ファイルを開いた状態で、メニュー「ファイル」 - 「保存オプションの詳細設定」で変更する。
を
に変更する。
※ この問題は、TypeScript 0.9.5 で起こることを確認していたが、TypeScript 1.0RC で修正されていることを確認。(2014-02-28 追記)
試しに次のような TypeScript ファイル "vector.ts" を作成し、コンパイルしてみよう。
// モジュール (名前空間やパッケージのようなもの) module Graphics { // 二次元ベクトルのクラス export class Vector { // コンストラクター constructor(public x: number = 0.0, public y: number = 0.0) {} // プロパティ (C# のプロパティにあたる/型付き) get length(): number { return Math.sqrt(this.x * this.x + this.y * this.y); } // メソッド (引数、戻り値ともに型付きにできる) add(vector: Vector): Vector { return new Vector(this.x + vector.x, this.y + vector.y); } multiply(vector: Vector): Vector { return new Vector(this.x * vector.x, this.y * vector.y); } } }
Pascal のように型名が前でなく後ろにくるなどの違いはあるが、C# などに慣れている人にはそれ程違和感のないコードだ。
これは、C# の次のようなコードにあたる。
using System; // 名前空間 namespace Graphics { // 二次元ベクトルのクラス public class Vector { public double X { get; set; } public double Y { get; set; } // コンストラクター public Vector(double x = 0.0, double y = 0.0) { X = x; Y = y; } // プロパティ public double Length { get { return Math.Sqrt(X * X + Y * Y); } } // メソッド public Vector Add(Vector vector) { return new Vector(X + vector.X, Y + vector.Y); } public Vector Multiply(Vector vector) { return new Vector(X * vector.X, this.Y * vector.Y); } } }
この TypeScript を、コンパイルすると "vector.js" という JavaScript ファイルが作られる。
// モジュール (名前空間やパッケージのようなもの) var Graphics; (function (Graphics) { // 二次元ベクトルのクラス var Vector = (function () { // コンストラクター function Vector(x, y) { if (typeof x === "undefined") { x = 0.0; } if (typeof y === "undefined") { y = 0.0; } this.x = x; this.y = y; } Object.defineProperty(Vector.prototype, "length", { // プロパティ (C# のプロパティにあたる/型付き) get: function () { return Math.sqrt(this.x * this.x + this.y * this.y); }, enumerable: true, configurable: true }); // メソッド (引数、戻り値ともに型付きにできる) Vector.prototype.add = function (vector) { return new Vector(this.x + vector.x, this.y + vector.y); }; Vector.prototype.multiply = function (vector) { return new Vector(this.x * vector.x, this.y * vector.y); }; return Vector; })(); Graphics.Vector = Vector; })(Graphics || (Graphics = {})); //# sourceMappingURL=vector.js.map
ちゃんと JavaScript に変換されているのが分かる。
これを HTML に組み込んで使えば OK だ。
今回は TypeScript による JavaScript で、HTML5 の Canvas 上の ImageData にピクセル単位の描画を行う例をあげてみる。
描画自体は、特に TypeScript に依存した内容ではないが、TypeScript に慣れるためのサンプルという意味合いだ。
前回を参考に、Visual Studio で「TypeScript を使用した HTML アプリケーション」を作成してやってみよう。
先ず setpixelsample.html という HTML ファイルを用意する。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8" /> <title>SetPixel Sample</title> <script src="setpixelsample.js"></script> </head> <body onload="SetPixelSample.Program.run()"> <canvas width="500" height="500"></canvas> </body> </html>
この HTML ファイルでは、setpixelsample.js という JavaScript ファイルを組み込んでいる。そこで setpixelsample.ts という TypeScript ファイルを追加する。
また、onload="SetPixelSample.Program.run()" という記述があるが、このメソッドも setpixelsample.ts 内に用意する。
そして、このメソッド内から HTML 内の canvas に描画を行うこととする。
canvas オブジェクトを取得し、その中に (canvas オブジェクトと同じ幅と高さの) ImageData オブジェクトを作成するには、createImageData を使う。
var canvas = <HTMLCanvasElement>document.querySelector("canvas"); var context = canvas.getContext("2d"); var imageData = context.createImageData(canvas.width, canvas.height);
また、ImageData オブジェクトに任意の色のピクセルを置くには、次のように ImageData オブジェクトの data に赤、緑、青、アルファ値の順で1バイトずつ書き込めば良い。
// ImageData の指定した座標の 1 ピクセルを指定した色にする private static setPixel(imageData: ImageData, x: number, y: number, red: number, green: number, blue: number, alpha: number = 0xff) { // 指定した座標のピクセルが ImageData の data のどの位置にあるかを計算 var index = (x + y * imageData.width) * 4; // その位置から、赤、緑、青、アルファ値の順で1バイトずつ書き込むことで、ピクセルがその色になる imageData.data[index + 0] = red ; imageData.data[index + 1] = green; imageData.data[index + 2] = blue ; imageData.data[index + 3] = alpha; }
任意の色のピクセルが置かれた ImageData オブジェクトを描画するには putImageData を使う。
// ImageData を描画 context.putImageData(imageData, 0, 0);
ImageData オブジェクトにランダムに 100,000 個のピクセルを置いて描画するサンプルを作ってみると次のようになる (setpixelsample.ts)。
module SetPixelSample { export class Program { static run() { var canvas = <HTMLCanvasElement>document.querySelector("canvas"); var context = canvas.getContext("2d"); var imageData = context.createImageData(canvas.width, canvas.height); // ImageData にランダムにピクセルを置く Program.setRandomPixels(canvas, imageData); // ImageData を描画 context.putImageData(imageData, 0, 0); } // ImageData のランダムな座標のピクセルをランダムな色にする private static setRandomPixels(canvas: HTMLCanvasElement, imageData: ImageData) { for (var index = 0; index < 100000; index++) { var x = Program.randomInteger(canvas.height); var y = Program.randomInteger(canvas.width ); var red = Program.randomInteger(0x100 ); var green = Program.randomInteger(0x100 ); var blue = Program.randomInteger(0x100 ); Program.setPixel(imageData, x, y, red, green, blue); } } // 疑似乱数 (0 から value 未満の整数) private static randomInteger(value: number): number { return Math.floor(value * Math.random()); } // ImageData の指定した座標の 1 ピクセルを指定した色にする private static setPixel(imageData: ImageData, x: number, y: number, red: number, green: number, blue: number, alpha: number = 0xff) { // 指定した座標のピクセルが ImageData の data のどの位置にあるかを計算 var index = (x + y * imageData.width) * 4; // その位置から、赤、緑、青、アルファ値の順で1バイトずつ書き込むことで、ピクセルがその色になる imageData.data[index + 0] = red ; imageData.data[index + 1] = green; imageData.data[index + 2] = blue ; imageData.data[index + 3] = alpha; } } }
コンパイルして setpixelsample.js を生成し、setpixelsample.html を Internet Explorer 9 以降等の HTML5 対応の Web ブラウザーで表示してみると次のようにランダムな 100,000 個のピクセルが描画される。
実際のサンプルを次の場所に用意した。
※ 『[TypeScript] ImageData によるピクセル単位の描画』の続き。
前回は、HTML5 の Canvas に ImageData を作成してランダムなピクセルを描画した。
今回も同様にピクセル単位の描画を行ってみよう。少しだけ応用してマンデルブロ集合を描画してみる。
マンデルブロ集合は、フラクタルとして有名な図形だ。
詳しくは、次の場所が参考になる:
HTML5 の Canvas にピクセル単位の描画を行う方法については、前回を参考にしてほしい。
今回は、TypeScript の特長である「クラス設計のやり易さ」を活かすこととし、前回よりもきちんとクラス分けを行ってみたい。
先ず、前回同様の mandelbrot.html という HTML ファイルを用意する。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8" /> <title>Mandelbrot Sample</title> <script src="graph.js"></script> <script src="mandelbrot.js"></script> </head> <body onload="MandelbrotSample.MainProgram.run()"> <canvas width="800" height="800"></canvas> </body> </html> <!-- Usage: mandelbrot.html : position = (-2.8, 2.0), scale = 4.0 mandelbrot.html?posy=1&scale=2 : position = (-2.8, 1.0), scale = 2.0 mandelbrot.html?posx=0&posy=0&scale=1 : position = ( 0.0, 0.0), scale = 1.0 - This HTML can work with mandelbrot.js and graph.js online or offline. -->
この HTML ファイルでは、graph.js と mandelbrot.js という2つの JavaScript ファイルを組み込んでいる。 それぞれ graph.ts と mandelbrot.ts いう TypeScript ファイルから生成されることになる。
onload="MandelbrotSample.MainProgram.run()" という記述があるが、このメソッドは mandelbrot.ts 内に用意する。
graph.ts は、Canvas にピクセル単位の描画を行うモジュールだ。
今回は、Canvas もクラス化してみよう。ピクセル毎に描画を行う部分もクラス化する。
また、座標、大きさ、矩形、色などの基本的なクラスもここに置くことにする。
module Graph { // 色 export class Color { constructor(public red: number = 0x00, public green: number = 0x00, public blue: number = 0x00, public alpha: number = 0xff) { } } // 二次元ベクトル export class Vector { constructor(public x: number = 0.0, public y: number = 0.0) {} add(vector: Vector): Vector { return new Vector(this.x + vector.x, this.y + vector.y); } multiply(vector: Vector): Vector { return new Vector(this.x * vector.x, this.y * vector.y); } multiplyBy(value: number): Vector { return new Vector(this.x * value, this.y * value); } } // 二次元の大きさ export class Size { constructor(public width: number, public height: number) {} } // 矩形 export class Rectangle { constructor(public position: Vector, public size: Size) {} } // ImageData への描画用 class ImageDataHelper { // ImageData の指定した座標の 1 ピクセルを指定した色にする static setPixel(imageData: ImageData, position: Vector, color: Color) { // ImageData のサイズ var imageDataSize = new Size(imageData.width, imageData.height); // 指定したピクセルの座標が有効でなかったら if (!ImageDataHelper.isValid(imageDataSize, position)) return; // 指定した座標のピクセルが ImageData の data のどの位置にあるかを計算 var index = ImageDataHelper.toIndex(imageDataSize, position); // その位置から、赤、緑、青、アルファ値の順で1バイトずつ書き込むことで、ピクセルがその色になる imageData.data[index + 0] = color.red ; imageData.data[index + 1] = color.green; imageData.data[index + 2] = color.blue ; imageData.data[index + 3] = color.alpha; } // 指定したピクセルの座標が有効かどうか private static isValid(imageDataSize: Size, position: Vector): boolean { return position.x >= 0.0 && position.x <= imageDataSize.width && position.y >= 0.0 && position.y <= imageDataSize.height; } // 指定した座標のピクセルが ImageData の data のどの位置にあるかを計算 private static toIndex(imageDataSize: Size, position: Vector): number { return (position.x + position.y * imageDataSize.width) * 4; } } // Canvas に ImageData を置きピクセル毎に描画を行う export class Sheet { private context_ : CanvasRenderingContext2D; private imageData_: ImageData; get context() { return this.context_; } constructor(context: CanvasRenderingContext2D, size: Size) { this.context_ = context; this.imageData_ = context.createImageData(size.width, size.height); } // ImageData の指定した座標の 1 ピクセルを指定した色にする setPixel(position: Vector, color: Color) { ImageDataHelper.setPixel(this.imageData_, position, color); } // 指定した位置に ImageData を描画 draw(position: Vector = new Vector()) { this.context.putImageData(this.imageData_, position.x, position.y); } } // キャンバス export class Canvas { private canvas_ : HTMLCanvasElement; private context_: CanvasRenderingContext2D; constructor() { this.canvas_ = <HTMLCanvasElement>document.querySelector("canvas"); this.context_ = this.canvas_.getContext("2d"); } get size(): Graph.Size { return Canvas.getCanvasSize(this.canvas_); } get position(): Graph.Rectangle { return new Graph.Rectangle(new Graph.Vector(), this.size); } get context(): CanvasRenderingContext2D { return this.context_; } private static getCanvasSize(canvas: HTMLCanvasElement): Graph.Size { return new Graph.Size(canvas.width, canvas.height); } } }
mandelbrot.ts は、Canvas にマンデルブロ集合を描画する行うモジュールだ。
メイン プログラム、マンデルブロ集合、マンデルブロ集合を描画するときのパラメーター、そしてパラメーターをクエリ文字列から取得するためのユーティリティの各クラスからなる。
/// <reference path="graph.ts"/> module MandelbrotSample { class Utility { // クエリ文字列から数を取得 static getNumberFromQueryString(key: string): number { var queryString = Utility.getQueryString(key); if (queryString != "") { try { return parseInt(queryString); } catch (ex) {} } return null; } // クエリ文字列の取得 static getQueryString(key: string, default_: string = null): string { if (default_ == null) default_ = ""; key = key.replace(/[\[]/, "\\\[").replace(/[\]]/, "\\\]"); var regex = new RegExp("[\\?&]" + key + "=([^&#]*)"); var queryString = regex.exec(window.location.href); return queryString == null ? default_ : queryString[1]; } } // パラメーター class Parameter { position : Graph.Vector = new Graph.Vector(-2.8, 2.0); maximum : number = 32; private scale_: number = 4.0; get ratio(): number { return this.scale_ / this.size.width; } constructor(public size: Graph.Size) { this.setPositionX(); this.setPositionY(); this.setScale (); this.setMaximum (); } private setPositionX() { var positionX = Utility.getNumberFromQueryString("posx"); if (positionX != null) this.position.x = positionX; } private setPositionY() { var positionY = Utility.getNumberFromQueryString("posy"); if (positionY != null) this.position.y = positionY; } private setScale() { var scale = Utility.getNumberFromQueryString("scale"); if (scale != null) this.scale_ = scale; } private setMaximum() { var maximum = Utility.getNumberFromQueryString("max"); if (maximum != null) this.maximum = maximum; } } class Mandelbrot { private position_ : Graph.Vector; private sheet_ : Graph.Sheet; private parameter_: Parameter; constructor(context: CanvasRenderingContext2D, position: Graph.Rectangle, size: Graph.Size) { this.position_ = position.position; this.sheet_ = new Graph.Sheet(context, size); this.parameter_ = new Parameter(size); } draw(palette: Graph.Color[]) { var point = new Graph.Vector(); for (point.y = 0; point.y < this.parameter_.size.height; point.y++) { for (point.x = 0; point.x < this.parameter_.size.width; point.x++) { var a = this.parameter_.position.add(new Graph.Vector(point.x, -point.y).multiplyBy(this.parameter_.ratio)); this.setPixel(point, this.getCount(a), palette); } } this.sheet_.draw(this.position_); } private getCount(a: Graph.Vector): number { var squareBorder = 25.0; var square = new Graph.Vector(); var point = new Graph.Vector(); var count = 0; do { point = new Graph.Vector(square.x - square.y + a.x, 2.0 * point.x * point.y + a.y); square = point.multiply(point); count++; } while (square.x + square.y < squareBorder && count <= this.parameter_.maximum); return count < this.parameter_.maximum ? count : 0; } private setPixel(point: Graph.Vector, count: number, palette: Graph.Color[]) { this.sheet_.setPixel(point, palette[Mandelbrot.toColorIndex(count, palette.length)]); } private static toColorIndex(colorNumber: number, paletteSize: number): number { var colorIndexNumber = paletteSize * 2 - 2; var colorIndex = colorNumber % colorIndexNumber; if (colorIndex >= paletteSize) colorIndex = colorIndexNumber - colorIndex; return colorIndex; } } export class MainProgram { static run() { var canvas = new Graph.Canvas(); var mandelbrot = new Mandelbrot(canvas.context, canvas.position, canvas.size); mandelbrot.draw(MainProgram.getPalette()); } // パレット (予め色を格納しておき、パレット番号で色を参照) private static getPalette(): Graph.Color[] { return [ new Graph.Color(0x02, 0x08, 0x80), new Graph.Color(0x10, 0x10, 0x70), new Graph.Color(0x20, 0x18, 0x60), new Graph.Color(0x30, 0x20, 0x50), new Graph.Color(0x40, 0x28, 0x40), new Graph.Color(0x50, 0x30, 0x30), new Graph.Color(0x60, 0x38, 0x20), new Graph.Color(0x70, 0x40, 0x10), new Graph.Color(0x80, 0x48, 0x0e), new Graph.Color(0x90, 0x50, 0x0c), new Graph.Color(0xa0, 0x58, 0x0a), new Graph.Color(0xb0, 0x60, 0x08), new Graph.Color(0xc0, 0x68, 0x06), new Graph.Color(0xd0, 0x70, 0x04), new Graph.Color(0xe8, 0x78, 0x02), new Graph.Color(0xff, 0x80, 0x01) ]; } } }
以上のクラス構成を、クラス図で見てみよう。次のようになる。
TypeScript を使っている為に、このようなクラス設計は比較的容易だ。
では、実行してみよう。
コンパイルすると、graph.js と mandelbrot.js が生成される。
mandelbrot.html を Internet Explorer 9 以降等の HTML5 対応の Web ブラウザーで表示してみると、次のようにマンデルブロ集合が描画される。
実際のサンプルを次の場所に用意した。
マンデルブロ集合のどこを描画するかを指定するパラメーターがクエリ文字列で渡せるようになっているので、次のようにして描画位置を変えることができる。
比較の為に、同様のものを C# (Windows ストア アプリ) で作成してみよう。
Visual Studio でメニュー「新規作成」 - 「プロジェクト」から「Visual C#」 - 「Windows ストア」 - 「新しいアプリケーション (XAML)」で Windows ストア アプリを新規作成する。
App.xaml と App.xaml.cs には特に変更を加えない。
謂わば mandelbrot.html にあたる部分。 TypeScript の場合同様、こちらも Canvas にイメージのオブジェクトを置き、描画する。
<Page x:Class="MandelbrotSample.WindowsStore.MainPage" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="using:MandelbrotSample.WindowsStore" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d" Loaded="Page_Loaded"> <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"> <Canvas x:Name="canvas"> <Image x:Name="image" /> </Canvas> <ProgressRing x:Name="progress" Width="50" Height="50" Foreground="Blue" /> </Grid> </Page>
using MandelbrotSample.WindowsStore.Graph; using System.Threading.Tasks; using Windows.UI.Xaml; using Windows.UI.Xaml.Controls; namespace MandelbrotSample.WindowsStore { public sealed partial class MainPage : Page { public MainPage() { InitializeComponent(); } async void Page_Loaded(object sender, RoutedEventArgs e) { await Run(); } async Task Run() { var canvasPosition = new IntRectangle { Position = new IntVector(), Size = new IntSize { Width = (int)canvas.ActualWidth , Height = (int)canvas.ActualHeight } }; progress.IsActive = true ; await MainProgram.RunAsync(canvasPosition, image); progress.IsActive = false; } } }
graph.ts にあたる部分は次のような感じ。
こちらでは、Palette はクラスにした。
また、TypeScript では number という型だったものは、int と double に分けた。
using System.Diagnostics; using System.IO; using System.Runtime.InteropServices.WindowsRuntime; using Windows.UI; using Windows.UI.Xaml.Media.Imaging; namespace MandelbrotSample.WindowsStore.Graph { public class Palette { readonly Color[] colors; public int Size { get { return colors.Length; } } public Palette(int size) { colors = new Color[size]; } public Color this[int index] { get { return index >= 0 && index < Size ? colors[index] : new Color(); } set { if (index >= 0 && index < Size) colors[index] = value; } } } public class Vector { public double X { get; set; } public double Y { get; set; } public static Vector operator +(Vector vector1, Vector vector2) { return new Vector { X = vector1.X + vector2.X, Y = vector1.Y + vector2.Y }; } public static Vector operator -(Vector vector1, Vector vector2) { return new Vector { X = vector1.X - vector2.X, Y = vector1.Y - vector2.Y }; } public static Vector operator *(Vector vector1, Vector vector2) { return new Vector { X = vector1.X * vector2.X, Y = vector1.Y * vector2.Y }; } public static Vector operator *(Vector vector, double value) { return new Vector { X = vector.X * value, Y = vector.Y * value }; } public static Vector operator /(Vector vector, double value) { return new Vector { X = vector.X / value, Y = vector.Y / value }; } } public class IntVector { public int X { get; set; } public int Y { get; set; } public static IntVector operator -(IntVector vector, IntSize size) { return new IntVector { X = vector.X - size.Width, Y = vector.Y - size.Height }; } } public class IntSize { public int Width { get; set; } public int Height { get; set; } public static IntSize operator +(IntSize size1, IntSize size2) { return new IntSize { Width = size1.Width + size2.Width, Height = size1.Height + size2.Height }; } public static IntSize operator *(IntSize size, IntVector vector) { return new IntSize { Width = size.Width * vector.X, Height = size.Height * vector.Y }; } } public class IntRectangle { public IntVector Position { get; set; } public IntSize Size { get; set; } } public static class WriteableBitmapExtension { public static void Clear(this WriteableBitmap bitmap, Color color) { var arraySize = bitmap.PixelBuffer.Capacity; var array = new byte[arraySize]; for (var index = 0; index < arraySize; index += 4) { array[index ] = color.B; array[index + 1] = color.G; array[index + 2] = color.R; array[index + 3] = color.A; } using (var pixelStream = bitmap.PixelBuffer.AsStream()) { pixelStream.Seek(0, SeekOrigin.Begin); pixelStream.Write(array, 0, array.Length); } } public static void SetPixel(this WriteableBitmap bitmap, int x, int y, Color color) { var bitmapWidth = bitmap.PixelWidth; var bitmapHeight = bitmap.PixelHeight; if (!IsValid(bitmapWidth, bitmapHeight, x, y)) return; var index = ToIndex(bitmapWidth, x, y); Debug.Assert(index >= 0 && index < bitmap.PixelBuffer.Capacity); using (var pixelStream = bitmap.PixelBuffer.AsStream()) { var array = new byte[] { color.B, color.G, color.R, color.A }; pixelStream.Seek(index, SeekOrigin.Begin); pixelStream.Write(array, 0, array.Length); } } static int ToIndex(int bitmapWidth, int x, int y) { return (x + y * bitmapWidth) * 4; } static bool IsValid(int width, int height, int x, int y) { return x >= 0 && x < width && y >= 0 && y < height; } } public class Sheet { public WriteableBitmap Bitmap { get; set; } public Sheet(IntSize size, Color backgroundColor = new Color()) { Bitmap = new WriteableBitmap(size.Width, size.Height); Bitmap.Clear(backgroundColor); } public void SetPixel(IntVector position, Color color) { Bitmap.SetPixel(position.X, position.Y, color); } } }
mandelbrot.ts にあたる部分は次のような感じ。
TypeScript の run メソッドは、非同期の RunAsync とした。
using MandelbrotSample.WindowsStore.Graph; using System; using System.Threading.Tasks; using Windows.UI; using Windows.UI.Xaml; using Windows.UI.Xaml.Controls; using Windows.UI.Xaml.Media.Imaging; namespace MandelbrotSample.WindowsStore { public class Mandelbrot { public class Parameter { public Vector Position { get; private set; } public int Maximum { get; private set; } public IntSize Size { get; private set; } public double Ratio { get; private set; } public Parameter(IntSize size) : this(position: new Vector { X = -2.8, Y = 1.5 }, scale: 5.0, maximum: 32, size: size) {} public Parameter(Vector position, double scale, int maximum, IntSize size) { Position = position; Maximum = maximum; Size = size; Ratio = scale / size.Width; } } const double squareBorder = 25.0; readonly Sheet sheet; readonly Parameter parameter; public WriteableBitmap Bitmap { get { return sheet.Bitmap; } } public Mandelbrot(IntRectangle position, Color backgroundColor = new Color()) { sheet = new Sheet(position.Size, backgroundColor); this.parameter = new Parameter(size: position.Size); } public Mandelbrot(IntRectangle position, Parameter parameter, Color backgroundColor = new Color()) { sheet = new Sheet(position.Size, backgroundColor); this.parameter = parameter; } public void Draw(Palette palette) { var point = new Graph.IntVector(); for (point.Y = 0; point.Y < parameter.Size.Height; point.Y++) { for (point.X = 0; point.X < this.parameter.Size.Width; point.X++) { var a = parameter.Position + new Vector { X = point.X, Y = -point.Y } * parameter.Ratio; SetPixel(point, GetCount(a), palette); } } } int GetCount(Vector a) { var square = new Vector(); var point = new Vector(); var count = 0; do { point = new Vector { X = square.X - square.Y + a.X, Y = 2.0 * point.X * point.Y + a.Y }; square = point * point; count++; } while (square.X + square.Y < squareBorder && count <= parameter.Maximum); return count < parameter.Maximum ? count : 0; } void SetPixel(IntVector point, int count, Palette palette) { sheet.SetPixel(point, palette[ToColorIndex(count, palette.Size)]); } static int ToColorIndex(int colorNumber, int paletteSize) { var colorIndexNumber = paletteSize * 2 - 2; var colorIndex = colorNumber % colorIndexNumber; if (colorIndex >= paletteSize) colorIndex = colorIndexNumber - colorIndex; return colorIndex; } } class MainProgram { public static async Task RunAsync(IntRectangle canvasPosition, Image image) { await Window.Current.Dispatcher.RunIdleAsync(e => image.Source = DrawMandelbrot(canvasPosition)); } static Palette GetPalette() { var palette = new Graph.Palette(16); palette[ 0] = new Color { R = 0x02, G = 0x08, B = 0x80 }; palette[ 1] = new Color { R = 0x10, G = 0x10, B = 0x70 }; palette[ 2] = new Color { R = 0x20, G = 0x18, B = 0x60 }; palette[ 3] = new Color { R = 0x30, G = 0x20, B = 0x50 }; palette[ 4] = new Color { R = 0x40, G = 0x28, B = 0x40 }; palette[ 5] = new Color { R = 0x50, G = 0x30, B = 0x30 }; palette[ 6] = new Color { R = 0x60, G = 0x38, B = 0x20 }; palette[ 7] = new Color { R = 0x70, G = 0x40, B = 0x10 }; palette[ 8] = new Color { R = 0x80, G = 0x48, B = 0x0e }; palette[ 9] = new Color { R = 0x90, G = 0x50, B = 0x0c }; palette[10] = new Color { R = 0xa0, G = 0x58, B = 0x0a }; palette[11] = new Color { R = 0xb0, G = 0x60, B = 0x08 }; palette[12] = new Color { R = 0xc0, G = 0x68, B = 0x06 }; palette[13] = new Color { R = 0xd0, G = 0x70, B = 0x04 }; palette[14] = new Color { R = 0xe8, G = 0x78, B = 0x02 }; palette[15] = new Color { R = 0xff, G = 0x80, B = 0x01 }; return palette; } static WriteableBitmap DrawMandelbrot(IntRectangle canvasPosition) { var mandelbrot = new Mandelbrot(position: canvasPosition, backgroundColor: Colors.MidnightBlue); mandelbrot.Draw(GetPalette()); return mandelbrot.Bitmap; } } }
実行結果もほぼ変わらない。
米国サンフラシスコで 4/2 - 4/4 に開催される Build 2014 の内容を凝縮して日本で開催される有償の開発者向けイベント。
3月上旬に登録開始予定。
かつて日本でも Microsoft TechEd という開発者/ITプロ向けの最大級のマイクロソフトの技術に関するカンファレンスが毎年横浜などで開かれていたが、2011年の東日本大震災の影響で開催が延期、その後中止され、それ以降開催されていない。
de:code は、日本での今年最大の開発者向けののマイクロソフトの技術に関するカンファレンスとなるだろう。
昨年まで Community Open Day として全国で一斉に開催されていたマイクロソフト技術の勉強会だが、今年は MVP Community Camp 2014 としてアジア パシフィック全体で行われる。
17日から21日迄はオンライン イベント、22日はオフライン イベントとなる。
私は北陸会場の担当。
MVP Community Camp 2014 北陸会場 | |
---|---|
会期 | 2014年3月22日(土) |
会場 | 石川工業高等専門学校 (石川県河北郡津幡町北中条タ1) 6号館 (トライアル研究センター) 2階講義室 |
参加費 | 無料 |
主催 | Hokuriku.NET 福井情報技術者協会[FITEA] |
協力 | 日本マイクロソフト |
詳細/お申込み | MVP Community Camp 2014 北陸会場 - ATND |
※ 『Hokuriku.NET Vol.13 in 富山「3D で遊ぼう ~C#er も TypeScript で楽々 WebGL~」』の資料の内容の解説。
今回は、TypeScript を使って WebGL に挑戦してみよう。
素で使うと中々に取っつき難い WebGL だが、TypeScript を使うことで比較的楽に使うことができる。 C#er の方も是非 Web での 3D プログラミングに興味を持っていただきたい。
WebGL は、HTML5 の Canvas に三次元 (または二次元) のグラフィックを表示する標準仕様だ。
Web ブラウザーで、プラグインなしで動作する。
現状 WebGL は、PC 上のブラウザーでは、最新の IE、Google Chrome、FireFoxでは、ほぼ動作する。但し全てが動くとは限らない。
モバイルで動作するものは多くない。
Platform | iPhone, iPad | Phones & Tablet | Android 4.0+ | Kindle Fire | Phones | BB10 | Tablet | MeeGo - N9 | Symbian | Windows Phone | Windows 8 | Android & Symbian | Java,iOS Android | Android, MeeGo* | Firefox OS |
WebGL 3D Canvas for the web |
○ Specific device |
○ 30+ |
○ |
○ 2.0+ |
○ 11+ |
○ 12+ (android) |
○ |
○ |
Windows RT タブレットでは動作する。
WebGL のプログラミングには、シェーディング言語である GLSL (OpenGL 2.0) と JavaScript を用いる。
次の図のような感じだ。左側が GLSL、右側が JavaScript による部分だ。
慣れていない人には、結構大変だ。
そこで、ここでは three.js という JavaScript のライブラリを用いることによる。
three.js は、WebGL のラッパーで、WebGL を抽象化して易しく使えるようにしてくれるのだ。
次の場所で入手することができる。
ダウンロードし、three.js または three.min.js というファイルを HTML ファイルから参照すれば OK だ。
three.js を TypeScript から使う為には、型定義ファイルの three.d.ts も必要だ。
こちらは、次の場所で入手することができる。
または、NuGet で入手することができる。
Visual Studio で NuGet を検索してインストールすることも可能だ。
この three.d.ts を自分の TypeScript ファイルから参照する。
では three.js で 3D プログラミングを始めてみよう。
ここでは、次のような順序で進めていく。
threejswebglsample.html では、three.min.js と threejswebglsample.ts から生成される threejswebglsample.js を参照する。
次のようにする。
<!DOCTYPE html> <html lang="ja"> <head> <meta charset="utf-8" /> <script src="three.min.js"></script> <script src="threejswebglsample.js"></script> <title>THREE.js で WebGL</title> </head> <body> <div id="viewport"></div> </body> </html>
中に viewport という id の div があるが、後で three.js によって、この中に Canvas を作る。
レンダラーを作成する TypeScript のコードは次のようになる。
// 1. WebGL レンダラーを作成 try { var renderer = new THREE.WebGLRenderer({ antialias: true }); } catch (e) {} if (renderer == null) { alert("お使いの環境では WebGL はご利用いただけません。"); return; } // サイズの設定 renderer.setSize(window.innerWidth, window.innerHeight); // 背景色の設定(色, 透明度) renderer.setClearColorHex(0x000000, 1); // レンダラーによって、viewport の ID を持つ HTML 要素に子要素として canvas を追加 document.querySelector("#viewport").appendChild(renderer.domElement);
これで描画する準備ができた。
次にカメラを作成する。
// 2. カメラを作成 // (透視投影の) カメラを作成 var fov = 100; // 画角 var aspect = window.innerWidth / window.innerHeight; // 縦横比 var camera = new THREE.PerspectiveCamera(fov, aspect); camera.position = new THREE.Vector3(0, 0, 1000); // z 方向に 1000 ずらす
カメラは、z 方向に 1000 ずらした位置に置くことにする。
シーン (空間) を作成する。 シーンには、後でライト (光源) を置いたり、メッシュと呼ばれるオブジェクトを配置したりする。
// 3. シーン (空間) を作成 var scene = new THREE.Scene();
シーン (空間) が作成された。
ライト (光源) を作成し、シーンに追加する。
ライトには幾つかの種類があるが、ここでは太陽光のような平行光源 (無限遠光源) を作成する。
// 3.1 ライト (光源) を作成 → シーンに追加 // 平行光源 (無限遠光源) を作成 var directionalLight = new THREE.DirectionalLight(0xffffff, 1); // 引数: 色, 強さ directionalLight.position = new THREE.Vector3(0, 0, 1); // z 方向から照らす // 光源をシーンに追加 scene.add(directionalLight);
シーン (空間) にライト (光源) が置かれた。
ジオメトリー (形) とマテリアル (材料) からメッシュを作成し、シーンに追加する。
three.js では、予めプリミティブ (基本的な) ジオメトリー (形) が幾つか用意されている。ここでは、その中から直方体を作成する。
また、マテリアル (材料) も幾つもの種類が用意されているが、ここでは単純な色だけを持ったものを用いる。
// 3.2 メッシュを用意 → シーンに追加 // ジオメトリー (形) とマテリアル (材料) からメッシュを作成 // プリミティブなジオメトリーを作成 var geometry = new THREE.CubeGeometry(500, 500, 500); // マテリアルを作成 (赤) var material = new THREE.MeshLambertMaterial({ color: 0xff0000 }); // ジオメトリーとマテリアルを合わせてメッシュを作成 var cubeMesh = new THREE.Mesh(geometry, material); // メッシュをシーンに追加 scene.add(cubeMesh);
これで、シーン (空間) にメッシュが置かれた。
最後にレンダリングを行うと描画される。
// 4. レンダリング - レンダラーとカメラでシーンをレンダリング renderer.render(scene, camera);
ここまでをクラスのメソッドに入れ、実行できるようにしてみよう。threejswebglsample.ts は、こうなる。
/// <reference path="three.d.ts"/> module ThreeJSWebGLSample { export class Application { static run() { // 1. WebGL レンダラーを作成 try { var renderer = new THREE.WebGLRenderer({ antialias: true }); } catch (e) {} if (renderer == null) { alert("お使いの環境では WebGL はご利用いただけません。"); return; } // サイズの設定 renderer.setSize(window.innerWidth, window.innerHeight); // 背景色の設定(色, 透明度) renderer.setClearColorHex(0x000000, 1); // レンダラーによって、viewport の ID を持つ HTML 要素に子要素として canvas を追加 document.querySelector("#viewport").appendChild(renderer.domElement); // 2. カメラを作成 // (透視投影の) カメラを作成 var fov = 100; // 画角 var aspect = window.innerWidth / window.innerHeight; // 縦横比 var camera = new THREE.PerspectiveCamera(fov, aspect); camera.position = new THREE.Vector3(0, 0, 1000); // z 方向に 1000 ずらす // 3. シーン (空間) を作成 var scene = new THREE.Scene(); // 3.1 ライト (光源) を作成 → シーンに追加 // 平行光源 (無限遠光源) を作成 var directionalLight = new THREE.DirectionalLight(0xffffff, 1); // 引数: 色, 強さ directionalLight.position = new THREE.Vector3(0, 0, 1); // z 方向から照らす // 光源をシーンに追加 scene.add(directionalLight); // 3.2 メッシュを用意 → シーンに追加 // ジオメトリー (形) とマテリアル (材料) からメッシュを作成 // プリミティブなジオメトリーを作成 var geometry = new THREE.CubeGeometry(500, 500, 500); // マテリアルを作成 (赤) var material = new THREE.MeshLambertMaterial({ color: 0xff0000 }); // ジオメトリーとマテリアルを合わせてメッシュを作成 var cubeMesh = new THREE.Mesh(geometry, material); // メッシュをシーンに追加 scene.add(cubeMesh); // 4. レンダリング - レンダラーとカメラでシーンをレンダリング renderer.render(scene, camera); } } } // ウィンドウがロードされた時 window.onload = () => { // アプリケーションの起動 ThreeJSWebGLSample.Application.run(); };
コンパイルして threejswebglsample.js を生成し、threejswebglsample.html を Internet Explorer 11 以降等の WebGL 対応の Web ブラウザーで表示してみよう。
赤い四角形が表示された。
実際には、立方体なのだが、真横から見ている為単なる正方形に見える。
これでは面白くないので、アニメーションをさせてみよう。
アニメーションをさせる為に次のようなコードでレンダリングしてみる。 メッシュの角度を少しずつ変化させるコードを requestAnimationFrame で再帰的に呼び出す。
これで立方体が少しずつ回転する。
static rendering(renderer: THREE.Renderer, camera: THREE.Camera, scene: THREE.Scene, cubeMesh: THREE.Mesh) { // メッシュを自転 cubeMesh.rotation.x += 0.01; cubeMesh.rotation.y += 0.01; // レンダリング renderer.render(scene, camera); // 繰り返す // - setInterval(render, 1000 / 60); より軽い // -- 不要な場合にループを行わない // -- 最適化されている requestAnimationFrame(() => Application.rendering(renderer, camera, scene, cubeMesh)); }
このコードを追加すると、threejswebglsample.ts 全体は次のようになる。
/// <reference path="three.d.ts"/> module ThreeJSWebGLSample { export class Application { static run() { // 1. WebGL レンダラーを作成 try { var renderer = new THREE.WebGLRenderer({ antialias: true }); } catch (e) {} if (renderer == null) { alert("お使いの環境では WebGL はご利用いただけません。"); return; } // サイズの設定 renderer.setSize(window.innerWidth, window.innerHeight); // 背景色の設定(色, 透明度) renderer.setClearColorHex(0x000000, 1); // レンダラーによって、viewport の ID を持つ HTML 要素に子要素として canvas を追加 document.querySelector("#viewport").appendChild(renderer.domElement); // 2. カメラを作成 // (透視投影の) カメラを作成 var fov = 100; // 画角 var aspect = window.innerWidth / window.innerHeight; // 縦横比 var camera = new THREE.PerspectiveCamera(fov, aspect); camera.position = new THREE.Vector3(0, 0, 1000); // z 方向に 1000 ずらす // 3. シーン (空間) を作成 var scene = new THREE.Scene(); // 3.1 ライト (光源) を作成 → シーンに追加 // 平行光源 (無限遠光源) を作成 var directionalLight = new THREE.DirectionalLight(0xffffff, 1); // 引数: 色, 強さ directionalLight.position = new THREE.Vector3(0, 0, 1); // z 方向から照らす // 光源をシーンに追加 scene.add(directionalLight); // 3.2 メッシュを用意 → シーンに追加 // ジオメトリー (形) とマテリアル (材料) からメッシュを作成 // プリミティブなジオメトリーを作成 var geometry = new THREE.CubeGeometry(500, 500, 500); // マテリアルを作成 (赤) var material = new THREE.MeshLambertMaterial({ color: 0xff0000 }); // ジオメトリーとマテリアルを合わせてメッシュを作成 var cubeMesh = new THREE.Mesh(geometry, material); // メッシュをシーンに追加 scene.add(cubeMesh); // 4. レンダリング - レンダラーとカメラでシーンをレンダリング Application.rendering(renderer, camera, scene, cubeMesh); } static rendering(renderer: THREE.Renderer, camera: THREE.Camera, scene: THREE.Scene, cubeMesh: THREE.Mesh) { // メッシュを自転 cubeMesh.rotation.x += 0.01; cubeMesh.rotation.y += 0.01; // レンダリング renderer.render(scene, camera); // 繰り返す // - setInterval(render, 1000 / 60); より軽い // -- 不要な場合にループを行わない // -- 最適化されている requestAnimationFrame(() => Application.rendering(renderer, camera, scene, cubeMesh)); } } } // ウィンドウがロードされた時 window.onload = () => { // アプリケーションの起動 ThreeJSWebGLSample.Application.run(); };
コンパイルして表示してみよう。
今度は、立方体が回っているのが分かるだろう。
試しに、マテリアル (材料) を換えてみよう。 例えば、
// マテリアルを作成 (赤) var material = new THREE.MeshLambertMaterial({ color: 0xff0000 });
の部分を、
// マテリアルを作成 (赤) var material = new THREE.MeshPhongMaterial({ color : 0xff0000, specular : 0xcccccc, shininess: 50 , ambient : 0xffffff });
に変えてコンパイルし、表示してみよう。
先程のものより艶のある立方体が表示された筈だ。
実際のサンプルを次の場所に用意した。
マテリアル (材料) を変えたり、ジオメトリー (形) を立方体以外にしたり、ライト (証明) を変えたり、アニメーションを変えたり、色々と試してみてほしい。 3D のものが表示されるのは楽しいものだ。
次回は、別のサンプルをご紹介する。
Xamarin は、iOS、Android、Windows、Mac のアプリケーションを C# で開発できるツールの名称であり、また、そのツールを開発/販売する企業の名称だ。
オープンソースの .NET Framework 互換環境である Mono を基盤としている。前身は、MonoTouch や Mono for Android 等だ。
Xamarin を試した。
Mac (や Windows) 上で Xamarin に含まれる Xamarin Studio を使って iOS アプリを開発する場合は、もっと簡単だ。 Visual Studio と似た IDE で、iOS アプリを新規に作成し、ビルド・実行できる。
Mac (や Windows) 上での Xamarin Studio による Android アプリ開発も同様。
Xamarin は、C# で iOS、Android、Windows、Mac OS のアプリケーションを開発できるクロス プラットフォーム開発環境だ。
ネイティブにアプリケーションを書けるのを特長としており、それぞれのプラットフォーム用のコードを C# で記述できる。
その為、特化した部分はプラットフォーム毎に別個に書く必要があり、複数のプラットフォームで動作するアプリケーションを効率よく書く為には、いかにコードを共通化するか、が重要となる。
Xamarin は、PCL (Portable Class Library: 複数のプラットフォームに対応可能な .NET Framework のクラス ライブラリ) に対応しており、ここにできるだけ多くのコードを集めたい。 その為には、MVVM (Model-View-ViewModel) パターンで Model と ViewModel の部分を PCL において共通化し、なるべく View の薄い部分だけを個々に記述するようにしたいものだ。 画面の記述は、プラットフォームによって異なるので、個々の画面の記述をできるだけ薄くし、内部処理等を切り離して共通化する訳だ。
Xamarin から利用できる MVVM フレームワーク "MvvmCross" 関連の情報を集めてみた。
昨年まで Community Open Day として全国で一斉に開催されていたマイクロソフト技術の勉強会が、今年は MVP Community Camp 2014 としてアジア パシフィック全体で行われた。
3月17日から21日迄はオンライン イベント、22日はオフライン イベントだった。
オフライン イベントは、例年通り北陸会場でも開催され、39名の方が参加された。
『MVP Community Camp 2014 北陸会場』 | |
---|---|
日時 | 2014年3月22日 |
会場 | 石川工業高等専門学校 (石川県河北郡津幡町) |
主催 | Hokuriku.NET、福井情報技術者協会[FITEA] |
協力 | 日本マイクロソフト |
サイト | MVP Community Camp 2014 北陸会場 - ATND |
タイトル | スピーカー | 内容 | 写真 | ||
---|---|---|---|---|---|
受付 | |||||
挨拶 | ライブストリーミングの視聴 | ||||
キーノート (基調講演) | スクリーンのその先へ Beyond the screen : There and Back Again | 東 賢 氏 | インフラジスティックス・ジャパン㈱ 代表取締役、MVP | ライブストリーミングの視聴 | |
セッション 1 | Windows Server 2012 R2 を使ったオンラインサービス連携 | 澤田 賢也 氏 | Windows Server for Small and Medium Business MVP | ||
セッション 2 | まだ Windows 8.1 を使っていないの? ~そんなあなたに勧めたい! Windows 8.1 & Surface | さくしま たかえ 氏 | MVP for Windows Expert-Consumer | 資料 | |
ランチタイム | インド料理「るびな」の弁当 | ||||
セッション 3 | エバンジェリスト養成講座 パワースライド特別編@北陸 | 西脇 資哲 氏 | 日本マイクロソフト株式会社 エバンジェリスト | ||
セッション 4 | HTML5 時代に必要なスキル | 山崎 大助 氏 | デジタルハリウッド講師/INOP/U-SYS Microsoft MVP | 資料 | |
セッション 5 | C# MVP によるドキドキ・ライブコーディング! | 石野 光仁 氏、鈴木 孝明 氏、小島 富治雄 氏 | C# MVP | ||
ライトニングトークス | Windows Azure Web サイトと SendGrid で遊ぼう! | 芝村 達郎 氏 | Microsoft MVP for ASP.NET/IIS | 資料 | |
Windows Server 2012 R2 Essentialsの魅力~手間暇かけずに活用しよう! | 那須 悟 氏 | Microsoft MVP for Windows Home Server | 資料 | ||
僕と君と、彼と彼女と ~ヨニンヨンショク、四人でリバーシ~ | 敷浪俊樹 氏 | 学生さん | |||
アップルプラネット | 寺本大輝 氏 | 学生さん | |||
.NET MicroFrameworkで遊ぼうとしてみた | 西永俊文 氏 | 学生さん | |||
プレゼント抽選会 | |||||
懇親会 |
マイクロソフトの開発者会議 BUILD 2014 (4月2–4日、米国サンフランシスコ) とその周辺で発表されたマイクロソフト技術へのリンク。
先日、マイクロソフトの開発者会議 BUILD 2014 (4月2–4日、米国サンフランシスコ) で Roslyn がオープンソースとして公開されたことが発表された。
※ その他の BUILD 2014 に関する記事は、 「[Event] BUILD 2014 からの情報」を参照のこと。
Roslyn は、C# や Visual Basic のコンパイラーを再実装し、内部の コード分析などの API 等を公開したものだ。
Roslyn に関しては、以前、次にあげる記事で扱った。参考にしてほしい。
今回は、公開された Roslyn を触ってみたい。
「Taking a tour of Roslyn - C# Frequently Asked Questions - MSDN Blogs」にやり方が書いてある。
先ずは、Roslyn Preview のインストールからだ。
Roslyn の Preview Site に行き、Roslyn Preview をダウンロードする。マイクロソフト アカウントが必要だ (持っていない場合は新規登録すれば OK)。
ダウンロードした Roslyn SDK Preview.zip の中にある次のファイルをインストールする。
もし Visual Studio 2013 SDK がインストールされていない場合はこちらも必要だ。 ちなみに Visual Studio Express Edition にはインストールできない。 Professional Edition 以上が必要だ。
これらをインストールして Visual Studio を起動する。 すると、「ファイル」 - 「新規作成」 - 「プロジェクト」を開くと、「テンプレート」 - 「Visual C#」の下に「Roslyn」が増えていて、「Console Application」、「Code Refactoring」、「Diagnostic with Code Fix」が選択できるようになっている。 「Visual Basic」の下も同様だ。
Roslyn による診断機能やリファクタリング機能を作成することができるようになっている。
これらについては、後日別の記事で試そうと思う。 「The Future of C# | Build 2014 | Channel 9」でそれらの作成手順を確認することができる。
次に Roslyn のソースコードをダウンロードしてみよう。
オープンソースとなった Roslyn のソースコードは、「.NET Compiler Platform ("Roslyn") - CodePlex」からダウンロードすることができる。
これで、ローカル マシンの指定した場所に Roslyn のソースコードがダウンロードされる。
では、この中から Roslyn.sln を Visual Studio で開いてみよう。
沢山のソースコードが確認できる。
この儘 Visual Studio でビルドしてみよう。 ビルドが完了すると、Binaries というフォルダーの下に rcsc.exe (C# コンパイラー) や rvbc.exe (Visual Basic コンパイラー) などが作られる。
rcsc.exe (C# コンパイラー) を試してみよう。
今回は、"Hello world!" を表示する次のような C# のファイル hello.cs を (C:\Source\Roslyn\Test に) 作成した。
class Program { static void Main() { System.Console.WriteLine("Hello world!"); } }
コマンドラインで、これをコンパイルしてみる。
C:\Source\Roslyn\Test>"C:\Source\Roslyn\Binaries\Debug\rcsc.exe" hello.cs Microsoft (R) Visual C# Compiler version 0.7.0.0 Copyright (C) Microsoft Corporation. All rights reserved.
無事ビルドされ、hello.exe が作られる。
実行してみよう。
C:\Source\Roslyn\Test>hello Hello world!
Hello world! と表示された。
折角ソースコードがあるので、「Taking a tour of Roslyn」を真似て、試しに Roslyn のソースコードを少し弄ってみることにする。
例えば、キーワード "class" の代わりに "クラス" を使うようにしてみよう。
変更するのは、C:\Source\Roslyn\Src\Compilers\CSharp\Source\Syntax\SyntaxKindFacts.cs だ。
このファイル中の "class" を "クラス" に文字列置換する。
case "class":
を
case "クラス":
に変更。
return "class";
を
return "クラス";
に変更。
次に、文字列リテラルで使われるダブル クォーテーションの代わりに鉤括弧 「」 も使えるようにしてみよう。
変更するのは、C:\Source\Roslyn\Src\Compilers\CSharp\Source\Parser\Lexer.cs だ。
character = TextWindow.PeekChar(); switch (character) { case '\"': case '\'':
を
character = TextWindow.PeekChar(); switch (character) { case '\"': case '\'': case '「':
に変更。
private bool ScanStringLiteral(ref TokenInfo info, bool allowEscapes = true) { var quoteCharacter = TextWindow.PeekChar(); if (quoteCharacter == '\'' || quoteCharacter == '"') {
を
private bool ScanStringLiteral(ref TokenInfo info, bool allowEscapes = true) { var quoteCharacter = TextWindow.PeekChar(); if (quoteCharacter == '\'' || quoteCharacter == '"' || quoteCharacter == '「') {
に変更。
else if (ch == quoteCharacter) { TextWindow.AdvanceChar(); break; }
を
else if (ch == quoteCharacter || (ch == '」' && quoteCharacter == '「')) { TextWindow.AdvanceChar(); break; }
に変更。
変更した Roslyn をビルドして使ってみよう。
今回用意するのは、次のような hello.cs だ。 "class" を"クラス"にし、ダブル クオーテーションを鉤括弧にした。
クラス プログラム { static void Main() { System.Console.WriteLine(「こんにちは。ロズリン。」); } }
新しい rcsc.exe でビルドする。
C:\Source\Roslyn\Test>"C:\Source\Roslyn\Binaries\Debug\rcsc.exe" hello.cs Microsoft (R) Visual C# Compiler version 0.7.0.0 Copyright (C) Microsoft Corporation. All rights reserved.
無事ビルドされ、hello.exe が作られる。
実行してみよう。
C:\Source\Roslyn\Test>hello こんにちは。ロズリン。
うまくいったようだ。
今回は、オープンソースとなった Roslyn を弄ってみた。 次回は、新しい Roslyn Preview を使ってコードの診断やリファクタリングの機能を作ってみたい。
※ 『[TypeScript][WebGL][Three.js] 3D で遊ぼう ~C#er も TypeScript で楽々 WebGL~ ― 準備編』の続き。
以前、HTML5 で多体問題シミュレーションというのを作った。
今回は、これを WebGL でやってみよう。
実装には、前にやったように同様これを WebGL でやってみよう。
WebGL 対応のブラウザ (Internet Explorer、Firefox、Google Chrome、の現時点での最新版等) では、画像のクリックで実際に動作させてみることができる。
ソースコードは TypeScript で書かれていて、three.js を使用している。
こちら: NBodyWebGL.Source.zip。
TypeScript を使うことで、クラス設計も楽に行うことができる。
「世界一IQの低いソースコードはこれ。」で始まる tweet が興味深かった。
https://twitter.com/vjroba/status/494882208788660226
世界一IQの低いソースコードはこれ。
if と else を使って KeyEvent クラスの中の 200 を超える数の定数と一つずつ比較して、文字列に変換している Java のコードだ。
色々リプライされているようだが、「初心者向けの入門書なので、リフレクションや連想配列を (switch すら) 使わない方針で書いている」のかも知れない。
しかし、何のために、こんな方法を使ってまで KeyEvent の中の定数を文字列にしてるのかは不明だ。
初心者向けサンプルにこういう「実務で余り役に立たないし、手本にならないコード」を選んでいることが間違っているように思う。
コードの良し悪しについては議論するまでもないような気がするし、定数名を文字列に変換する必要性が生じた時点でおかしい気がするが、if をこんなに続けた場合のパフォーマンスが気になったので、C# で試した。
以下では、あえて if を使って定数名を文字列に変換し、switch や Dictinary などを使った場合と処理時間を比較してみたい。
先ず、225 個の const int を持つ Sample クラス。あえて enum は使っていない。
※ ぞっとするようなこのクラスや後述する else if の繰り返し, case の繰り返しのソースコードは、Enumerable.Range(1,225).ToList().ForEach(number => Console.WriteLine(適当なフォーマット文字列, number)) で作った。
public static class Sample { public const int CONST_001 = 1; public const int CONST_002 = 2; public const int CONST_003 = 3; public const int CONST_004 = 4; public const int CONST_005 = 5; public const int CONST_006 = 6; public const int CONST_007 = 7; public const int CONST_008 = 8; public const int CONST_009 = 9; public const int CONST_010 = 10; public const int CONST_011 = 11; public const int CONST_012 = 12; public const int CONST_013 = 13; public const int CONST_014 = 14; public const int CONST_015 = 15; public const int CONST_016 = 16; public const int CONST_017 = 17; public const int CONST_018 = 18; public const int CONST_019 = 19; public const int CONST_020 = 20; public const int CONST_021 = 21; public const int CONST_022 = 22; public const int CONST_023 = 23; public const int CONST_024 = 24; public const int CONST_025 = 25; public const int CONST_026 = 26; public const int CONST_027 = 27; public const int CONST_028 = 28; public const int CONST_029 = 29; public const int CONST_030 = 30; public const int CONST_031 = 31; public const int CONST_032 = 32; public const int CONST_033 = 33; public const int CONST_034 = 34; public const int CONST_035 = 35; public const int CONST_036 = 36; public const int CONST_037 = 37; public const int CONST_038 = 38; public const int CONST_039 = 39; public const int CONST_040 = 40; public const int CONST_041 = 41; public const int CONST_042 = 42; public const int CONST_043 = 43; public const int CONST_044 = 44; public const int CONST_045 = 45; public const int CONST_046 = 46; public const int CONST_047 = 47; public const int CONST_048 = 48; public const int CONST_049 = 49; public const int CONST_050 = 50; public const int CONST_051 = 51; public const int CONST_052 = 52; public const int CONST_053 = 53; public const int CONST_054 = 54; public const int CONST_055 = 55; public const int CONST_056 = 56; public const int CONST_057 = 57; public const int CONST_058 = 58; public const int CONST_059 = 59; public const int CONST_060 = 60; public const int CONST_061 = 61; public const int CONST_062 = 62; public const int CONST_063 = 63; public const int CONST_064 = 64; public const int CONST_065 = 65; public const int CONST_066 = 66; public const int CONST_067 = 67; public const int CONST_068 = 68; public const int CONST_069 = 69; public const int CONST_070 = 70; public const int CONST_071 = 71; public const int CONST_072 = 72; public const int CONST_073 = 73; public const int CONST_074 = 74; public const int CONST_075 = 75; public const int CONST_076 = 76; public const int CONST_077 = 77; public const int CONST_078 = 78; public const int CONST_079 = 79; public const int CONST_080 = 80; public const int CONST_081 = 81; public const int CONST_082 = 82; public const int CONST_083 = 83; public const int CONST_084 = 84; public const int CONST_085 = 85; public const int CONST_086 = 86; public const int CONST_087 = 87; public const int CONST_088 = 88; public const int CONST_089 = 89; public const int CONST_090 = 90; public const int CONST_091 = 91; public const int CONST_092 = 92; public const int CONST_093 = 93; public const int CONST_094 = 94; public const int CONST_095 = 95; public const int CONST_096 = 96; public const int CONST_097 = 97; public const int CONST_098 = 98; public const int CONST_099 = 99; public const int CONST_100 = 100; public const int CONST_101 = 101; public const int CONST_102 = 102; public const int CONST_103 = 103; public const int CONST_104 = 104; public const int CONST_105 = 105; public const int CONST_106 = 106; public const int CONST_107 = 107; public const int CONST_108 = 108; public const int CONST_109 = 109; public const int CONST_110 = 110; public const int CONST_111 = 111; public const int CONST_112 = 112; public const int CONST_113 = 113; public const int CONST_114 = 114; public const int CONST_115 = 115; public const int CONST_116 = 116; public const int CONST_117 = 117; public const int CONST_118 = 118; public const int CONST_119 = 119; public const int CONST_120 = 120; public const int CONST_121 = 121; public const int CONST_122 = 122; public const int CONST_123 = 123; public const int CONST_124 = 124; public const int CONST_125 = 125; public const int CONST_126 = 126; public const int CONST_127 = 127; public const int CONST_128 = 128; public const int CONST_129 = 129; public const int CONST_130 = 130; public const int CONST_131 = 131; public const int CONST_132 = 132; public const int CONST_133 = 133; public const int CONST_134 = 134; public const int CONST_135 = 135; public const int CONST_136 = 136; public const int CONST_137 = 137; public const int CONST_138 = 138; public const int CONST_139 = 139; public const int CONST_140 = 140; public const int CONST_141 = 141; public const int CONST_142 = 142; public const int CONST_143 = 143; public const int CONST_144 = 144; public const int CONST_145 = 145; public const int CONST_146 = 146; public const int CONST_147 = 147; public const int CONST_148 = 148; public const int CONST_149 = 149; public const int CONST_150 = 150; public const int CONST_151 = 151; public const int CONST_152 = 152; public const int CONST_153 = 153; public const int CONST_154 = 154; public const int CONST_155 = 155; public const int CONST_156 = 156; public const int CONST_157 = 157; public const int CONST_158 = 158; public const int CONST_159 = 159; public const int CONST_160 = 160; public const int CONST_161 = 161; public const int CONST_162 = 162; public const int CONST_163 = 163; public const int CONST_164 = 164; public const int CONST_165 = 165; public const int CONST_166 = 166; public const int CONST_167 = 167; public const int CONST_168 = 168; public const int CONST_169 = 169; public const int CONST_170 = 170; public const int CONST_171 = 171; public const int CONST_172 = 172; public const int CONST_173 = 173; public const int CONST_174 = 174; public const int CONST_175 = 175; public const int CONST_176 = 176; public const int CONST_177 = 177; public const int CONST_178 = 178; public const int CONST_179 = 179; public const int CONST_180 = 180; public const int CONST_181 = 181; public const int CONST_182 = 182; public const int CONST_183 = 183; public const int CONST_184 = 184; public const int CONST_185 = 185; public const int CONST_186 = 186; public const int CONST_187 = 187; public const int CONST_188 = 188; public const int CONST_189 = 189; public const int CONST_190 = 190; public const int CONST_191 = 191; public const int CONST_192 = 192; public const int CONST_193 = 193; public const int CONST_194 = 194; public const int CONST_195 = 195; public const int CONST_196 = 196; public const int CONST_197 = 197; public const int CONST_198 = 198; public const int CONST_199 = 199; public const int CONST_200 = 200; public const int CONST_201 = 201; public const int CONST_202 = 202; public const int CONST_203 = 203; public const int CONST_204 = 204; public const int CONST_205 = 205; public const int CONST_206 = 206; public const int CONST_207 = 207; public const int CONST_208 = 208; public const int CONST_209 = 209; public const int CONST_210 = 210; public const int CONST_211 = 211; public const int CONST_212 = 212; public const int CONST_213 = 213; public const int CONST_214 = 214; public const int CONST_215 = 215; public const int CONST_216 = 216; public const int CONST_217 = 217; public const int CONST_218 = 218; public const int CONST_219 = 219; public const int CONST_220 = 220; public const int CONST_221 = 221; public const int CONST_222 = 222; public const int CONST_223 = 223; public const int CONST_224 = 224; public const int CONST_225 = 225; }
次に、実行時間測定用に PerformanceTester というクラスを用意した。
using System; using System.Diagnostics; public static class PerformanceTester { static Stopwatch stopwatch = new Stopwatch(); public static double Test(Action action, long times) { stopwatch.Restart(); for (var counter = 0L; counter < times; counter++) action(); stopwatch.Stop(); return stopwatch.ElapsedMilliseconds / 1000.0; } }
最後に Main を含んだクラスだ。
このクラスには、次の3種の「int の値を渡すと定数名を返す」メソッドがある。
最後の Dictionary で書いたものは、初期化の部分にリフレクションを使った ConstantsTable クラスを使っている。
毎回リフレクションで取得することも可能だが、そうすると、3桁位実行速度が落ちてしまう。
そこで、最初にリフレクションで、フィールドの値と名前の一覧を取得し、値をキーとして Dictionary に格納する。
その Dictionary を使って、値から文字列を得る仕組みだ。
Test メソッドは、「渡されたメソッドを使って1~255の値を定数名に変換する」処理を 100,000回実行する時間を測り、それを表示する。
using System; using System.Collections.Generic; using System.Linq; using System.Reflection; class IfPerformanceTestProgram { static string IfSample(int value) { string result; if (value == Sample.CONST_001) result = "CONST_001"; else if (value == Sample.CONST_002) result = "CONST_002"; else if (value == Sample.CONST_003) result = "CONST_003"; else if (value == Sample.CONST_004) result = "CONST_004"; else if (value == Sample.CONST_005) result = "CONST_005"; else if (value == Sample.CONST_006) result = "CONST_006"; else if (value == Sample.CONST_007) result = "CONST_007"; else if (value == Sample.CONST_008) result = "CONST_008"; else if (value == Sample.CONST_009) result = "CONST_009"; else if (value == Sample.CONST_010) result = "CONST_010"; else if (value == Sample.CONST_011) result = "CONST_011"; else if (value == Sample.CONST_012) result = "CONST_012"; else if (value == Sample.CONST_013) result = "CONST_013"; else if (value == Sample.CONST_014) result = "CONST_014"; else if (value == Sample.CONST_015) result = "CONST_015"; else if (value == Sample.CONST_016) result = "CONST_016"; else if (value == Sample.CONST_017) result = "CONST_017"; else if (value == Sample.CONST_018) result = "CONST_018"; else if (value == Sample.CONST_019) result = "CONST_019"; else if (value == Sample.CONST_020) result = "CONST_020"; else if (value == Sample.CONST_021) result = "CONST_021"; else if (value == Sample.CONST_022) result = "CONST_022"; else if (value == Sample.CONST_023) result = "CONST_023"; else if (value == Sample.CONST_024) result = "CONST_024"; else if (value == Sample.CONST_025) result = "CONST_025"; else if (value == Sample.CONST_026) result = "CONST_026"; else if (value == Sample.CONST_027) result = "CONST_027"; else if (value == Sample.CONST_028) result = "CONST_028"; else if (value == Sample.CONST_029) result = "CONST_029"; else if (value == Sample.CONST_030) result = "CONST_030"; else if (value == Sample.CONST_031) result = "CONST_031"; else if (value == Sample.CONST_032) result = "CONST_032"; else if (value == Sample.CONST_033) result = "CONST_033"; else if (value == Sample.CONST_034) result = "CONST_034"; else if (value == Sample.CONST_035) result = "CONST_035"; else if (value == Sample.CONST_036) result = "CONST_036"; else if (value == Sample.CONST_037) result = "CONST_037"; else if (value == Sample.CONST_038) result = "CONST_038"; else if (value == Sample.CONST_039) result = "CONST_039"; else if (value == Sample.CONST_040) result = "CONST_040"; else if (value == Sample.CONST_041) result = "CONST_041"; else if (value == Sample.CONST_042) result = "CONST_042"; else if (value == Sample.CONST_043) result = "CONST_043"; else if (value == Sample.CONST_044) result = "CONST_044"; else if (value == Sample.CONST_045) result = "CONST_045"; else if (value == Sample.CONST_046) result = "CONST_046"; else if (value == Sample.CONST_047) result = "CONST_047"; else if (value == Sample.CONST_048) result = "CONST_048"; else if (value == Sample.CONST_049) result = "CONST_049"; else if (value == Sample.CONST_050) result = "CONST_050"; else if (value == Sample.CONST_051) result = "CONST_051"; else if (value == Sample.CONST_052) result = "CONST_052"; else if (value == Sample.CONST_053) result = "CONST_053"; else if (value == Sample.CONST_054) result = "CONST_054"; else if (value == Sample.CONST_055) result = "CONST_055"; else if (value == Sample.CONST_056) result = "CONST_056"; else if (value == Sample.CONST_057) result = "CONST_057"; else if (value == Sample.CONST_058) result = "CONST_058"; else if (value == Sample.CONST_059) result = "CONST_059"; else if (value == Sample.CONST_060) result = "CONST_060"; else if (value == Sample.CONST_061) result = "CONST_061"; else if (value == Sample.CONST_062) result = "CONST_062"; else if (value == Sample.CONST_063) result = "CONST_063"; else if (value == Sample.CONST_064) result = "CONST_064"; else if (value == Sample.CONST_065) result = "CONST_065"; else if (value == Sample.CONST_066) result = "CONST_066"; else if (value == Sample.CONST_067) result = "CONST_067"; else if (value == Sample.CONST_068) result = "CONST_068"; else if (value == Sample.CONST_069) result = "CONST_069"; else if (value == Sample.CONST_070) result = "CONST_070"; else if (value == Sample.CONST_071) result = "CONST_071"; else if (value == Sample.CONST_072) result = "CONST_072"; else if (value == Sample.CONST_073) result = "CONST_073"; else if (value == Sample.CONST_074) result = "CONST_074"; else if (value == Sample.CONST_075) result = "CONST_075"; else if (value == Sample.CONST_076) result = "CONST_076"; else if (value == Sample.CONST_077) result = "CONST_077"; else if (value == Sample.CONST_078) result = "CONST_078"; else if (value == Sample.CONST_079) result = "CONST_079"; else if (value == Sample.CONST_080) result = "CONST_080"; else if (value == Sample.CONST_081) result = "CONST_081"; else if (value == Sample.CONST_082) result = "CONST_082"; else if (value == Sample.CONST_083) result = "CONST_083"; else if (value == Sample.CONST_084) result = "CONST_084"; else if (value == Sample.CONST_085) result = "CONST_085"; else if (value == Sample.CONST_086) result = "CONST_086"; else if (value == Sample.CONST_087) result = "CONST_087"; else if (value == Sample.CONST_088) result = "CONST_088"; else if (value == Sample.CONST_089) result = "CONST_089"; else if (value == Sample.CONST_090) result = "CONST_090"; else if (value == Sample.CONST_091) result = "CONST_091"; else if (value == Sample.CONST_092) result = "CONST_092"; else if (value == Sample.CONST_093) result = "CONST_093"; else if (value == Sample.CONST_094) result = "CONST_094"; else if (value == Sample.CONST_095) result = "CONST_095"; else if (value == Sample.CONST_096) result = "CONST_096"; else if (value == Sample.CONST_097) result = "CONST_097"; else if (value == Sample.CONST_098) result = "CONST_098"; else if (value == Sample.CONST_099) result = "CONST_099"; else if (value == Sample.CONST_100) result = "CONST_100"; else if (value == Sample.CONST_101) result = "CONST_101"; else if (value == Sample.CONST_102) result = "CONST_102"; else if (value == Sample.CONST_103) result = "CONST_103"; else if (value == Sample.CONST_104) result = "CONST_104"; else if (value == Sample.CONST_105) result = "CONST_105"; else if (value == Sample.CONST_106) result = "CONST_106"; else if (value == Sample.CONST_107) result = "CONST_107"; else if (value == Sample.CONST_108) result = "CONST_108"; else if (value == Sample.CONST_109) result = "CONST_109"; else if (value == Sample.CONST_110) result = "CONST_110"; else if (value == Sample.CONST_111) result = "CONST_111"; else if (value == Sample.CONST_112) result = "CONST_112"; else if (value == Sample.CONST_113) result = "CONST_113"; else if (value == Sample.CONST_114) result = "CONST_114"; else if (value == Sample.CONST_115) result = "CONST_115"; else if (value == Sample.CONST_116) result = "CONST_116"; else if (value == Sample.CONST_117) result = "CONST_117"; else if (value == Sample.CONST_118) result = "CONST_118"; else if (value == Sample.CONST_119) result = "CONST_119"; else if (value == Sample.CONST_120) result = "CONST_120"; else if (value == Sample.CONST_121) result = "CONST_121"; else if (value == Sample.CONST_122) result = "CONST_122"; else if (value == Sample.CONST_123) result = "CONST_123"; else if (value == Sample.CONST_124) result = "CONST_124"; else if (value == Sample.CONST_125) result = "CONST_125"; else if (value == Sample.CONST_126) result = "CONST_126"; else if (value == Sample.CONST_127) result = "CONST_127"; else if (value == Sample.CONST_128) result = "CONST_128"; else if (value == Sample.CONST_129) result = "CONST_129"; else if (value == Sample.CONST_130) result = "CONST_130"; else if (value == Sample.CONST_131) result = "CONST_131"; else if (value == Sample.CONST_132) result = "CONST_132"; else if (value == Sample.CONST_133) result = "CONST_133"; else if (value == Sample.CONST_134) result = "CONST_134"; else if (value == Sample.CONST_135) result = "CONST_135"; else if (value == Sample.CONST_136) result = "CONST_136"; else if (value == Sample.CONST_137) result = "CONST_137"; else if (value == Sample.CONST_138) result = "CONST_138"; else if (value == Sample.CONST_139) result = "CONST_139"; else if (value == Sample.CONST_140) result = "CONST_140"; else if (value == Sample.CONST_141) result = "CONST_141"; else if (value == Sample.CONST_142) result = "CONST_142"; else if (value == Sample.CONST_143) result = "CONST_143"; else if (value == Sample.CONST_144) result = "CONST_144"; else if (value == Sample.CONST_145) result = "CONST_145"; else if (value == Sample.CONST_146) result = "CONST_146"; else if (value == Sample.CONST_147) result = "CONST_147"; else if (value == Sample.CONST_148) result = "CONST_148"; else if (value == Sample.CONST_149) result = "CONST_149"; else if (value == Sample.CONST_150) result = "CONST_150"; else if (value == Sample.CONST_151) result = "CONST_151"; else if (value == Sample.CONST_152) result = "CONST_152"; else if (value == Sample.CONST_153) result = "CONST_153"; else if (value == Sample.CONST_154) result = "CONST_154"; else if (value == Sample.CONST_155) result = "CONST_155"; else if (value == Sample.CONST_156) result = "CONST_156"; else if (value == Sample.CONST_157) result = "CONST_157"; else if (value == Sample.CONST_158) result = "CONST_158"; else if (value == Sample.CONST_159) result = "CONST_159"; else if (value == Sample.CONST_160) result = "CONST_160"; else if (value == Sample.CONST_161) result = "CONST_161"; else if (value == Sample.CONST_162) result = "CONST_162"; else if (value == Sample.CONST_163) result = "CONST_163"; else if (value == Sample.CONST_164) result = "CONST_164"; else if (value == Sample.CONST_165) result = "CONST_165"; else if (value == Sample.CONST_166) result = "CONST_166"; else if (value == Sample.CONST_167) result = "CONST_167"; else if (value == Sample.CONST_168) result = "CONST_168"; else if (value == Sample.CONST_169) result = "CONST_169"; else if (value == Sample.CONST_170) result = "CONST_170"; else if (value == Sample.CONST_171) result = "CONST_171"; else if (value == Sample.CONST_172) result = "CONST_172"; else if (value == Sample.CONST_173) result = "CONST_173"; else if (value == Sample.CONST_174) result = "CONST_174"; else if (value == Sample.CONST_175) result = "CONST_175"; else if (value == Sample.CONST_176) result = "CONST_176"; else if (value == Sample.CONST_177) result = "CONST_177"; else if (value == Sample.CONST_178) result = "CONST_178"; else if (value == Sample.CONST_179) result = "CONST_179"; else if (value == Sample.CONST_180) result = "CONST_180"; else if (value == Sample.CONST_181) result = "CONST_181"; else if (value == Sample.CONST_182) result = "CONST_182"; else if (value == Sample.CONST_183) result = "CONST_183"; else if (value == Sample.CONST_184) result = "CONST_184"; else if (value == Sample.CONST_185) result = "CONST_185"; else if (value == Sample.CONST_186) result = "CONST_186"; else if (value == Sample.CONST_187) result = "CONST_187"; else if (value == Sample.CONST_188) result = "CONST_188"; else if (value == Sample.CONST_189) result = "CONST_189"; else if (value == Sample.CONST_190) result = "CONST_190"; else if (value == Sample.CONST_191) result = "CONST_191"; else if (value == Sample.CONST_192) result = "CONST_192"; else if (value == Sample.CONST_193) result = "CONST_193"; else if (value == Sample.CONST_194) result = "CONST_194"; else if (value == Sample.CONST_195) result = "CONST_195"; else if (value == Sample.CONST_196) result = "CONST_196"; else if (value == Sample.CONST_197) result = "CONST_197"; else if (value == Sample.CONST_198) result = "CONST_198"; else if (value == Sample.CONST_199) result = "CONST_199"; else if (value == Sample.CONST_200) result = "CONST_200"; else if (value == Sample.CONST_201) result = "CONST_201"; else if (value == Sample.CONST_202) result = "CONST_202"; else if (value == Sample.CONST_203) result = "CONST_203"; else if (value == Sample.CONST_204) result = "CONST_204"; else if (value == Sample.CONST_205) result = "CONST_205"; else if (value == Sample.CONST_206) result = "CONST_206"; else if (value == Sample.CONST_207) result = "CONST_207"; else if (value == Sample.CONST_208) result = "CONST_208"; else if (value == Sample.CONST_209) result = "CONST_209"; else if (value == Sample.CONST_210) result = "CONST_210"; else if (value == Sample.CONST_211) result = "CONST_211"; else if (value == Sample.CONST_212) result = "CONST_212"; else if (value == Sample.CONST_213) result = "CONST_213"; else if (value == Sample.CONST_214) result = "CONST_214"; else if (value == Sample.CONST_215) result = "CONST_215"; else if (value == Sample.CONST_216) result = "CONST_216"; else if (value == Sample.CONST_217) result = "CONST_217"; else if (value == Sample.CONST_218) result = "CONST_218"; else if (value == Sample.CONST_219) result = "CONST_219"; else if (value == Sample.CONST_220) result = "CONST_220"; else if (value == Sample.CONST_221) result = "CONST_221"; else if (value == Sample.CONST_222) result = "CONST_222"; else if (value == Sample.CONST_223) result = "CONST_223"; else if (value == Sample.CONST_224) result = "CONST_224"; else if (value == Sample.CONST_225) result = "CONST_225"; else result = "" ; return result; } static string SwitchSample(int value) { string result; switch (value) { case Sample.CONST_001: result = "CONST_001"; break; case Sample.CONST_002: result = "CONST_002"; break; case Sample.CONST_003: result = "CONST_003"; break; case Sample.CONST_004: result = "CONST_004"; break; case Sample.CONST_005: result = "CONST_005"; break; case Sample.CONST_006: result = "CONST_006"; break; case Sample.CONST_007: result = "CONST_007"; break; case Sample.CONST_008: result = "CONST_008"; break; case Sample.CONST_009: result = "CONST_009"; break; case Sample.CONST_010: result = "CONST_010"; break; case Sample.CONST_011: result = "CONST_011"; break; case Sample.CONST_012: result = "CONST_012"; break; case Sample.CONST_013: result = "CONST_013"; break; case Sample.CONST_014: result = "CONST_014"; break; case Sample.CONST_015: result = "CONST_015"; break; case Sample.CONST_016: result = "CONST_016"; break; case Sample.CONST_017: result = "CONST_017"; break; case Sample.CONST_018: result = "CONST_018"; break; case Sample.CONST_019: result = "CONST_019"; break; case Sample.CONST_020: result = "CONST_020"; break; case Sample.CONST_021: result = "CONST_021"; break; case Sample.CONST_022: result = "CONST_022"; break; case Sample.CONST_023: result = "CONST_023"; break; case Sample.CONST_024: result = "CONST_024"; break; case Sample.CONST_025: result = "CONST_025"; break; case Sample.CONST_026: result = "CONST_026"; break; case Sample.CONST_027: result = "CONST_027"; break; case Sample.CONST_028: result = "CONST_028"; break; case Sample.CONST_029: result = "CONST_029"; break; case Sample.CONST_030: result = "CONST_030"; break; case Sample.CONST_031: result = "CONST_031"; break; case Sample.CONST_032: result = "CONST_032"; break; case Sample.CONST_033: result = "CONST_033"; break; case Sample.CONST_034: result = "CONST_034"; break; case Sample.CONST_035: result = "CONST_035"; break; case Sample.CONST_036: result = "CONST_036"; break; case Sample.CONST_037: result = "CONST_037"; break; case Sample.CONST_038: result = "CONST_038"; break; case Sample.CONST_039: result = "CONST_039"; break; case Sample.CONST_040: result = "CONST_040"; break; case Sample.CONST_041: result = "CONST_041"; break; case Sample.CONST_042: result = "CONST_042"; break; case Sample.CONST_043: result = "CONST_043"; break; case Sample.CONST_044: result = "CONST_044"; break; case Sample.CONST_045: result = "CONST_045"; break; case Sample.CONST_046: result = "CONST_046"; break; case Sample.CONST_047: result = "CONST_047"; break; case Sample.CONST_048: result = "CONST_048"; break; case Sample.CONST_049: result = "CONST_049"; break; case Sample.CONST_050: result = "CONST_050"; break; case Sample.CONST_051: result = "CONST_051"; break; case Sample.CONST_052: result = "CONST_052"; break; case Sample.CONST_053: result = "CONST_053"; break; case Sample.CONST_054: result = "CONST_054"; break; case Sample.CONST_055: result = "CONST_055"; break; case Sample.CONST_056: result = "CONST_056"; break; case Sample.CONST_057: result = "CONST_057"; break; case Sample.CONST_058: result = "CONST_058"; break; case Sample.CONST_059: result = "CONST_059"; break; case Sample.CONST_060: result = "CONST_060"; break; case Sample.CONST_061: result = "CONST_061"; break; case Sample.CONST_062: result = "CONST_062"; break; case Sample.CONST_063: result = "CONST_063"; break; case Sample.CONST_064: result = "CONST_064"; break; case Sample.CONST_065: result = "CONST_065"; break; case Sample.CONST_066: result = "CONST_066"; break; case Sample.CONST_067: result = "CONST_067"; break; case Sample.CONST_068: result = "CONST_068"; break; case Sample.CONST_069: result = "CONST_069"; break; case Sample.CONST_070: result = "CONST_070"; break; case Sample.CONST_071: result = "CONST_071"; break; case Sample.CONST_072: result = "CONST_072"; break; case Sample.CONST_073: result = "CONST_073"; break; case Sample.CONST_074: result = "CONST_074"; break; case Sample.CONST_075: result = "CONST_075"; break; case Sample.CONST_076: result = "CONST_076"; break; case Sample.CONST_077: result = "CONST_077"; break; case Sample.CONST_078: result = "CONST_078"; break; case Sample.CONST_079: result = "CONST_079"; break; case Sample.CONST_080: result = "CONST_080"; break; case Sample.CONST_081: result = "CONST_081"; break; case Sample.CONST_082: result = "CONST_082"; break; case Sample.CONST_083: result = "CONST_083"; break; case Sample.CONST_084: result = "CONST_084"; break; case Sample.CONST_085: result = "CONST_085"; break; case Sample.CONST_086: result = "CONST_086"; break; case Sample.CONST_087: result = "CONST_087"; break; case Sample.CONST_088: result = "CONST_088"; break; case Sample.CONST_089: result = "CONST_089"; break; case Sample.CONST_090: result = "CONST_090"; break; case Sample.CONST_091: result = "CONST_091"; break; case Sample.CONST_092: result = "CONST_092"; break; case Sample.CONST_093: result = "CONST_093"; break; case Sample.CONST_094: result = "CONST_094"; break; case Sample.CONST_095: result = "CONST_095"; break; case Sample.CONST_096: result = "CONST_096"; break; case Sample.CONST_097: result = "CONST_097"; break; case Sample.CONST_098: result = "CONST_098"; break; case Sample.CONST_099: result = "CONST_099"; break; case Sample.CONST_100: result = "CONST_100"; break; case Sample.CONST_101: result = "CONST_101"; break; case Sample.CONST_102: result = "CONST_102"; break; case Sample.CONST_103: result = "CONST_103"; break; case Sample.CONST_104: result = "CONST_104"; break; case Sample.CONST_105: result = "CONST_105"; break; case Sample.CONST_106: result = "CONST_106"; break; case Sample.CONST_107: result = "CONST_107"; break; case Sample.CONST_108: result = "CONST_108"; break; case Sample.CONST_109: result = "CONST_109"; break; case Sample.CONST_110: result = "CONST_110"; break; case Sample.CONST_111: result = "CONST_111"; break; case Sample.CONST_112: result = "CONST_112"; break; case Sample.CONST_113: result = "CONST_113"; break; case Sample.CONST_114: result = "CONST_114"; break; case Sample.CONST_115: result = "CONST_115"; break; case Sample.CONST_116: result = "CONST_116"; break; case Sample.CONST_117: result = "CONST_117"; break; case Sample.CONST_118: result = "CONST_118"; break; case Sample.CONST_119: result = "CONST_119"; break; case Sample.CONST_120: result = "CONST_120"; break; case Sample.CONST_121: result = "CONST_121"; break; case Sample.CONST_122: result = "CONST_122"; break; case Sample.CONST_123: result = "CONST_123"; break; case Sample.CONST_124: result = "CONST_124"; break; case Sample.CONST_125: result = "CONST_125"; break; case Sample.CONST_126: result = "CONST_126"; break; case Sample.CONST_127: result = "CONST_127"; break; case Sample.CONST_128: result = "CONST_128"; break; case Sample.CONST_129: result = "CONST_129"; break; case Sample.CONST_130: result = "CONST_130"; break; case Sample.CONST_131: result = "CONST_131"; break; case Sample.CONST_132: result = "CONST_132"; break; case Sample.CONST_133: result = "CONST_133"; break; case Sample.CONST_134: result = "CONST_134"; break; case Sample.CONST_135: result = "CONST_135"; break; case Sample.CONST_136: result = "CONST_136"; break; case Sample.CONST_137: result = "CONST_137"; break; case Sample.CONST_138: result = "CONST_138"; break; case Sample.CONST_139: result = "CONST_139"; break; case Sample.CONST_140: result = "CONST_140"; break; case Sample.CONST_141: result = "CONST_141"; break; case Sample.CONST_142: result = "CONST_142"; break; case Sample.CONST_143: result = "CONST_143"; break; case Sample.CONST_144: result = "CONST_144"; break; case Sample.CONST_145: result = "CONST_145"; break; case Sample.CONST_146: result = "CONST_146"; break; case Sample.CONST_147: result = "CONST_147"; break; case Sample.CONST_148: result = "CONST_148"; break; case Sample.CONST_149: result = "CONST_149"; break; case Sample.CONST_150: result = "CONST_150"; break; case Sample.CONST_151: result = "CONST_151"; break; case Sample.CONST_152: result = "CONST_152"; break; case Sample.CONST_153: result = "CONST_153"; break; case Sample.CONST_154: result = "CONST_154"; break; case Sample.CONST_155: result = "CONST_155"; break; case Sample.CONST_156: result = "CONST_156"; break; case Sample.CONST_157: result = "CONST_157"; break; case Sample.CONST_158: result = "CONST_158"; break; case Sample.CONST_159: result = "CONST_159"; break; case Sample.CONST_160: result = "CONST_160"; break; case Sample.CONST_161: result = "CONST_161"; break; case Sample.CONST_162: result = "CONST_162"; break; case Sample.CONST_163: result = "CONST_163"; break; case Sample.CONST_164: result = "CONST_164"; break; case Sample.CONST_165: result = "CONST_165"; break; case Sample.CONST_166: result = "CONST_166"; break; case Sample.CONST_167: result = "CONST_167"; break; case Sample.CONST_168: result = "CONST_168"; break; case Sample.CONST_169: result = "CONST_169"; break; case Sample.CONST_170: result = "CONST_170"; break; case Sample.CONST_171: result = "CONST_171"; break; case Sample.CONST_172: result = "CONST_172"; break; case Sample.CONST_173: result = "CONST_173"; break; case Sample.CONST_174: result = "CONST_174"; break; case Sample.CONST_175: result = "CONST_175"; break; case Sample.CONST_176: result = "CONST_176"; break; case Sample.CONST_177: result = "CONST_177"; break; case Sample.CONST_178: result = "CONST_178"; break; case Sample.CONST_179: result = "CONST_179"; break; case Sample.CONST_180: result = "CONST_180"; break; case Sample.CONST_181: result = "CONST_181"; break; case Sample.CONST_182: result = "CONST_182"; break; case Sample.CONST_183: result = "CONST_183"; break; case Sample.CONST_184: result = "CONST_184"; break; case Sample.CONST_185: result = "CONST_185"; break; case Sample.CONST_186: result = "CONST_186"; break; case Sample.CONST_187: result = "CONST_187"; break; case Sample.CONST_188: result = "CONST_188"; break; case Sample.CONST_189: result = "CONST_189"; break; case Sample.CONST_190: result = "CONST_190"; break; case Sample.CONST_191: result = "CONST_191"; break; case Sample.CONST_192: result = "CONST_192"; break; case Sample.CONST_193: result = "CONST_193"; break; case Sample.CONST_194: result = "CONST_194"; break; case Sample.CONST_195: result = "CONST_195"; break; case Sample.CONST_196: result = "CONST_196"; break; case Sample.CONST_197: result = "CONST_197"; break; case Sample.CONST_198: result = "CONST_198"; break; case Sample.CONST_199: result = "CONST_199"; break; case Sample.CONST_200: result = "CONST_200"; break; case Sample.CONST_201: result = "CONST_201"; break; case Sample.CONST_202: result = "CONST_202"; break; case Sample.CONST_203: result = "CONST_203"; break; case Sample.CONST_204: result = "CONST_204"; break; case Sample.CONST_205: result = "CONST_205"; break; case Sample.CONST_206: result = "CONST_206"; break; case Sample.CONST_207: result = "CONST_207"; break; case Sample.CONST_208: result = "CONST_208"; break; case Sample.CONST_209: result = "CONST_209"; break; case Sample.CONST_210: result = "CONST_210"; break; case Sample.CONST_211: result = "CONST_211"; break; case Sample.CONST_212: result = "CONST_212"; break; case Sample.CONST_213: result = "CONST_213"; break; case Sample.CONST_214: result = "CONST_214"; break; case Sample.CONST_215: result = "CONST_215"; break; case Sample.CONST_216: result = "CONST_216"; break; case Sample.CONST_217: result = "CONST_217"; break; case Sample.CONST_218: result = "CONST_218"; break; case Sample.CONST_219: result = "CONST_219"; break; case Sample.CONST_220: result = "CONST_220"; break; case Sample.CONST_221: result = "CONST_221"; break; case Sample.CONST_222: result = "CONST_222"; break; case Sample.CONST_223: result = "CONST_223"; break; case Sample.CONST_224: result = "CONST_224"; break; case Sample.CONST_225: result = "CONST_225"; break; default : result = "" ; break; } return result; } static class ConstantsTable { static Dictionary<int, string> constantsDictionary = new Dictionary<int, string>(); static ConstantsTable() { var fields = typeof(Sample) .GetFields(BindingFlags.Public | BindingFlags.Static); fields.Where(field => field.IsLiteral && field.FieldType.Equals(typeof(int))) .ToList() .ForEach(field => constantsDictionary.Add((int)field.GetValue(null), field.Name)); } public static string GetConstantName(int value) { string name; return constantsDictionary.TryGetValue(value, out name) ? name : ""; } } static string ReflectionAndDictionarySample(int value) { return ConstantsTable.GetConstantName(value); } static void Test(Func<int, string> sampleFunction, string sampleName) { const long times = 100000L; string result; var time = PerformanceTester.Test( () => Enumerable .Range(1, 225) .ToList() .ForEach(number => result = sampleFunction(number)), times ); Console.WriteLine("{0}の場合\t: {1:F3}/{2}sec.", sampleName, time, times); } static void Main() { Test(IfSample , "if" ); Test(SwitchSample , "switch" ); Test(ReflectionAndDictionarySample, "ReflectionとDictionary"); } }
実行してみると、次のようになった。
ifの場合 : 2.575/100000sec. switchの場合 : 0.864/100000sec. ReflectionとDictionaryの場合 : 1.152/100000sec.
現状、比較が 200 以上も続く上記のようなケースでは、if と else で書いた方が遅いようだ。
アトラシアン エバンジェリスト長沢 智治さん、関西の Microsoft MVP 3名が来福! 参加者募集中!
『Hokuriku.NET Vol.15』 | |
---|---|
日時 | 2014年8月30日(土) 13時00分~17時30分 |
会場 | 福井市地域交流プラザ 研修室603 (AOSSA 6階) (福井県福井市手寄1-4-1) |
参加費 | 500円 (イベントのみ), 5,500円 (イベント+懇親会) |
内容/申込み | http://hokurikunet.doorkeeper.jp/events/13746 |
こみゅぷらす夏恒例の飲み食いしながらの勉強会イベント!
マイクロソフト荒井省三さんと過ごす COM の1日、こみゅぷらすメンバーのセッション等。参加者受付開始!
『こみゅぷらす Tech Aid 2014』 | |
---|---|
日時 | 2014年8月23日(土) 11時00分~18時00分 |
会場 | 新宿の居酒屋にて (参加申し込みをいただいた方にのみご連絡いたします) |
参加費 | 3,500円 (飲食代です) |
内容/申込み | http://comuplus.doorkeeper.jp/events/14019 |
※ 「[Event] こみゅぷらす Tech Aid 2014 (8/23)」の続き。
こみゅぷらす夏恒例の飲み食いしながらの勉強会イベントを開催。
『こみゅぷらす Tech Aid 2014』 | |
---|---|
日時 | 2014年8月23日(土) 11時00分~18時00分 |
会場 | 新宿の居酒屋 |
主催 | こみゅぷらす (COMU+) |
Webサイト | こみゅぷらす Tech Aid 2014 - こみゅぷらす (COMU+) | Doorkeeper |
|
※ 「[Event] Hokuriku.NET Vol.15 in FUKUI」の続き。
Hokuriku.NET の15回目の勉強会がJR福井駅前で開催された。
アトラシアン エバンジェリストの長沢 智治 さんや関西から3名の皆さんがスピーカーとしていらした。
『Hokuriku.NET Vol.15』 | |
---|---|
日時 | 2014年8月30日(土) 13時00分~17時30分 |
会場 | 福井市地域交流プラザ 研修室603 (AOSSA 6階) (福井県福井市手寄1-4-1) |
主催 | Hokuriku.NET |
Webサイト | Hokuriku.NET vol.15 - Hokuriku.NET | Doorkeeper |
「僕とハッカソン」 森理 麟 さん (@moririring) Microsoft MVP for Visual C# |
「Introducing Windows Runtime」 遥 佐保 さん Microsoft MVP for Windows Platform Development |
「酒の肴はC# vNext」 鈴木 孝明 さん Microsoft MVP for Visual C#、小島 富治雄 さん Microsoft MVP for Visual C# |
「JavascriptのPromiseのイケてないところ」 丸山和秀 さん
|
「Friendly の紹介」 鈴木 孝明 さん
|
※ 「[Event] Hokuriku.NET Vol.15 in FUKUI 開催」の続き。
『Hokuriku.NET vol.15』2014-08-30(土) の「酒の肴はC# vNext」で発表した資料を公開。
※ [C++] 『ラムダ式でステップアップ! C++のプログラムから汎用的なアルゴリズムを切り出し利用してみよう』 - CodeZine の続き。
C# によるプログラミングの記事を投稿。
「本稿では、C#のプログラムをリファクタリングして、汎用的なアルゴリズムを切り出し、利用する流れをステップバイステップで解説します。また、C# 3.0で採用されたラムダ式がそれを利用するのに、いかに便利なのか、どういう仕組みなのか、を紹介します。それにより、LINQ(Language INtegrated Query:統合言語クエリ)についての理解も深めていただけると思います。」
『MVP ComCamp -2nd Round- Nov 17 - Nov 21, 2014 が開催されている。
初日に開発者向けセッションを担当 (トラック 2 - Day 1 (2014/11/17): Learn フェイズ)。
発表した資料を公開。
「C# Advent Calendar 2014」の12日目の記事。
以前、「[C#][式木][LINQ] Hokuriku.NET C# 勉強会『C# 式木』(2014-10-26、金沢) のスライド公開」で、IQueryable な LINQ について解説した。
本記事では、その中の IQueryable なサンプルを補足する。
IQueryable な LINQ の中はどのようになっているのだろうか。
試しに少し実装してみることで、LINQ について理解を深めよう。
「[C#][ラムダ式][LINQ][式木] 匿名メソッドとラムダ式の違い」で紹介したように、匿名メソッドは delegate としてしか使えないが、ラムダ式は delegate としても式木としても使うことができる。
※ [C#][ラムダ式][式木] Expression として扱えるラムダ式と扱えないラムダ式」で紹介したように、ラムダ式であれば必ず式木として使うことができるわけではない。
※ クエリ構文は、「式木として扱えるラムダ式」の糖衣構文。つまり、式木を扱うことになる。
参考: LINQ でのクエリ構文とメソッド構文 (C#) - MSDN
LINQ の中には、次の二つの種類のライブラリがある。
LINQ to Objects などは前者で処理され、LINQ to SQL、LINQ to Entities などは後者だ。
今回は、IQueryable な Twitter のライムライン クラスを作ろうとしてみる。
先ずは IQueryable なクラス QueryableTweets。
※ IQueryable なだけでは OrderBy の対象となることができないので、ここでは IQueryable からの派生で OrderBy 可能な IOrderedQueryable を用いることにする。
// QueryableTweets.cs using System.Linq; // IOrderedQueryable<string> な QueryableTweets // まだ IOrderedQueryable インタフェイスを実装してないのでコンパイル エラー public class QueryableTweets<TElement> : IOrderedQueryable<TElement> {}
ここから、Visual Studio でインタフェイスの実装を行うと次のようになる。
// QueryableTweets.cs using System; using System.Collections; using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; // IOrderedQueryable<string> な QueryableTweets // Visual Studio でインタフェイスを実装した直後 public class QueryableTweets<TElement> : IOrderedQueryable<TElement> { public Type ElementType { get { throw new NotImplementedException(); } } public Expression Expression { get { throw new NotImplementedException(); } } public IQueryProvider Provider { get { throw new NotImplementedException(); } } public IEnumerator<TElement> GetEnumerator() { throw new NotImplementedException(); } IEnumerator IEnumerable.GetEnumerator() { throw new NotImplementedException(); } }
IQueryable は IEumerable から派生している。そのため IEumerable のメンバーである GetEnumerator() を実装する必要がある。
その他に、ElementType、Expression、Provider というプロパティを実装しなければならない。
実装を進めていこう。このクラスの実装はそれほど大変ではない。
Provider プロパティのために IQueryProvider インタフェイスを持つクラスを用意する必要があるが、ここでは、それを仮に TwitterQueryProvider クラスとしておこう。 TwitterQueryProvider クラスは後述する。
// QueryableTweets.cs using System; using System.Collections; using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; // IOrderedQueryable<string> な QueryableTweets public class QueryableTweets<TElement> : IOrderedQueryable<TElement> { public Type ElementType { get { return typeof(TElement); } } public Expression Expression { get; set; } public IQueryProvider Provider { get; set; } public QueryableTweets() { Provider = new TwitterQueryProvider(); // IQueryProvider インタフェイスを実装したクラス。後述。 Expression = Expression.Constant(this); } public IEnumerator<TElement> GetEnumerator() { return ((IEnumerable<TElement>)Provider.Execute(Expression)).GetEnumerator(); } IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } }
続いて、上記 QueryableTweets で使うための、IQueryProvider なものの実装だ。
これは、LINQ プロバイダーと呼ばれるもので、式木としてのクエリーを解釈する。
こちらの実装は大変だ。
クラス名を TwitterQueryProvider として、IQueryProvider を実装していこう。
// TwitterQueryProvider.cs using System.Linq; // LINQ プロバイダーの実験用 // まだ IQueryProvider インタフェイスを実装してないのでコンパイル エラー public class TwitterQueryProvider : IQueryProvider {}
ここから、Visual Studio でインタフェイスの実装を行うと次のようになる。
// TwitterQueryProvider.cs using System; using System.Linq; using System.Linq.Expressions; // LINQ プロバイダーの実験用 // Visual Studio でインタフェイスを実装した直後 public class TwitterQueryProvider : IQueryProvider { public IQueryable CreateQuery(Expression expression) { throw new NotImplementedException(); } public IQueryable<TElement> CreateQuery<TElement>(Expression expression) { throw new NotImplementedException(); } public object Execute(Expression expression) { throw new NotImplementedException(); } public TResult Execute<TResult>(Expression expression) { throw new NotImplementedException(); } }
少し実装を進めてみる。
// TwitterQueryProvider.cs using System; using System.Linq; using System.Linq.Expressions; // LINQ プロバイダーの実験用 public class TwitterQueryProvider : IQueryProvider { IQueryable IQueryProvider.CreateQuery(Expression expression) { return null; } public IQueryable<TElement> CreateQuery<TElement>(Expression expression) { return new QueryableTweets<TElement> { Provider = this, Expression = expression }; } public TResult Execute<TResult>(Expression expression) { return default(TResult); } public object Execute(Expression expression) { // ここで式木を解釈して、コレクションを作って返す return null; // とりあえずは仮に null を返すだけにしておく } }
この中で、ポイントとなるのは Execute メソッドだ。
この Execute メソッドには、式木が渡ってくる。この式木を解釈してやって、そこからコレクションとしての結果を返してやれば良い。
ここは後で実装することにして、とりあえずは null を返すだけにしておく。
LINQ プロバイダーの Execute メソッドでの式木を解釈だが、それには、ExpressionVisitor というクラスが使える。
ExpressionVisitor から派生することで、Visitor パターンによる解析が可能となる。
参考: ExpressionVisitor クラス - MSDN
LINQ のための式木をきちんと解釈するのは、かなり大変なことだ。
ここでは、ごく一部の構文にだけ注目して、そこのみに対応することにする。
取り敢えずの最低限のサンプル コード、Where(text => text.Contains("C#")) の形にのみ対応してみる。
尚、この中では、Twitter のタイムラインを取得する TwitterTimeline クラスを使っているが、 TwitterTimeline クラスは後述する。
// TwitterExpressionVisitor.cs using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; // Visitor パターンで式木を解析して検索用文字列を取り出し、それを使って Twitter のタイムラインを取得する public class TwitterExpressionVisitor : ExpressionVisitor { public IEnumerable<string> Statuses { get; private set; } // 取り敢えずの最低限のサンプル コード // Where(text => text.Contains("C#")) の形にのみ対応してみる protected override Expression VisitMethodCall(MethodCallExpression expression) { // もし Where メソッドを呼ぶ式だったら if (expression.Method.Name == "Where") { // Where メソッドの第二引数であるラムダ式を取り出す var lambdaExpression = (LambdaExpression)((UnaryExpression)(expression.Arguments[1])).Operand; // そのラムダ式の Body 部を取り出す var bodyExpression = lambdaExpression.Body as MethodCallExpression; // もし Contains メソッドを呼ぶ式で if (bodyExpression != null && bodyExpression.Method.Name == "Contains") { // その引数が定数式だったら var constantExpression = bodyExpression.Arguments[0] as ConstantExpression; if (constantExpression != null) { // その定数の値を検索文字列とし var searchText = constantExpression.Value as string; if (searchText != null) // TwitterTimeline クラス (後述) を使って、タイムラインからその検索文字列にあたる Status を取得しておく Statuses = new TwitterTimeline().Filter(searchText).Select(status => status.Text); } } } return base.VisitMethodCall(expression); } }
次に、Twitter のタイムラインを取得するためのダミー クラス TwitterTimeline を用意する。
実際に Twitter のタイムラインを取得するコードを用意すれば良いわけだが、今回は説明の簡略化のために CoreTweet というライブラリとダミー コードを用いることにする。
CoreTweet は、Visual Studio から NuGet でインストールできる。
マイクロソフトが「C丼キャンペーン」というのを始めようとしているようだ。
手元に C丼のストラップがある。
プログラミング言語 C# (シーシャープ) の "#" の文字は実は、井桁 (number sign: 番号記号) であり、♯ (sharp、嬰記号) ではない、というのは有名だが、こちらは丼だ。
ところで、IT 技術者というのは、「それって美味しいの?」という台詞をよく口にする。
エンジニアリングでは、理論的なことはともかく、「それは実際にモノづくりの役に立つのか?」を追求する。 エンジニアは、「それは美味しいのか (実践的に役立つのか)? 」を気にするのだ。
「C丼」? それって美味しいのか?
実際に作って食べてみることにした。
「C丼」部分は色から考えて、ニンジンで作るのが良いだろう。
包丁と細かい作業が出来る刃物を用意。
できた。曲線部分や丼の井のところが難しいかと思ったが、割と簡単だった。
これを、出汁醤油で煮てみる。
煮物は冷めるときに味がしみ込む。
さて、ご飯を用意しよう。
件のストラップを再度確認しよう。
何やら解説が書いてある。
「XAML: ザーサイ&ナムル」? 「RICE: Rich Imagination Construction Engine」?
ちょっと何言ってるか分からないが、ともかく真似してみよう。
丼にご飯をよそう。
福井県産のコシヒカリだ。炊き立て。
そこに、ザーサイ&ナムルを半々にのせる。
ストラップではナムル部分がもっと緑だが、まあ良しとしよう。
その上に、「C丼」部分を飾れば完成だ!
さて食べよう。
これは!
そもそも「ダジャレ」ベースでデザインされた丼なので、全く期待していなかったが、うまいではないか。
ザーサイとナムルはご飯に合うし、一緒に食べてもうまい。
(ザーサイとナムルの味に思い切り依存するが)
完食!
「C丼」は美味しい。お試しあれ。
MVP Community Camp がアジア パシフィック地域で開催されています。
今年 (2015年) の 1 月 31 日は全国 8 箇所で、オフラインの勉強会が開催されます。
北陸は、今年は富山での開催です。
今回は、.NET と Java の合同勉強会となっており、 2つの部屋にて、それぞれのセッションが同時進行されます。
.NET 側が MVP Community Camp 2015 北陸会場となります。
同じ会場で、絶品寒ブリのしゃぶしゃぶなどを楽しめる懇親会も開催され、その後の宿泊も可能です。温泉もあります。
是非ご参加ください。
富山合同勉強会 .NET & Java - MVP Community Camp 2015 北陸会場 - | |
---|---|
会期 | 2015年1月31日(土) |
会場 | 富山県 呉羽ハイツ |
参加費 | 無料 |
主催 | Hokuriku.NET 北陸エンジニアグループ |
協力 | 日本マイクロソフト |
詳細/お申込み | 富山合同勉強会 .NET & Java - MVP Community Camp 2015 北陸会場 | connpass |
豪華講師陣などの詳しい内容が確認できる MVP Community Camp 2015 北陸会場の リーフレットがダウンロードできます (PDF): こちら
※ 「MVP Community Camp 2015 (2015年1月31日) ~北陸は富山でやります~」の続き。
1月31日に、「富山合同勉強会 .NET & Java - MVP Community Camp 2015 北陸会場 -」が開催されました。
資料を公開します。
富山県の旅館の 2 つの部屋で、それぞれ .NET と Java のセッションが行われました。
懇親会では、寒ブリのしゃぶしゃぶなどが楽しまれました。
富山合同勉強会 .NET & Java - MVP Community Camp 2015 北陸会場 - | |
---|---|
会期 | 2015年1月31日(土) |
会場 | 富山県 呉羽ハイツ |
主催 | Hokuriku.NET 北陸エンジニアグループ |
協力 | 日本マイクロソフト |
最大の開発者向けカンファレンスである Build が今年も開催されており (今年は 4.29-5.1 PDT サンフランシスコ)、様々な人に沢山の Web 記事等が書かれているのでまとめてみたい。
※ 随時更新。
Microsoft Virtual Academy (MVA) についてご紹介します。
Microsoft Virtual Academy (MVA) は Microsoft が提供するオンライン トレーニングです。
Microsoft Virtual Academy (MVA) は 5 回までは会員登録なしで、すぐに授業動画の視聴と資料のダウンロードが可能です。
さっそく授業を閲覧して、使い方を確認してみましょう。
それでは、多くの学習コースの中から、開発者向けの3つをご紹介します。
各コース名をクリックすることで、それぞれを受講することができます。
コース名 | 説明 | コース内容 |
---|---|---|
Visual Studio 2015 における統合開発環境の進化 | リリース間近な Visual Studio 2015 の最新の開発環境の説明。 |
|
Unity を使用した Windows 用の 2D および 3D ゲーム開発ジャンプ スタート | C# と Unity を使用した Windows 用の Unity ゲームを開発する方法。ビデオは英語だが、日本語字幕付き。 |
|
Microsoft Azure の基礎 | Microsoft Azure の最初のコース。 |
|
『C#実践開発手法』という本を監訳された長沢 智治さんにいただいた。 レビューしたい。
タイトル | C#実践開発手法 |
---|---|
サブタイトル等 | デザインパターンとSOLID原則によるアジャイルなコーディング (マイクロソフト公式解説書) |
著者・監訳・翻訳 | Gary McLean Hall (著), 長沢 智治 (監訳), クイープ (翻訳) |
出版社 | 日経BP社 |
発売日 | 2015年6月4日 |
本体価格 | ¥5,000+税 |
頁数 | 440頁 |
本の種類 | 単行本、Kindle |
原書 (英語) は次の通り。
タイトル | Adaptive Code via C# |
---|---|
サブタイトル | Agile coding with design patterns and SOLID principles |
著者 | Gary McLean Hall |
出版社 | Microsoft Press |
発売日 | 2014年10月19日 |
頁数 | 448頁 |
本の種類 | ペーパーバック、Kindle |
次のような内容となっている。
ソフトウェア開発は、常に追加・変更し続けるのが特徴だ。 一気に全体が出来上がる訳ではない。 最初の1行を書いてから、段々と機能が足され、また、改良されていく。 変更し続けるのが特徴なので、なるべく変化を受け入れやすい作り方をする方が良い。
本書には、そういう「Adaptive Code (適応力のあるコード)」を書くための原則とそれを実践するための方法としてベストプラクティスが書かれている。
ソフトウェア開発において、原則は重要だ。
C# 等のプログラミング言語の文法を覚えればそれだけでソフトウェアが書けるかというと、そうではない。 また、いつもいつも日進月歩の開発技術を一から習得しなおさないといけないかというと、それも違う。 経験があり優秀な技術者は、新しいプログラミング言語、新しい開発基盤でも実力を発揮するものだ。
そこには、個々の開発技術にそれほど依存せず、新しい技術が出てきたときにも使える知恵があるだろう。 つまり、プログラミング言語や技術のトレンド等の枠を超えて重要な、開発手法や原則、パターン、アンチパターン、ベストプラクティスというものがあるのだ。
それは、偉大な先人の知恵が集まったものだ。経験のあるプログラマーが長年掛かって身に付ける暗黙知のような技術を、先人が形式知として習得しやすくしてくれたのだ。 そうした知恵は広くソフトウェア開発に応用が効き、また、ドッグイヤーと言われるソフトウェア開発の技術革新の中でも古びない。 また、プログラマー同士がコミュニケーションするときの語彙としてもずっと重要なものだ。 プログラマーとしては是非とも知っておきたい。
「原則だのパターンだのは、実務では役に立たない」というのを聞いたことがある。 それは違うと思う。 パターンをどこにでも適用しさえすれば良い、ということではない。 サンプルコードそのものをコピー & ペーストして使え、という話でも断じてない。 原則やパターン等は、「守破離」でいう「守」だ。基礎なのだ。
確かに、単にそれらの開発手法や原則、パターン、アンチパターン、ベストプラクティスを並べただけでは、中々理解が難しい場合もある。 実務への応用が易しくない場合もあるだろう。抽象的な知識は、広く使えて古びないが、具体的な知識と違って、深く理解しないと現場で活かしづらい。
本書は、単なる原則論にとどまっておらず、豊富な C# のサンプルコードや Visual Studio 等のツールの使い方の例によって、具体的に実践方法をあげている。 ここが、とても重要なところだと思う。
過去にこうした知恵が書かれたプログラマーの必読書としては、次のようなものがあった:
本書『C#実践開発手法』は、C#プログラマーにとって、そうした本に並ぶものだと思う。 より新しい内容も含まれているし、何より Visual Studio と C# による実装レベルにまで具体的に噛み砕いている。 中堅のC#プログラマーに必読だろう。
『日経ソフトウェア 2015年8月号』で、【特集1】「最新Visual Studioで、Windowsアプリを作ろう」のPart 1-3を執筆しました。
『日経ソフトウェア 2015年8月号』 | |
---|---|
発売日 | 2015年6月24日 |
出版社 | 日経BP社 |
Amazon.co.jp | 雑誌 |
Kindle版 |
Visual Studio の最新版 Visual Studio 2015 のこの夏の登場を前に、無料で使える Visual Studio Community 2015 RC を使った Windows アプリケーションの作り方の記事を書きました。
Visual Studio を使ったことがない人でも分かるように書きましたが、Visual Studio 2015 の新しい機能も紹介していますので、既に Visual Studio をお使いの方もぜひどうぞ。
日経ソフトウエアのサイトで冒頭を読むことができます。
次のような内容です。
尚、Part 4 は日本マイクロソフトによる「Visual Studio 2015 の新機能」です。
日経ソフトウエア最新号、CD-ROMとマンガ冊子のW付録つきで本日発売! 特集1は最新のVisual... http://t.co/YCjznlNRYc
— 日経ソフトウエア (@nikkei_software) June 24, 2015
最新号のCD-ROM付録は「書籍PDF」と「レッスン動画」の豪華版です。収録した書籍は以下の5冊。いずれも過去に大好評を博した本誌付録ですが、電子書籍化されているものもあります。PDF形式なので、サンプルコードのコピペにも対応し... http://t.co/oM4ccrmI5J
— 日経ソフトウエア (@nikkei_software) June 25, 2015
最新号の特集1は、最新の「Visual Studio」を使ったWindowsアプリ作りに挑戦します。Windows 10のリリースを前に「Visual Studio... http://t.co/WSGwb0saBs
— 日経ソフトウエア (@nikkei_software) June 26, 2015
特集はVisual Studioの使い方。高機能IDE怖いとか言ってる人にはお勧め記事かも?w >今月の日経ソフトウェア
— やまねこ@楢ノ木技研 (@felis_silv) June 26, 2015
今月の日経ソフトウェアから。 「悪い開発ツールは、それを使うことに振り回される」 いいことおっしゃる
pic.twitter.com/yjWO1HOjR8
— HAJIME Fukuna / 福名 一 (@f97one)
June 26, 2015
Microsoft MVP Award を再受賞しました。11年目になります。
1月から、Visual C#, Visual Basic, Visual F# が .NET に統合されたため、今回は .NET という技術専門分野での受賞になりました。
いくつかの IT 系の大企業が、このような表彰制度を用意して、技術者を (かなり本気で) 褒めてくださいます。 大人になると、中々褒められる機会がないので、こうして褒められるとかなり嬉しいものです。 ありがたいことです。
子供はもちろんのこと、大人であっても褒める、ということの大切さを改めて感じます。
そして、お世話になっている皆様に感謝です。ありがとうございます。
※ もし、執筆や技術系セッションなどご要望があれば、よろしくお願いします (こちら等へ)。
※ Visual Studio / Visual Studio Code Advent Calendar の12月10日の記事。
Microsoft の開発者向けテキスト エディターである Visual Studio Code には、インテリセンスやデバッグ機能などの Visual Studio の素晴らしい機能があり、Windows 以外にも Linux や OS X で使うことができる。オープンソースだ。
豊富な拡張機能も魅力だ。これにより、様々な機能を使うことができる。また、C# や JavaScript のみならず、Go、D Language、Haskell 等の沢山のプログラミング言語にも対応できる。
どんな拡張機能があるかは、次のサイトで見ることができる。
この記事では、Visual Studio Code の拡張機能の一つ、"Twitter Client" を紹介したい。
この拡張機能を使うことによって、テキスト エディターから出ることなく、Twitter のタイムラインを見たり、呟いたりすることができる。
実際にインストールして使ってみよう。
以下で、その手順を示したい。
Visual Studio Code (現在 Version 0.10.3) は、次のサイトからダウンロードしてインストールすることができる。
インストールが完了したら起動してみよう。
次に、Visual Studio Code に Twitter Client 拡張機能をインストールする。
詳しい内容は、次のページで見ることができる (英語)。
Visual Studio Code で F1キーを押すと、上にコマンドを入力するテキスト ボックスが現れる。
ここに、"ext install" と入力し、Enter キーを押す。
暫く待つと、拡張機能がリストアップされてくる (インターネット接続が必要)。
ここで、"ext install " の後ろに "twitter" と入力する。"Twitter Client" が表示されるので、右側の雲のアイコンをクリックしてインストールする。
インストールが終わると、"Restart Now" というボタンが表示される。クリックして Visual Studio Code を再起動しよう。
Twitter のクライアント アプリを作るためには、Twitter Developer Account によって Twitter App の作成を行う必要がある。
その手順は次のページで見ることができる (英語)。
"Twitter Client" には、このステップの為のウィザードが用意されている。見てみよう。
再び F1 キーを押して、コマンドとして "Twi Wizard" と入力する。
すると、"Twitter Client" のウィザードが起動し、Twitter App をセットアップする手順を教えてくれる。 "Continue" をクリックしよう。
すると、"https://apps.twitter.com" で Twitter App を作成するか訊いてくる。 "Continue" をクリックする。
Web ブラウザーで、"https://apps.twitter.com" が開かれる。 ここで Twitter App の作成を行う。Twitter にログインしよう (Twitter アカウントが必要)。
"Create New App" をクリックする。
Visual Studio Code 側では、ウィザードを進めていこう。
すると、Web ブラウザー側では、Twitter App を作成するための必要事項の入力を求められる。 入力しよう。
Twitter App が作成されたら、"Access level" が "Read & write" になっているのを確認しておこう (もし違っていたら、"modify app permissions" から変更しておく)。
次に、"Keys and Access Tokens" のタブをクリックして切り替える。
ここに表示されている "Consumer Key (API Key)" と "Consumer Secret (API Secret)" は、後で使うのでメモしておく。
そして、下部の "Create my access token" をクリックしよう。
"Access token" が表示される。 "Access Token" と "Access Token Secret" をメモっておこう。
メモっておいたキーやトークンは、Visual Studio Code で入力する。
Visual Studio Code で、メニューから "File" - "Preferences" - "User Settings" を選ぶ。
"settings.json" というファイルが開く。 右側がユーザー用の設定ファイルだ。 次のように入力する。
"twitter.consumerkey": "xxxx", // Consumer Key (API Key)
"twitter.consumersecret": "xxxx", // Consumer Secret (API Secret)
"twitter.accesstokenkey": "xxxx", // Access Token
"twitter.accesstokensecret": "xxxx" // Access Token Secret
以上で準備は完了だ。 使ってみよう。
Visual Studio Code の下部に "Twitter" と書かれたボタンが表示されている。
クリックしてみよう。
すると、上の方に Twitter 用のコマンドが表示される。
例えば、"Home" をクリックしてみると、自分のタイムラインが表示される筈だ。
"User" を選んで自分の呟きだけに切り替え、"Post" を使って呟いてみると次のような感じだ。
今回は、Visual Studio Code の拡張機能の一つ Twitter Client を試した。
Visual Studio Code には、他にも豊富な拡張機能がある。是非試してほしい。
※ C# Advent Calendar 2015 の12月19日の記事。
C 等を使用している場合と異なり、C# では、それがメモリ上でどのような姿をしているのかを意識する機会は少なめだ。
C の場合、型とメモリ上のビットの並びを意識したプログラミングをする機会が多い。ビット演算も比較的良く使われる。 それに比べると、C# や Java などの言語だけを使っている場合は、そうした機会は割と少ない。
しかし、C# 等であっても、やはりそれぞれの型がメモリ上でどのようにして値を保持しているかを知っていることが有効な場合がある。 例えば、double の演算では誤差が生じることが多いが、double の内部表現を知ることで、その理由も腑に落ちやすいだろう。
本記事では、敢えて C# の double (System.Double) の内部の表現に焦点を当ててみたい。 後ろの方では、double の内部を見るクラスなども紹介する。
※ C# 6.0 を使用。
C# で double や float のような浮動小数点数型を扱う場合には、誤差に注意する必要がある。
次の例では、↑ボタンを押す度に数値に 0.1 を足し、↓ボタンを押す度に数値から 0.1 を引いているのだが、何度も↑ボタンや↓ボタンを押した結果、誤差が生じている。
ちなみに、ソース コードは次のようなものだ。
using System.Windows;
using System.Windows.Controls;
namespace 浮動小数点数サンプル.WPF
{
public partial class UpDown : UserControl
{
const double d = 0.1;
public static readonly DependencyProperty ValueProperty = DependencyProperty.Register("Value", typeof(double), typeof(UpDown), new PropertyMetadata(0.0));
public double Value {
get { return (double)GetValue(ValueProperty); }
set { SetValue(ValueProperty, value); }
}
public UpDown()
{ InitializeComponent(); }
void OnUpButtonClick (object sender, RoutedEventArgs e) => Value += d;
void OnDownButtonClick(object sender, RoutedEventArgs e) => Value -= d;
}
}
<UserControl x:Class="浮動小数点数サンプル.WPF.UpDown"
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"
xmlns:local="clr-namespace:浮動小数点数サンプル.WPF"
mc:Ignorable="d"
d:DesignHeight="80" d:DesignWidth="200">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<RepeatButton Content="↑" Click="OnUpButtonClick"/>
<RepeatButton Grid.Row="1" Content="↓" Click="OnDownButtonClick"/>
<TextBlock Grid.Column="1" Grid.RowSpan="2" Text="{Binding Value, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:UpDown}}}" HorizontalAlignment="Right" VerticalAlignment="Center" TextWrapping="Wrap"/>
</Grid>
</UserControl>
問題点を絞るために、もっとシンプルなサンプルで見てみよう。
次の例では、コンソール アプリケーションで、0.1 を 20 回足した後に20回引いてみて、値の変化を表示している。
using System;
public static class Extension
{
// 回数繰り返す
public static void Repeat(this int times, Action action)
{
for (var counter = 0; counter < times; counter++)
action();
}
}
static class Program
{
static void WriteLine(double value) => Console.WriteLine($"{value:f16}");
static void Main()
{
// 0.0 から 0.1 ずつ20回増やしていき、0.1 ずつ20回減らしていく
const int times = 20;
const double d = 0.1;
double value = 0.0;
WriteLine(value);
times.Repeat(() => WriteLine(value += d));
times.Repeat(() => WriteLine(value -= d));
}
}
実行結果は次の通りだ。
最後の方で誤差が生じているのが分かる。
0.0000000000000000
0.1000000000000000
0.2000000000000000
0.3000000000000000
0.4000000000000000
0.5000000000000000
0.6000000000000000
0.7000000000000000
0.8000000000000000
0.9000000000000000
1.0000000000000000
1.1000000000000000
1.2000000000000000
1.3000000000000000
1.4000000000000000
1.5000000000000000
1.6000000000000000
1.7000000000000000
1.8000000000000000
1.9000000000000000
2.0000000000000000
1.9000000000000000
1.8000000000000000
1.7000000000000000
1.6000000000000000
1.5000000000000000
1.4000000000000000
1.3000000000000000
1.2000000000000000
1.1000000000000000
1.0000000000000000
0.9000000000000000
0.8000000000000000
0.7000000000000000
0.6000000000000000
0.5000000000000000
0.4000000000000000
0.3000000000000000
0.2000000000000000
0.0999999999999998
-0.0000000000000002
どうしてこのような誤差が生じるのだろうか?
「情報落ち」と呼ばれる現象がある。
絶対値が大きな値と小さな値を足したり引いたりした場合、小さい方の数値の末尾の分の情報が失われる、というものだ。
例えば、十進数で 1111111111.11111 と 1.11111111112345 を足す場合を考えてみよう。 double の有効桁は十進数で15桁程度なので、そうした場合を想定してみる。
有効桁が15桁の場合、1111111111.11111 + 1.11111111112345 = 1111111112.22222 となり、足し算の結果が小さい方の数値の末尾の分小さくなっている。 このような演算を繰り返すと、どんどんこの分の誤差が積もっていくことになる。
今回の場合だが、果たして 0.1 を足したり引いたりするだけで、このような誤差が生じるのだろうか。 0.1 の足し算や引き算であれば、有効桁が15桁もあれば誤差は生じないのではなかろうか。
その鍵は、double の内部表現にある。
double の内部表現は二進数である。
そこで、二進数としての小数を考えてみよう。
例えば、十進数で 2.5 の場合、これを二進数で表現すると次のようになる。
2.5(10) = 2 + 0.5 = 10(2) + 1×2-1 = 10.1(2)
そして、十進数で 0.1 の場合、二進数で表現すると、次のように循環小数となるのだ。
0.1(10) = 0.0 0011 0011 0011…(2)
double 内部の二進数としての有効桁は、52 + 1桁である。十進数としての 0.1 を入れたつもりでも、実際には循環小数の全ての桁を格納できる訳ではないので、有効桁を超えた分の情報はなくなってしまい、僅かに 0.1 と異なる値が格納されることになる。
また、この場合、有効桁いっぱいまで使って 0.1 に近い値を表現しているので、絶対値がより大きい値との演算で情報落ちが起こることがある。
では、double の内部表現をもっと詳しくみてみよう。
double は、次のような構造をしている。これは、「IEEE 754 倍精度 浮動小数点形式」と呼ばれる形式だ。
64ビットの中に二進数で、 符号部、指数部、仮数部の三つが格納されており、次のような値を表す。
部分 | ビット数 | 意味 | 補足 |
---|---|---|---|
符号部 | 1 ビット | 負の数かどうかを表す | 1: 負の数、0: 負の数ではない |
指数部 | 11 ビット | 指数 | 1023 足される |
仮数部 | 52 ビット | 仮数の絶対値 | 非 0 の場合 1.XXXXX… になるように指数部で調整され、最上位の 1 は省かれる |
例えば、2.5(10) の場合だと次のようになる:
2.5(10)
= 10.1(2)
= 10.1(2)×20
= 1.01(2)×21 (1.XXXXX… になるように調整)
そして、それぞれの部分は次のようになる。
部分 | 値 | 補足 |
---|---|---|
符号部 | 0 | 負の数でない |
指数部 | 10000000000 | 1(10)+1023(10)=1024(10)=10000000000(2) |
仮数部 | 0100000000000000000000000000000000000000000000000000 | 1.0100000000000000000000000000000000000000000000000000 の先頭の 1. を省いたもの |
従って、2.5(10) を double に格納したときの全体の内部表現は、|0|10000000000|0100000000000000000000000000000000000000000000000000| となる。
そして、0.1(10) の場合だと次のようになる:
0.1(10)
= 0.0 0011 0011 0011 …(2) (循環小数となる)
= 1.1 0011 0011 …(2) × 2-4 (1.XXXXX… になるように調整)
それぞれの部分は次のようになる。
部分 | 値 | 補足 |
---|---|---|
符号部 | 0 | 負の数でない |
指数部 | 01111111011 | -4(10)+1023(10)=1019(10)=01111111011(2) |
仮数部 | 1001100110011001100110011001100110011001100110011010 | 1.1 0011 0011 …(2) の整数部分の1を省略し、最後の桁の次の桁を「0捨1入」 |
つまり、0.1(10) を double に格納したときの全体の内部表現は、|0|01111111011|1001100110011001100110011001100110011001100110011010| となる。
尚、同じく浮動小数点数型である float に関しても同様だ。こちらは、4 バイト = 32 ビット (符号部 1 ビット、指数部 8 ビット、仮数部 23 ビット) となる。
最後に、C# で double の内部の値を見るクラスを作ってみよう。
先ず、double をバイト列にしたり、バイト列を十六進数表現に変換したり、十六進数表現を二進数表現に変換したりするメソッド群を用意した。
バイト列に変換するのには、System.BitConverter クラスが使用できる。 また、メモリ上での配置がリトル エンディアンになっている場合は、反転する必要があるが、これも System.BitConverter クラスで判定できる。
十六進数や二進数としての表現は、文字列として得られるようにした。
using System;
using System.Linq;
using System.Text;
public static class BinaryUtility
{
// バイト列をビッグ エンディアンに変換 (メモリ上での配置がリトル エンディアンになっている場合は反転)
public static byte[] ToBigEndian(this byte[] bytes) =>
BitConverter.IsLittleEndian ? bytes.Reverse().ToArray() : bytes;
// double をバイト列に変換 (ビッグ エンディアン)
public static byte[] ToBytes(this double value) =>
BitConverter.GetBytes(value).ToBigEndian();
// バイト列を十六進数に変換
public static string ToHexadecimalNumber(this byte[] byteArray) =>
BitConverter.ToString(byteArray).Replace("-", "");
// 十六進数一桁を二進数に変換
public static string HexadecimalNumberToBinaryNumber(char hexadecimalNumber) =>
Convert.ToString(Convert.ToInt32(hexadecimalNumber.ToString(), 16), 2).PadLeft(4, '0');
// 十六進数を二進数に変換
public static string HexadecimalNumberToBinaryNumber(string hexadecimalString) =>
hexadecimalString.Select(character => HexadecimalNumberToBinaryNumber(character))
.Aggregate(new StringBuilder(), (stringBuilder, text) => stringBuilder.Append(text))
.ToString();
// 二進数一桁を int に変換
public static int BinaryNumberToInteger(char binaryNumber) =>
binaryNumber == '0' ? 0 : 1;
}
次に、これを利用して、double 即ち IEEE 754 倍精度 浮動小数点形式から各部分の値を取り出すクラスを作ってみよう。
using System;
// IEEE 754 倍精度 浮動小数点形式 (8バイト)
// 符号部 1bit マイナス符号があるとき1
// 指数部 11bits 指数 + 2^10−1 (2^10−1 = 1024 - 1 = 1023)
// 仮数部 52bits 整数部分の1を省略
// ※ 仮数部は整数部分が1のみになるように調整
public class IEEE754Double
{
const int exponentBias = 1024 - 1; // 指数のバイアス: 2^10−1 = 1024 - 1 = 1023
readonly double value; // double としての値
readonly string hexadecimalNumber; // 十六進数
readonly string binaryNumber; // ニ進数
public IEEE754Double(double value)
{
this.value = value;
hexadecimalNumber = value.ToBytes().ToHexadecimalNumber();
binaryNumber = BinaryUtility.HexadecimalNumberToBinaryNumber(HexadecimalNumber);
}
// double としての値
public double Value => value;
// 十六進数
public string HexadecimalNumber => hexadecimalNumber;
// ニ進数
public string BinaryNumber => binaryNumber;
// 符号部の二進数 (マイナス符号があるかないか: 符号部 1bit)
public char SignBinaryNumber => binaryNumber[0];
// 指数部の二進数 (指数部 11bits)
public string ExponentBinaryNumber => binaryNumber.Substring(1, 11);
// 仮数部の二進数 (仮数部 52bits : 整数部分の1を省略した小数部分)
public string DigitsBinaryNumber => binaryNumber.Substring(1 + 11, 64 - 1 - 11);
// 符号 (マイナス符号があるかないか: 符号部 1bit から取得)
public bool Sign => SignBinaryNumber == '1';
// 指数 (「指数部 11bits: 指数 + 指数のバイアス」から取得)
public int Exponent => Convert.ToInt32(ExponentBinaryNumber, 2) - exponentBias;
// 仮数
public double Digits
{
get {
var wholeDigitsBinaryNumber = "1" + DigitsBinaryNumber; // 二進数
// 二進数を double に変換
var result = 0.0;
for (var index = wholeDigitsBinaryNumber.Length - 1; index >= 0; index--)
result = result / 2.0 + BinaryUtility.BinaryNumberToInteger(wholeDigitsBinaryNumber[index]);
return result;
}
}
public override string ToString() => $"{SignString}{Digits} * 2^{Exponent}";
// 符号文字列 (- または +)
string SignString => Sign ? "-" : "+";
}
これらを使って、2.5(10) と 0.1(10) の内部の値をみてみよう。
using System;
static class Program
{
static void WriteLine(double value)
{
var i3eDouble = new IEEE754Double(value);
Console.WriteLine($"{i3eDouble.Value:f16}, {i3eDouble.HexadecimalNumber}, {i3eDouble.BinaryNumber}, {i3eDouble.ToString()}");
}
static void Main()
{
WriteLine(2.5);
WriteLine(0.1);
}
}
実行結果は次のようになる。 先に考察したのと同じ結果だ。
2.5000000000000000, 4004000000000000, 0100000000000100000000000000000000000000000000000000000000000000, +1.25 * 2^1
0.1000000000000000, 3FB999999999999A, 0011111110111001100110011001100110011001100110011001100110011010, +1.6 * 2^-4
試しに、double.MaxValue、double.MinValue、double.PositiveInfinity、double.NegativeInfinity、double.NaN を出してみると次のようになった。
179769313486232000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000.0000000000000000, 7FEFFFFFFFFFFFFF, 0111111111101111111111111111111111111111111111111111111111111111, +2 * 2^1023
-179769313486232000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000.0000000000000000, FFEFFFFFFFFFFFFF, 1111111111101111111111111111111111111111111111111111111111111111, -2 * 2^1023
∞, 7FF0000000000000, 0111111111110000000000000000000000000000000000000000000000000000, +1 * 2^1024
-∞, FFF0000000000000, 1111111111110000000000000000000000000000000000000000000000000000, -1 * 2^1024
NaN, FFF8000000000000, 1111111111111000000000000000000000000000000000000000000000000000, -1.5 * 2^1024
double.PositiveInfinity、double.NegativeInfinity、double.NaN に関しては最後の部分は無用だが、それぞれの内部表現は確認できた。
では、始めの方で行った、0.1 を足したり引いたりする例について試してみよう。
using System;
public static class Extension
{
// 回数繰り返す
public static void Repeat(this int times, Action action)
{
for (var counter = 0; counter < times; counter++)
action();
}
}
static class Program
{
static void WriteLine(double value)
{
var i3eDouble = new IEEE754Double(value);
Console.WriteLine($"{i3eDouble.Value:f16}, {i3eDouble.HexadecimalNumber}, {i3eDouble.BinaryNumber}, {i3eDouble.ToString()}");
}
static void Main()
{
// 0.0 から 0.1 ずつ20回増やしていき、0.1 ずつ20回減らしていく
const int times = 20;
const double d = 0.1;
double value = 0.0;
WriteLine(value);
times.Repeat(() => WriteLine(value += d));
times.Repeat(() => WriteLine(value -= d));
}
}
実行結果は次のようになった。 二進数の演算で誤差が生じている箇所が確認できるだろうか。
0.0000000000000000, 0000000000000000, 0000000000000000000000000000000000000000000000000000000000000000, +1 * 2^-1023
0.1000000000000000, 3FB999999999999A, 0011111110111001100110011001100110011001100110011001100110011010, +1.6 * 2^-4
0.2000000000000000, 3FC999999999999A, 0011111111001001100110011001100110011001100110011001100110011010, +1.6 * 2^-3
0.3000000000000000, 3FD3333333333334, 0011111111010011001100110011001100110011001100110011001100110100, +1.2 * 2^-2
0.4000000000000000, 3FD999999999999A, 0011111111011001100110011001100110011001100110011001100110011010, +1.6 * 2^-2
0.5000000000000000, 3FE0000000000000, 0011111111100000000000000000000000000000000000000000000000000000, +1 * 2^-1
0.6000000000000000, 3FE3333333333333, 0011111111100011001100110011001100110011001100110011001100110011, +1.2 * 2^-1
0.7000000000000000, 3FE6666666666666, 0011111111100110011001100110011001100110011001100110011001100110, +1.4 * 2^-1
0.8000000000000000, 3FE9999999999999, 0011111111101001100110011001100110011001100110011001100110011001, +1.6 * 2^-1
0.9000000000000000, 3FECCCCCCCCCCCCC, 0011111111101100110011001100110011001100110011001100110011001100, +1.8 * 2^-1
1.0000000000000000, 3FEFFFFFFFFFFFFF, 0011111111101111111111111111111111111111111111111111111111111111, +2 * 2^-1
1.1000000000000000, 3FF1999999999999, 0011111111110001100110011001100110011001100110011001100110011001, +1.1 * 2^0
1.2000000000000000, 3FF3333333333333, 0011111111110011001100110011001100110011001100110011001100110011, +1.2 * 2^0
1.3000000000000000, 3FF4CCCCCCCCCCCD, 0011111111110100110011001100110011001100110011001100110011001101, +1.3 * 2^0
1.4000000000000000, 3FF6666666666667, 0011111111110110011001100110011001100110011001100110011001100111, +1.4 * 2^0
1.5000000000000000, 3FF8000000000001, 0011111111111000000000000000000000000000000000000000000000000001, +1.5 * 2^0
1.6000000000000000, 3FF999999999999B, 0011111111111001100110011001100110011001100110011001100110011011, +1.6 * 2^0
1.7000000000000000, 3FFB333333333335, 0011111111111011001100110011001100110011001100110011001100110101, +1.7 * 2^0
1.8000000000000000, 3FFCCCCCCCCCCCCF, 0011111111111100110011001100110011001100110011001100110011001111, +1.8 * 2^0
1.9000000000000000, 3FFE666666666669, 0011111111111110011001100110011001100110011001100110011001101001, +1.9 * 2^0
2.0000000000000000, 4000000000000001, 0100000000000000000000000000000000000000000000000000000000000001, +1 * 2^1
1.9000000000000000, 3FFE666666666668, 0011111111111110011001100110011001100110011001100110011001101000, +1.9 * 2^0
1.8000000000000000, 3FFCCCCCCCCCCCCE, 0011111111111100110011001100110011001100110011001100110011001110, +1.8 * 2^0
1.7000000000000000, 3FFB333333333334, 0011111111111011001100110011001100110011001100110011001100110100, +1.7 * 2^0
1.6000000000000000, 3FF999999999999A, 0011111111111001100110011001100110011001100110011001100110011010, +1.6 * 2^0
1.5000000000000000, 3FF8000000000000, 0011111111111000000000000000000000000000000000000000000000000000, +1.5 * 2^0
1.4000000000000000, 3FF6666666666666, 0011111111110110011001100110011001100110011001100110011001100110, +1.4 * 2^0
1.3000000000000000, 3FF4CCCCCCCCCCCC, 0011111111110100110011001100110011001100110011001100110011001100, +1.3 * 2^0
1.2000000000000000, 3FF3333333333332, 0011111111110011001100110011001100110011001100110011001100110010, +1.2 * 2^0
1.1000000000000000, 3FF1999999999998, 0011111111110001100110011001100110011001100110011001100110011000, +1.1 * 2^0
1.0000000000000000, 3FEFFFFFFFFFFFFD, 0011111111101111111111111111111111111111111111111111111111111101, +2 * 2^-1
0.9000000000000000, 3FECCCCCCCCCCCCA, 0011111111101100110011001100110011001100110011001100110011001010, +1.8 * 2^-1
0.8000000000000000, 3FE9999999999997, 0011111111101001100110011001100110011001100110011001100110010111, +1.6 * 2^-1
0.7000000000000000, 3FE6666666666664, 0011111111100110011001100110011001100110011001100110011001100100, +1.4 * 2^-1
0.6000000000000000, 3FE3333333333331, 0011111111100011001100110011001100110011001100110011001100110001, +1.2 * 2^-1
0.5000000000000000, 3FDFFFFFFFFFFFFC, 0011111111011111111111111111111111111111111111111111111111111100, +2 * 2^-2
0.4000000000000000, 3FD9999999999996, 0011111111011001100110011001100110011001100110011001100110010110, +1.6 * 2^-2
0.3000000000000000, 3FD3333333333330, 0011111111010011001100110011001100110011001100110011001100110000, +1.2 * 2^-2
0.2000000000000000, 3FC9999999999993, 0011111111001001100110011001100110011001100110011001100110010011, +1.6 * 2^-3
0.0999999999999998, 3FB999999999998C, 0011111110111001100110011001100110011001100110011001100110001100, +1.6 * 2^-4
-0.0000000000000002, BCAC000000000000, 1011110010101100000000000000000000000000000000000000000000000000, -1.75 * 2^-53
以上、double の内部表現について、C# を使って説明してみた。
この度、石川県金沢市で、毎年恒例のマイクロソフトの技術に関する勉強会 ComCamp を開催します。 是非ご参加ください。
Japan ComCamp 2016 powered by MVPs の北陸会場であるこのイベントでは、日本マイクロソフトの人気エバンジェリスト西脇 資哲さんにドローンとマイクロソフトのテクノロジーについて語っていただきます。
その他、恒例の北信越の C# 大好き MVP 3名による、やらせなしライブ コーディングなど盛り沢山です。
ぜひご参加ください! 学生の方も大歓迎です。
※ 前回の様子:
富山合同勉強会 .NET & Java ~ぶりしゃぶで休もう~ - MVP Community Camp 2015 北陸会場 -
「Hokuriku ComCamp 2016 powered by MVPs」 | |
---|---|
日時 | 2016/2/20(土) 11:00-18:10 (受付 10:30-、終了後に近くで懇親会) ※ 開始・終了時刻は前後する可能性があります。 |
会場 | ITビジネスプラザ武蔵 6F 交流室2 石川県金沢市武蔵町14番31号 |
主催 | Hokuriku.NET 福井情報技術者協会[FITEA] |
協力 | 日本マイクロソフト |
詳細/お申込み | http://hokurikucomcamp.connpass.com/event/23628/ |
※セッションの順番は変更される可能性があります。
時間 | セッション | スピーカー |
---|---|---|
11:00~11:10 | オープニング | |
11:10~12:00 |
「C# 大好き MVP による、C# ドキドキ・ライブコーディング!!」 北信越の Visual Studio and Development Technologies MVP 3名が C# の魅力について語ります。LT や コーディング ライブ。 |
石野 光仁 氏 Microsoft MVP for Visual Studio and Development Technologies コミュニティ: AILight 鈴木 孝明 氏 Microsoft MVP for Visual Studio and Development Technologies ブログ: xin9le note 小島 富治雄 氏 Microsoft MVP for Visual Studio and Development Technologies ブログ: プログラミング C# - 翔ソフトウェア (Sho's) |
13:00~13:40 |
「Windows 10 楽しい使い方」 Windows 10には、今までのWindowsと違う、新しい機能がたくさん盛り込まれました。Windows 10の新しい機能で楽しくパソコンを使う方法を紹介します。 |
さくしま たかえ 氏 Microsoft MVP for Windows Experience ブログ: 世の中は不思議なことだらけ Microsoft Online Services 検証の館 著書: Windows 10 完全制覇パーフェクト |
13:50~15:00 |
「エンジニアよ興味を持ち続けろ! ドローンだってITだ」 日本マイクロソフトのエバンジェリストなのになぜか興味をもってしまったドローン。 しかしやがて、Microsoftテクノロジーと深い関係に。 興味を持ち続けることの重要性を伝えつつ、ドローンの現状と利活用の世界、さらにはドローンと Microsoft テクノロジーについて解説します。 軽量ドローンは持ち込んで飛ばしますよ~。 |
西脇 資哲 氏 日本マイクロソフト株式会社 業務執行役員・エバンジェリスト |
15:10~15:50 |
「DataTableを徹底解剖しようぜ!」 DataTableクラスについて、色々といじったり語り合ったりとかどうでしょう? |
片桐 継 氏 サイト |
16:00~16:40 | 未定 (Windows Server 2016 Essentials 関連) |
澤田 賢也 氏 Microsoft MVP for Cloud and Datacenter Management ブログ: Windows Server Essentials を中心とした雑記 著書: 現場で役立つ Windows Server 2012 R2 Essentials 構築・運用ガイド |
16:50~17:30 | 「馴染みの .NET Framework で CMS を使ってみよう」 |
矢後 比呂加 氏 Microsoft MVP for Visual Studio and Development Technologies ブログ: miso_soup3 Blog |
17:40~18:10 | ライトニングトーク (LT) |
金子 雄一氏 リモートデスクトップ関連 |
18:30-21:00 | 懇親会 | こちらからお申込みください。 |
『富山合同勉強会 2016 ~ぶりしゃぶ会~』に参加してきた。
「Babylon.js + TypeScript で簡単 3D プログラミング」という題で、ライトニングトーク (LT) を行った。
『富山合同勉強会 2016』 | |
---|---|
開催日 | 2016/01/30 |
会場 | 富山大学 五福キャンパス |
詳細 | http://toyama-eng.connpass.com/event/24840/ |
※ 「C# 大好き MVP による、C# ドキドキ・ライブコーディング!!」でテトリス的なゲームを作った。(Hokuriku ComCamp 2016 - 2月20日)」に続きます。
石川県金沢市で、毎年恒例のマイクロソフトの技術に関する勉強会 ComCamp を開催しました。
53 名の方に参加していただきました。
Japan ComCamp 2016 powered by MVPs の北陸会場であるこのイベントでは、日本マイクロソフトのカリスマ エバンジェリスト西脇 資哲さんが会場でドローンを飛ばしながら、IT技術との関連を熱く語っていただいたり、恒例の北信越の C# 大好き MVP 3名による、C# ライブ コーディングなどおおいに盛り上がりました。
当日の盛り上がりは Twitter などで見ることができます。
セッションは、当日ストリーミング配信されました。後日 Channel 9 で公開される予定です。
「Hokuriku ComCamp 2016 powered by MVPs」 | |
---|---|
日時 | 2016/2/20(土) 11:00-18:10 (終了後懇親会) |
会場 |
ITビジネスプラザ武蔵 6F 交流室2 石川県金沢市武蔵町 |
主催 |
Hokuriku.NET 福井情報技術者協会[FITEA] |
協力 | 日本マイクロソフト |
詳細 | http://hokurikucomcamp.connpass.com/event/23628/ |
セッション | スピーカー | |
---|---|---|
オープニング | 小島 富治雄 氏 Microsoft MVP for Visual Studio and Development Technologies ブログ: プログラミング C# - 翔ソフトウェア (Sho's) |
|
「C# 大好き MVP による、C# ドキドキ・ライブコーディング!!」 北信越の Visual Studio and Development Technologies MVP 3名が C# の魅力について語ります。LT や コーディング ライブ。 |
石野 光仁 氏 Microsoft MVP for Visual Studio and Development Technologies コミュニティ: AILight 鈴木 孝明 氏 Microsoft MVP for Visual Studio and Development Technologies ブログ: xin9le note 小島 富治雄 氏 |
|
「Windows 10 楽しい使い方」 Windows 10 には、今までのWindowsと違う、新しい機能がたくさん盛り込まれました。Windows 10 の新しい機能で楽しくパソコンを使う方法を紹介します。 |
さくしま たかえ 氏 Microsoft MVP for Windows Experience ブログ: 世の中は不思議なことだらけ Microsoft Online Services 検証の館 著書: Windows 10 完全制覇パーフェクト |
|
「DataTableを徹底解剖しようぜ!」 DataTableクラスについて、色々といじったり語り合ったりとかどうでしょう? |
片桐 継 氏 サイト |
|
「エンジニアよ興味を持ち続けろ! ドローンだってITだ」 日本マイクロソフトのエバンジェリストなのになぜか興味をもってしまったドローン。 しかしやがて、Microsoftテクノロジーと深い関係に。 興味を持ち続けることの重要性を伝えつつ、ドローンの現状と利活用の世界、さらにはドローンと Microsoft テクノロジーについて解説します。 軽量ドローンは持ち込んで飛ばしますよ~。 |
西脇 資哲 氏 日本マイクロソフト株式会社 業務執行役員・エバンジェリスト |
|
「いまから始める Windows Server」 Windows Server を触ったことがない方や開発用のサーバーを構築する必要がある方、中小企業などでコンピューターやデータの管理を効果的に行いたい方などに最新の Windows Server を使って紹介をいたします。 |
澤田 賢也 氏 Microsoft MVP for Cloud and Datacenter Management ブログ: Windows Server Essentials を中心とした雑記 著書: 現場で役立つ Windows Server 2012 R2 Essentials 構築・運用ガイド |
|
「馴染みの .NET Framework で CMS を使ってみよう」 |
矢後 比呂加 氏 Microsoft MVP for Visual Studio and Development Technologies ブログ: miso_soup3 Blog |
|
ライトニングトーク (LT) |
金子 雄一氏 リモートデスクトップ関連 むろほしりょうた 氏 「『var禁止』禁止」 山P 氏 「AzureAutomationを使ってみた話」 |
|
クロージング | じゃんけん大会 | |
懇親会 |
※ 「[Event] 「Hokuriku ComCamp 2016 powered by MVPs」 (2016年2月20日)を開催しました」の続きです。
「Hokuriku ComCamp 2016 powered by MVPs」では、北信越の3人の Microsoft MVP (石野 光仁 さん、鈴木 孝明 さん、私) で毎年恒例/大好評の「C# 大好き MVP による、C# ドキドキ・ライブコーディング!!」というセッションを行いました。
セッションは、毎回石野 さんが考えてくださっています。 無茶ブリが楽しい素敵なセッションだと思います。
今回の石野さんからのお題は、「30分でテトリス ライクなゲームを作ろう!」というかなりチャレンジングなものでした。
ライブ コーディングは毎回ドキドキものです。
交代で喋り、自分以外が喋っているときにコーディングさせてもらえる、という進行でした。
私の作った部分について説明してみます。
私の分の資料を下記で公開しました (slideshare と Docs.com にそれぞれ)。
戦略やどのようにインチキ工夫したかを書いてみました。
全体の構成は次のようになっています。
FTetris.Model | ビューから分離したモデル部分の C#/.NET 版です。他の .NET のプログラムから参照されるクラス ライブラリ (DLL) です。 | |
---|---|---|
FTetris.WPF | WPF (Windows Presentation Foundation) で作ったゲームです。C# で書かれています。View と ViewModel からなっており、上記 Model を参照しています。つまり、WPF の標準的な設計方法である MVVM (Model - View - ViewModel) アーキテクチャーで作られています。 | |
FTetris.WinForm | Windows Forms 版です。C# で書かれており、GDI+ で描画を行っています。セッション中のライブで主に作った部分です。 | |
FTetris.Console | コンソール アプリケーション版です。C# で書かれています。色がついています。 | |
FTetris.Matrix | セッションでモデルを説明するためだけに作った、映画マトリックスをイメージした緑色の数字が降ってくる版です。 | |
FTetris.Win | セッションで「インチキ」と称した、Windows Forms のメイン フォームに WebBrowser を貼って下記 TypeScript 版の URL を渡しただけの 1 分で作れる版です。 | |
FTetris.TS | 上の「インチキ」をやるために作っておいた Web アプリケーション版です。 TypeScript で書かれています。 モデル部分は、上の FTetris.Model から移植しました。 Three.js から WebGL を使っています。 Windows 以外の OS でも動作します (スマートフォンやタブレットでも動作可能ですが、操作にはキーボードが必要です)。 |
上記資料にも書きましたが、モデルを (WPF や Windows Forms、コンソール等の) ビューから分離しています。
ゲーム本体のロジックをモデルとして書くことで、ビュー側の実装に依存しない形にし、各プラットフォームで使えるようにしました。
モデル部分の設計は次のようになりました。
このモデルは、先ず C# で実装し、それから TypeScript に移植しましたので、TypeScript 版もほぼ同じ設計です。
このモデルを使った例として、WPF 版の設計は次のようになりました。
MVVM (Model - View - ViewModel) になっています。
私の分の全体のソースコードは GitHub で公開しています。
TypeScript で書いた Web アプリケーション版は下のリンクから実際に遊ぶことができます。
くるくる回るようにしたので、通常のものよりだいぶ難しくなっています。
マウスのドラッグやホイールで視点 (カメラ位置) を変えられます。
ゲームのキー操作は、どの版も共通で、次のようになっています。
キー | 動作 |
---|---|
← | 左移動 |
→ | 右移動 |
↑ | 回転 (時計回り) |
↓ | 回転 (反時計回り) |
Space | 落下 |
Enter | 最初からプレイ |
鈴木さんと石野さんも、ご自身の分のゲームを公開されています。
三者三様で興味深い内容です。
鈴木さんの記事はこちらです。
彼の作った部分について、アプリケーションやソースコード、戦略や設計なども公開されています。
マイクロソフトの最大の開発者向けイベント「Build 2016」がサンフランシスコで開催中。
内容は、一部ライブで観ることができる。
1日目に関する記事が、既にあちこちに上がっている。
1日目のキーノートに関する記事も沢山。
1日目の発表内容は以下の通りだ。
Windows 10 で Linux のシェルのバイナリ―をそのまま動作できるようにするとのこと。
マイクロソフトは、最近 Windows 以外の OS との親和性をどんどんと高めており、その一環だろう。
クラウド上の「マシン ラーニング」機能の応用としての「人工知能」関連だ。
最近マイクロソフトや Google、Amazon などが取り組んでいることで話題になっている。
今回発表されたのは、Bot を作るためのフレームワーク。
Visual Studio "15" Preview と Visual Studio 2015 Update 2 RTM が公開された。
Visual Studio 2015 Update 2 は「プログラムと機能」から Visual Studio 2015 の「変更」を選んでインストールするのが簡単だ。
また、MSDN からは Update 2 込みの Visual Studio 2015 がダウンロードできるようになっている。
その他、以下のような発表があった。
Win32/.NETアプリを UWP (*1) アプリに変換するツール。
(*1) UWP とは: ユニバーサル Windows プラットフォームの略。Windows 10 を積んだPC や Phone、XBox One、HoloLens、Surface Hub などで動作可能。
Visual Studio で XBox One 用に UWP アプリを開発可能に。Visual Studio からリモートデバッグもできる。
開発者エディションを提供開始。
様々なアップデートがあったようだ。
※ 「Build 2016 1日目の発表内容まとめ」の続き。
2日目のキーノートに関する記事。
2日目の発表内容は以下の通りだ。
マイクロソフトが買収した Xamarin (C# を使って iOS や Android などのネイティブ アプリが作れるツール) が、Visual Studio に含まれることになった。
Visual Studio の Enterprise エディションや Professional エディションの他、無償版の Community エディションでも追加料金なしで使うことができるようになる。
Microsoft MVP Award を再受賞しました。12年目になります。
いくつになっても、こうして褒められるのは嬉しいものです。 マイクロソフトのような大企業が本気で褒めてくれる。ありがたいことです。
沢山の素晴らしい方々と出会える機会をいただいております。 お世話になっている皆様に感謝です。いつもありがとうございます。
※ Visual Studio Advent Calendar 2016 の12月16日の記事。
コーディング機能やデバッグやテストの機能が大幅パワーアップ! 次期バージョン「Visual Studio 2017 RC」 | CodeZine という記事を書かせていただき、その中で、「Visual Studio 2017 RC」の「特におすすめの新機能」としてライブ ユニット テストに触れた。 もう少し具体的にライブ ユニット テストを紹介してみたい。
題材として、『ソフトウェア技術者サミット in 福井 2016』 (7月16日) の「テスト駆動開発ライブ 〜C#ペアプログラミング実況中継」というセッションの中で、平鍋健児氏と C# と Visual Studio 2015 を使っておこなったテスト駆動開発 (Test-Diven Development: TDD) のデモンストレーションを用いることにする。
このデモンストレーションは、北野弘治氏と平鍋健児氏による「車窓からの TDD | オブジェクト倶楽部」 を C# で再現したものだ。 テストを書きながら、スタックのクラスを作成していく。 順を追ってやってみよう。
最初に、Visual Studio 2017 RC を起動し、テストするプロジェクトとテストされるプロジェクトを作る。
では、早速 Visual Studio 2017 の新機能であるライブ ユニット テストを開始しよう。 次のようにメニューから選ぶだけだ。
それでは、単体テスト プロジェクトに最初のテストを追加してみよう。 TDD では、テストされるコードよりテスト コードを先に書くことが多い。
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace Shos.Collections.Test
{
[TestClass]
public class Stackのテスト
{
[TestMethod]
public void 作ったら空()
{
var stack = new Stack<int>();
Assert.IsTrue(stack.IsEmpty);
}
}
}
クラス名を「Stackのテスト」とし、「作ったら空」というテストを追加した。 まだ Stack クラスは作っていないので、静的なエラー (コンパイル時エラー) になり、エラー箇所に赤い波線が表示される。
バルブ アイコンをクリックするか、エラー箇所にカーソルがある状態で 'Ctrl+.' をタイプし、クラス ライブラリ プロジェクトの方に Stack クラスを作成しよう。
OK ボタンを押すと、クラス ライブラリ プロジェクトに Stack クラスが生成される。
namespace Shos.Collections
{
public class Stack<T>
{
public Stack()
{
}
}
}
テストの方では、まだ "stack.IsEmpty" の部分が静的エラーになっている。
バブル アイコンまたは 'Ctrl+.' から「プロパティ 'Stack.IsEmpty' を生成します」を選ぼう。
Stack クラスに、プロパティ "IsEmpty" が生成される。 このように、Visual Studio の助けを借りることで、スムーズに TDD を行うことができる。
"IsEmpty" が作られたことで、で静的エラーが消えた。
すると、ここでライブ ユニット テスト機能が自動で働く。 テストする側のクラス「Stackのテスト」の左に、テストが失敗したことを示す赤い×印が表示される。
そして、テストされる側のクラス Stack の左にも、テストが失敗したことを示す赤い×印があらわれる。
では、テストを成功させるために、Stack クラスを修正しよう。 テスト コード中の "IsEmpty" にカーソルがある状態で 'F12' を押す (または、"IsEmpty" を右クリックして「定義に移動」)。
ここでは、"IsEmpty" の実装を "public bool IsEmpty => true;" に変更してみる。
そうすると、ライブ ユニット テストによって自動でテストが通り、Stack クラスと「Stackのテスト」クラスに表示されていた赤い×印が緑のチェックに変わる。
このように TDD では、次のようなステップで踏み、それを繰り返しながら、プログラミングを進めていく。
テストされる側にコードを追加する場合は、テストを追加し一度テストを失敗させる。 そして、テストが通る分だけのコードをテストされる側に追加する。
テストによってコードのその時点でのあるべき姿を記述する。 つまり、メソッドなりクラスなりの外からみた仕様をテストで先に記述するわけだ。 このテストが通ったことをもって、コードがあるべき姿になったことにする。 これを繰り返すことで、あるべき姿を少しずつインクリメンタルに作っていくのだ。 メソッドやクラスが、その時点でのあるべき姿になったかどうかが、テストによってフィードバックされる。
そして、Visual Studio 2017 のライブ ユニット テスト機能を使えば、このフィードバックがリアルタイムに行われることになるのだ。 仕様を満たしていないコードがレッドで、仕様を満たしたコードがグリーンで、リアルタイムで可視化される。 とても効率の良いコーディング環境だ。
さて、テストを追加してみよう。
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace Shos.Collections.Test
{
[TestClass]
public class Stackのテスト
{
Stack<int> stack;
[TestInitialize]
public void 準備() => stack = new Stack<int>();
[TestMethod]
public void 作ったら空()
{
Assert.IsTrue(stack.IsEmpty);
}
[TestMethod]
public void Pushしたら空じゃなくなる()
{
stack.Push(1);
Assert.IsFalse(stack.IsEmpty);
}
}
}
"stack = new Stack<int>()" のコードが重複するのが嫌なので、"[TestInitialize] public void 準備() => stack = new Stack<int>();" というコードを追加した。 このメソッドは、個々のテストの実行前に一回ずつ呼ばれる。 この部分は、まだテストされていないが、このようなコードの左には、青い横棒が表示される。 テストしていないコードが可視化されるわけだ。 これにより、常にカバレッジが確認できる。
テストを追加した直後は、"stack.Push(1)" の "Push" が静的エラーになる。 Stack.Push メソッドがまだないためだ。 バブル アイコンまたは 'Ctrl+.' から「メソッド 'Stack.Push' を生成します」を選ぼう。 また動的テストが自動で走り、ちゃんと失敗する。
テストが通るように Stack クラスの Push メソッドを修正しよう。 テスト コード中の "Push" にカーソルがある状態で 'F12' を押す (または、"Push" を右クリックして「定義に移動」)。 "public void Push(int v) { throw new NotImplementedException(); }" の左には赤い×印が表示されているはずだ。
Stack クラスを、例えば次のように変更すると、テストが通って、緑のチェックに変わる。
ではまた、テストを追加しよう。 静的エラーになる (赤い波線)。
'Ctrl+.' から「プロパティ 'Stack.Top' を生成します」を選択。 また動的テストが自動で走り、ちゃんと失敗 (赤い×印)。
'F12' を押して、Stack クラスを修正。 また動的テストが自動で走って成功 (緑のチェック)。
今度は、テスト「PushせずにTopをみたら例外」の追加。失敗。
Stack クラスを修正するとテストが成功。
TDD ではレッドとグリーンの繰り返しのリズムが大切だと思う。 テストのための無駄な作業を省いてくれるライブ ユニット テスト機能が、このリズムを心地よいものにし、プログラミングをより楽しくしてくれることだろう。
この例では、なるべく小さなステップで進めるようにしているので、テストが通る最小限の修正にとどめているが、コードが明らかであるような場合は、もっと大きな修正をしても構わないだろう。
今回は出てきていないが、TDD では、テストが通った後にリファクタリング (外から見た仕様を変えずに内部構造を改善すること) を行うことが多い。Visual Studio 2017 のライブ ユニット テスト機能を使うと、このリファクタリングの際も常に自動で静的テストと動的テストによって、仕様どおりであり続けていることがチェックされることになる。 リファクタリング中に誤ってバグを作ってしまってもすぐに気付けることだろう。
リファクタリングでは、なるべくエラーが起きている時間を短くすることが肝要だが、こうしたエラーの可視化によるフィードバックが常に起こることは、エラー時間の最小化につながるだろう。
また、リファクタリング中の継続的なフィードバックによって、なるべくレッドにならないような手順でリファクタリングをするような良い習慣が生まれるのではないだろうか。
もちろん、Visual Studio 2017 で追加された多くの新たなリファクタリング機能も安全なリファクタリングに役立ってくれることだろう。
Visual Studio 2017 RC の新たな機能であるライブ ユニット テストは、機能を使うことに手間を取られない。 機能を使うことに振り回されて、コーディングのリズムを狂わされることがない。 つまり、Visual Studio 2017 RC を使うと TDD が楽しく快適になる。
※ C# Advent Calendar 2016 の12月23日の記事。
以前、「C# Tips: interface を 抽象クラス (abstract class) とどう使い分けるか」という記事を書いた。 その中で、「アスペクトの実装を便宜上 (言語の都合上) interface で行う」というイディオムについて触れた。 この記事はその続きだ。 より具体的にこのイディオムを紹介する。
ソフトウェア開発というものは往々にして複雑さとの戦いになるものだが、プログラムの設計において複雑さに立ち向かうための基礎となる考え方に、分割攻略 (Divide and Conquer、分割統治) というものがある。 大きく複雑な問題をそのまま解くのは大変なので、より小さくシンプルな問題に分けることで、全体としての複雑さを下げて解こう、という戦略だ。
上の図のようにすると大変なので、たとえば次のようにする。 10倍の大きさの問題の複雑さは10倍ではきかない、というのが基本的なアイディアだ。
分割するときに重要となるのが、「どう分けるか」だ。 一般的には次のようになれば良いとされている。
ソフトウェアの設計では、疎結合で高凝集になるように分割することが大切、ということだ。 いくら小さな単位に分けても、問題が互いにからみあっていたり、シンプルな単位に分かれていなかったりすると、うまく複雑さが減ってくれないからだ。
そして、これを実現するために、様々な設計の考え方がある。 オブジェクト指向という考え方を使っても、ある程度これを行うことができる。 オブジェクト指向では、クラスやオブジェクトなどという単位に分けることでこれを行うわけだ。
ところが、オブジェクト指向といっても万能なわけではなく、クラスやオブジェクトなどに分けるというだけでは、必ずしも高凝集にならない。
例をみてみよう。 以下は、C# による CAD (Computer-Aided Design) の設計の例だ。
図の中の赤いクラスがモデル (Model) で、抽象図形クラス Figure と、そこから継承した複数の具象図形クラス LineFigure・EllipseFigure がある。
青いクラスがビュー (View) で、モデルの IEnumerable<Figure> というインタフェイスを使ってモデルとデータバインドし、個々の図形を描画する。
C# によるソース コードは、たとえば次のようなものになる (説明に必要のない部分は省略)。
using System;
using System.Collections;
using System.Collections.Generic;
abstract class Figure
{
public abstract void Draw();
}
class LineFigure : Figure
{
public override void Draw() => Console.WriteLine("Line!"); // 仮実装
}
class EllipseFigure : Figure
{
public override void Draw() => Console.WriteLine("Ellipse!"); // 仮実装
}
class CadModel : IEnumerable<Figure>
{
public IEnumerator<Figure> GetEnumerator()
{
yield return new LineFigure ();
yield return new LineFigure ();
yield return new EllipseFigure();
yield return new LineFigure ();
}
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}
class CadView
{
public IEnumerable<Figure> DataSource {
set { value.ForEach(figure => figure.Draw()); }
}
}
class Program
{
static void Main()
{
new CadView().DataSource = new CadModel();
}
}
static class EnumerableExtensions
{
public static void ForEach<TElement>(this IEnumerable<TElement> @this, Action<TElement> action)
{
foreach (var element in @this)
action(element);
}
}
この実装では仮にコンソールに文字列を出力しているだけなので、実行結果は次のようになる。
Line!
Line!
Ellipse!
Line!
この段階ではまだ単純な設計であるため、オブジェクト指向を使うことでそこそこ高凝集になっている。
さて、ここまで作った後で、ファイルなどに保存するときのためにシリアライズ機能を付けたくなったとする。 CAD は一般的に様々なフォーマットをサポートすることが多いものだが、ここでは仮に SVG (Scalable Vector Graphics) 形式と独自のバイナリ―形式をサポートするものとしよう。
このために、それぞれの図形クラスに、2つの形式のシリアライズのための SvgSerialize と BinarySerialize という2つのメソッドを追加してみるとどうなるだろうか。
C# によるソース コードは、たとえば次のように変わるだろう。
using System;
using System.Collections;
using System.Collections.Generic;
abstract class Figure
{
public abstract void Draw();
public abstract void SvgSerialize();
public abstract void BinarySerialize();
}
class LineFigure : Figure
{
public override void Draw() => Console.WriteLine("Line!"); // 仮実装
public override void SvgSerialize() => Console.WriteLine("SvgSerialize line!"); // 仮実装
public override void BinarySerialize() => Console.WriteLine("BinarySerialize line!"); // 仮実装
}
class EllipseFigure : Figure
{
public override void Draw() => Console.WriteLine("Ellipse!"); // 仮実装
public override void SvgSerialize() => Console.WriteLine("SvgSerialize ellipse!"); // 仮実装
public override void BinarySerialize() => Console.WriteLine("BinarySerialize ellipse!"); // 仮実装
}
class CadModel : IEnumerable<Figure>
{
public void SvgSerialize() => this.ForEach(figure => figure.SvgSerialize());
public void BinarySerialize() => this.ForEach(figure => figure.BinarySerialize());
public IEnumerator<Figure> GetEnumerator()
{
yield return new LineFigure ();
yield return new LineFigure ();
yield return new EllipseFigure();
yield return new LineFigure ();
}
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}
class CadView
{
public IEnumerable<Figure> DataSource {
set { value.ForEach(figure => figure.Draw()); }
}
}
class Program
{
static void Main()
{
var model = new CadModel();
new CadView().DataSource = model;
model.SvgSerialize ();
model.BinarySerialize();
}
}
static class EnumerableExtensions
{
public static void ForEach<TElement>(this IEnumerable<TElement> @this, Action<TElement> action)
{
foreach (var element in @this)
action(element);
}
}
そして、これが実行結果だ。
Line!
Line!
Ellipse!
Line!
SvgSerialize line!
SvgSerialize line!
SvgSerialize ellipse!
SvgSerialize line!
BinarySerialize line!
BinarySerialize line!
BinarySerialize ellipse!
BinarySerialize line!
しかし、この設計では、高凝集になっていない部分がでてきてしまっているのがお分かりだろうか。
図形クラスという部分に関しては、ある程度高凝集になっている。 各図形に関する仕事 (それぞれの図形の描画やシリアライズ) は、ちゃんと各図形でやっているように見える。
ところが、SVG シリアライズ、あるいはバイナリー シリアライズという関心事に目を向けてみると、そちらはどうなっているだろう。
たとえば、SVG シリアライズに関するメソッドは Figure、LineFigure、EllipseFigure、CadModel という複数のクラスにまたがってしまっている。 バイナリー シリアライズについても同様だ。 これでは、高凝集、すなわち「一つの分割単位の中が一つの関心事だけになり、かつ、その関心事がその分割単位の中だけにある」とは言えないだろう。
このように、オブジェクト指向では複数のクラスをまたがる関心事というものがでてきて設計に困ることがある。 この場合だと、それぞれの図形への関心事とシリアライズという関心事が直交してしまっている。 そのため、一方の視点での分割によるかたまりが、他方の視点での分割をまたがってしてしまうのだ。 このような関心事は、横断的関心事 (crosscutting concern) と呼ばれる。
この例では、はじめは図形のクラス設計を行っていて、後からSVG シリアライズやバイナリー シリアライズといった横断的関心事がでてきてしまったのだ。
なんとか、それまでのクラスの設計をこわすことなく、この横断的関心事を分離できないものだろうか。
この記事では、C# の interface と partial class を使ったイディオムをご紹介したい。
先のコードのようにいきなりシリアライズが必要なそれぞれのクラスの中に SvgSerialize メソッドと BinarySerialize メソッドを追加するのではなく、先ず partial class とだけしてみる。
using System;
using System.Collections;
using System.Collections.Generic;
abstract partial class Figure
{
public abstract void Draw();
}
partial class LineFigure : Figure
{
public override void Draw() => Console.WriteLine("Line!"); // 仮実装
}
partial class EllipseFigure : Figure
{
public override void Draw() => Console.WriteLine("Ellipse!"); // 仮実装
}
partial class CadModel : IEnumerable<Figure>
{
public IEnumerator<Figure> GetEnumerator()
{
yield return new LineFigure ();
yield return new LineFigure ();
yield return new EllipseFigure();
yield return new LineFigure ();
}
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}
class CadView
{
public IEnumerable<Figure> DataSource {
set { value.ForEach(figure => figure.Draw()); }
}
}
class Program
{
static void Main()
{
var model = new CadModel();
new CadView().DataSource = model;
model.SvgSerialize ();
model.BinarySerialize();
}
}
static class EnumerableExtensions
{
public static void ForEach<TElement>(this IEnumerable<TElement> @this, Action<TElement> action)
{
foreach (var element in @this)
action(element);
}
}
この Main メソッドでは、SvgSerialize メソッドを BinarySerialize メソッドを使っているが、この時点ではどこにも実装がない。
次に、SvgSerialize メソッドを持つという interface である ISvgSerializable を作り、それを各モデル クラスで実装する。そして、 この実装を partial class の機能を用いて別ファイルで行うことにする。 こんな具合だ。
using System;
interface ISvgSerializable
{
void SvgSerialize();
}
partial class Figure : ISvgSerializable
{
public abstract void SvgSerialize();
}
partial class LineFigure
{
public override void SvgSerialize() => Console.WriteLine("SvgSerialize line!"); // 仮実装
}
partial class EllipseFigure
{
public override void SvgSerialize() => Console.WriteLine("SvgSerialize ellipse!"); // 仮実装
}
partial class CadModel : ISvgSerializable
{
public void SvgSerialize() => this.ForEach(figure => figure.SvgSerialize());
}
こうすると、SVG シリアライズという責務を記述するコードがすべてこのファイルに集まることになる。
BinarySerialize メソッドの方も、同様に別ファイルを用意する。 そこで IBinarySerializable という interface を作り、それを各モデル クラスで実装する。
using System;
interface IBinarySerializable
{
void BinarySerialize();
}
partial class Figure : IBinarySerializable
{
public abstract void BinarySerialize();
}
partial class LineFigure
{
public override void BinarySerialize() => Console.WriteLine("BinarySerialize line!"); // 仮実装
}
partial class EllipseFigure
{
public override void BinarySerialize() => Console.WriteLine("BinarySerialize ellipse!"); // 仮実装
}
partial class CadModel : IBinarySerializable
{
public void BinarySerialize() => this.ForEach(figure => figure.BinarySerialize());
}
実行結果は変わらない。 クラス図も ISvgSerializable と IBinarySerializable という2つのインタフェイスが加わっただけだ。
これは、オブジェクト指向で横断的関心事を分離したわけではない。 オブジェクト指向にも限界がある。 ここでは、横断的関心事をクラスにマッピングすることではうまく分離できなかった。
そのため、ここでは C# の機能を使い、それまでの関心事に直交した新たな関心事をファイルにマッピングすることで分離した、ということだ。
Microsoft MVP Award を再受賞しました。13年目になります。
素晴らしい機会とこの機会によって関われた皆様に感謝です。
csv (Comma-Separated Values または Character-Separated Values) を読み書きするためのシンプルなライブラリーを書いてみた。
csv 形式のファイルは Excel などで表示/編集することができ、便利なので、そこそこ必要になる。 csv を読み書きするライブラリーは既に他にあるが、よりシンプルな使い勝手を目指してみた。
このライブラリーを使うと、プレーンなオブジェクトのコレクション (IEnumerable<Something>) を csv 形式で読み書きすることができる。
まず、何か IEnumerable<TElement> なものを用意する:
// 何か IEnumerable<TElement> なもの IEnumerable<ToDo> toDoes = new ToDoList();
例えばこんなクラス:
class ToDoList : IEnumerable<ToDo> // サンプル コレクション { public IEnumerator<ToDo> GetEnumerator() { yield return new ToDo { Id = 1, Title = "filing tax returns", Deadline = new DateTime(2018, 12, 1) }; yield return new ToDo { Id = 2, Title = "report of a business trip", Detail = "\"ASAP\"", DaySpan = new DaySpan(3), Priority = Priority.High }; yield return new ToDo { Id = 3, Title = "expense slips", Detail = "book expenses: \"C# 6.0 and the .NET 4.6 Framework\",\"The C# Programming\"", Priority = Priority.Low, Done = true }; yield return new ToDo { Id = 4, Title = " wish list ", Detail = " \t (1) \"milk\"\n \t (2) shampoo\n \t (3) tissue ", Priority = Priority.High }; } IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); }
これを、次のように書きこめる:
const string csvFileName = "todo.csv"; await toDoes.WriteCsvAsync(csvFileName);
出来上がった csv ファイルは次のようになる:
Id,Title,Deadline,Done,Priority,Details,DaySpan 1,filing tax returns,2018/12/01 0:00:00,False,Middle,,0 2,report of a business trip,2017/07/12 13:13:01,False,High,"""ASAP""",3 3,expense slips,2017/07/12 13:13:01,True,Low,"book expenses: ""C# 6.0 and the .NET 4.6 Framework"",""The C# Programming""",0 4, wish list ,2017/07/12 13:13:01,False,High," (1) ""milk"" (2) shampoo (3) tissue ",0
読みこみは次のような感じ:
IEnumerable<ToDo> newToDoes = await CsvSerializer.ReadCsvAsync<ToDo>(csvFileName);
コレクションの要素の "get" と "set" の両方を持つ public なプロパティが csv ファイルに読み書きされる。
書きこみ時には、型を問わず、"ToString()" メソッドで文字列に変換される。
読みこみ時には、文字列型はそのまま、enum (列挙型) も文字列の通りの値として読みこまれる。 それ以外の型のときは、"TryParse" か "Parse" を使って文字列を値に変更しようとする。 このいずれもできない型は読みこまれない。
"get" と "set" の両方を持っていて、かつ、次のいずれかの型であるプロパティが読みこまれる。
例えば、上記 ToDo クラスはこんな型:
// 要素の型のサンプル // 〇 の付いたプロパティ: 読み書きされる // × の付いたプロパティ: 読み書きされない class ToDo { public int Id { get; set; } // 〇 public string Title { get; set; } = ""; // 〇 public DateTime Deadline { get; set; } = DateTime.Now; // 〇 public bool Done { get; set; } // 〇 public Priority Priority { get; set; } = Priority.Middle; // 〇 ユーザー定義 enum [ColumnName("Details")] public string Detail { get; set; } = ""; // 〇 [ColumnName("Details")] で csv ファイルでの列名が "Details" に変更される public DaySpan DaySpan { get; set; } // 〇 "Parse" を持つユーザー定義型 ("TryParse" は持たない) [CsvIgnore()] public string Option { get; set; } = ""; // × [CsvIgnore()] が付けられているので無視される public string Version => "1.0"; // × get しかできないプロパティなので無視される }
上記 ToDo クラスで使われているユーザー定義型は次の通り:
// ユーザー定義の enum の例 enum Priority { High, Middle, Low } // "Parse" を持つユーザー定義型 ("TryParse" は持たない) の例 struct DaySpan { public int Value { get; private set; } public DaySpan(int value) => Value = value; public static DaySpan Parse(string text) => new DaySpan(int.Parse(text)); public override string ToString() => Value.ToString(); }
csv の読み書きは、ヘッダー部分 (例えば、上記 csv ファイルの一行目) がない場合でも可能になっている。
ただし、ヘッダー部分がある場合は、列が入れ替わっていてもヘッダー部を照合することで読みこみ可能だが、ヘッダー部分がない場合は、列を入れ替えると読みこむことができない。
ヘッダー部分無しでの書きこみの例:
await toDoes.WriteCsvAsync(csvFilePathName: csvFileName, hasHeader: false);
出来上がった csv ファイル:
1,filing tax returns,2018/12/01 0:00:00,False,Middle,,0 2,report of a business trip,2017/07/06 18:08:13,False,High,"""ASAP""",3 3,expense slips,2017/07/06 18:08:13,True,Low,"book expenses: ""C# 6.0 and the .NET 4.6 Framework"",""The C# Programming""",0 4, wish list ,2017/07/12 13:13:01,False,High," (1) ""milk"" (2) shampoo (3) tissue ",0
こうしたヘッダー部分の無い csv ファイルを読みこむときは次のようにする:
IEnumerable<ToDo> newToDoes = await CsvSerializer.ReadCsvAsync<ToDo>(csvFilePathName: csvFileName, hasHeader: false);
文字コードは変更可能 (デフォルトは UTF8)。
CsvSerializer.Encoding = Encoding.GetEncoding(0);
区切り文字も変更可能 (デフォルトは ',')。
CsvSerializer.Separator = '\t';
ファイル名の代わりに stream も指定できる。
using (var stream = new FileStream(csvFileName, FileMode.Create)) await collection.WriteCsvAsync(stream);
leaveOpen を指定することで、読み書きの後に stream を開いたままにすることもできる。
using (var stream = new FileStream(csvFileName, FileMode.Create)) await collection.WriteCsvAsync(stream: stream, bufferSize: 1024, leaveOpen:true, hasHeader: true);
非同期メソッド以外に同期メソッドもある。
toDoes.WriteCsv(csvFileName);
各ライブラリーは NuGet に公開されているので、Visual Studio からインストールできる。
ソースコードは次の場所で公開:
含まれるプロジェクトは次の通り:
『仙台IT文化祭 2017』に参加してきた。
※ 『C# 大好き MVP による、C# ドキドキ・ライブコーディング!! (出張編)』で登壇。
『仙台IT文化祭 2017』 | |
---|---|
日時 | 2017/10/28(土) - 29日(日) |
場所 | 東北大学川内南キャンパス (C19) 文科系総合講義棟1F&2F (仙台市青葉区川内27-1) |
[Event] 「仙台IT文化祭」10月28日(土)-29日(日)https://t.co/HRsMc5DYSh
— Fujio Kojima (@Fujiwo) 2017年10月6日
一日目に「.NET学科」で登壇予定。仙台がとても楽しみ。 #sendaiitfes
今週末は仙台に行くことにした。30年ぶりくらい。 #sendaiitfes
— Fujio Kojima (@Fujiwo) 2017年10月23日
さて仙台へ。
— Fujio Kojima (@Fujiwo) 2017年10月27日
なんだか仙台に知人が集まってる気がする。
— Fujio Kojima (@Fujiwo) 2017年10月28日
なぜか青葉城恋唄が流れてない。
— Fujio Kojima (@Fujiwo) 2017年10月28日
#sendaiitfes から帰宅した。思ったより仙台近かった。また行きたい。
— Fujio Kojima (@Fujiwo) 2017年10月30日
仙台IT文化祭 「C# 大好き MVP による C# ドキドキ・ライブコーディング (出張編)」 小島の分https://t.co/G4iOroqb6g
— Fujio Kojima (@Fujiwo) 2017年10月30日
公開してます。後悔してません。 #sendaiitfes #s282035
『仙台IT文化祭 2017 #sendaiitfes』
— fullvirtue (@fullvirtue) 2017年10月29日
皆さまのご協力もあって、二日間累計1,343名もの方々にお越しいただきました!
ご来場いただいた皆さま、ご登壇いただいた皆さま、ご支援いただいたスポンサー企業の皆さま、個… https://t.co/SVsUXJURxZ pic.twitter.com/EG3k7xB6H8
※ C# Advent Calendar 2017 の12月23日の記事。
過去の C# Advent Calender の記事:
今回は、C# で C♯ を演奏してみた、と言いたいだけの記事。
プログラミング言語である C# は、通常 "C#" と表記し "c sharp" と読む。
※ "C" を進化させた "C++" を更に進化させたという意味の "C++++" が由来、というのも有名な話だ。 これは、C# の父である Anders Hejlsberg 氏から直に聞いたので間違いない。
ご存知の方が多いと思うが、"C#" の # は通常「ナンバーサイン」とか「番号記号」とか「井桁」とか呼ばれるもので、英語圏でも "number" とか "pound" とか "hash" などと発音される。 音楽記号である♯とは別の文字だ。
そして、筆者はよく音楽の演奏をするのだが、C♯ と書かれていれば、それは嬰ハ長調、または、嬰ハの音 (ハ長調のド♯) を表していることが多い。
というわけで (謎)、C# で C♯ を演奏してみたい。
C# で音階を演奏するため、2つの方法を試してみる。
一つ目は、System.Console.Beep(int frequency, int duration) を用いる方法だ。 このメソッドは、音の周波数と長さを渡して演奏するようになっている。
音は1オクターブ上がると周波数が2倍になる。平均率の場合、音階の中の全部の半音は同じだけ音が上がっていく。 1オクターブは半音12個分だから、半音上がるごとに周波数は「2の1/12乗」倍になる計算だ。 全音上がるごとだと「2の1/6乗」倍。
なので、C# で音名 C♯ の四分音符を演奏すると、例えば次のようになる。
using System;
static class MusicalScale
{
public static double A4Frequency { get; set; } = 440.0; // ベースとなる音 A4 (デフォルトは440Hz)
// 平均律 (equal temperament) | Wikipedia
// https://ja.wikipedia.org/wiki/%E5%B9%B3%E5%9D%87%E5%BE%8B
// 1オクターブ (6音) で周波数が2倍になり、半音ごとに周波数は「2の1/12乗」倍になる
public static double EqualTemperamentFrequency(double relativePositionInScale /* ベースとなる音 A4 から何音上か */)
=> A4Frequency * System.Math.Pow(2.0, relativePositionInScale * 2.0 / 12.0);
}
// 音符の長さ
enum Duration
{
Whole = 1600 , // 全音符
Half = Whole / 2, // 二分音符
Quarter = Half / 2, // 四分音符
Eighth = Quarter / 2, // 八分音符
Sixteenth = Eighth / 2, // 十六分音符
}
class Program
{
const double relativePositionOfCSharp = -4.0; // C♯(嬰ハ) は A(イ) より4音下
static void Main()
{
PlayCSharpNote();
Console.ReadKey();
}
// 音名 C♯ の音を演奏 (Beep で)
static void PlayCSharpNote()
=> Beep(frequency: MusicalScale.EqualTemperamentFrequency(relativePositionOfCSharp), duration: Duration.Quarter);
// 指定された周波数/長さの音を鳴らす
static void Beep(double frequency, Duration duration)
=> Console.Beep(frequency: (int)System.Math.Round(frequency), duration: (int)duration);
}
実行すると、C♯ が聞こえてくる。
この要領で、平均律で C♯ の音階 (嬰ハ長調のドレミファソラシド) を演奏してみよう。
using System;
using System.Collections.Generic;
static class EnumerableExtentions
{
public static void ForEach<T>(this IEnumerable<T> @this, Action<T> action)
{
foreach (var element in @this)
action(element);
}
}
static class MusicalScale
{
public static double A4Frequency { get; set; } = 440.0; // ベースとなる音 A4 (デフォルトは440Hz)
// 平均律 (equal temperament) | Wikipedia
// https://ja.wikipedia.org/wiki/%E5%B9%B3%E5%9D%87%E5%BE%8B
// 1オクターブ (6音) で周波数が2倍になり、半音ごとに周波数は「2の1/12乗」倍になる
public static double EqualTemperamentFrequency(double relativePositionInScale /* ベースとなる音 A4 から何音上か */)
=> A4Frequency * System.Math.Pow(2.0, relativePositionInScale * 2.0 / 12.0);
}
// 音符の長さ
enum Duration
{
Whole = 1600 , // 全音符
Half = Whole / 2, // 二分音符
Quarter = Half / 2, // 四分音符
Eighth = Quarter / 2, // 八分音符
Sixteenth = Eighth / 2, // 十六分音符
}
class Program
{
const double relativePositionOfCSharp = -4.0; // C♯(嬰ハ) は A(イ) より4音下
static void Main()
{
//PlayCSharpNote();
PlayCSharpEqualTemperament();
Console.ReadKey();
}
//// 音名 C♯ の音を演奏 (Beep で)
//static void PlayCSharpNote()
// => Beep(frequency: MusicalScale.EqualTemperamentFrequency(relativePositionOfCSharp), duration: Duration.Quarter);
// C♯ の音階を演奏 (平均律を Beep で)
static void PlayCSharpEqualTemperament()
=> new[] { 0.0, 1.0, 2.0, 2.5, 3.5, 4.5, 5.5, 6.0 } // ドレミファソラシドの各音がドから何音離れているか
.ForEach(note => Beep(frequency: MusicalScale.EqualTemperamentFrequency(note + relativePositionOfCSharp), duration: Duration.Quarter));
// 指定された周波数/長さの音を鳴らす
static void Beep(double frequency, Duration duration)
=> Console.Beep(frequency: (int)System.Math.Round(frequency), duration: (int)duration);
}
純正率でもやってみよう。
純正率では各音が単純な整数比になるため、和音にしたときに平均律のようにわずかな周波数のずれからくるうなりが生ずることがなく、綺麗にハモれる。
詳細は省略するが、純正率では、ドレミファソラシドの周波数が、それぞれ、1/1、9/8、5/4、4/3、3/2、5/3、15/8、2/1 となる。
これを踏まえて、C# で書いてみる。
using System;
using System.Collections.Generic;
using System.Linq;
static class EnumerableExtentions
{
public static void ForEach<T>(this IEnumerable<T> @this, Action<T> action)
{
foreach (var element in @this)
action(element);
}
}
static class MusicalScale
{
public static double A4Frequency { get; set; } = 440.0; // ベースとなる音 A4 (デフォルトは440Hz)
// 純正律の場合の各音 (ドレミファソラシド) の相対的な周波数の倍率
readonly static double[] JustIntonationRelativeFrequencyScales = new[] {
1.0 / 1.0, 9.0 / 8.0, 5.0 / 4.0, 4.0 / 3.0, 3.0 / 2.0, 5.0 / 3.0, 15.0 / 8.0, 2.0 / 1.0
};
// 平均律 (equal temperament) | Wikipedia
// https://ja.wikipedia.org/wiki/%E5%B9%B3%E5%9D%87%E5%BE%8B
// 1オクターブ (6音) で周波数が2倍になり、半音ごとに周波数は「2の1/12乗」倍になる
public static double EqualTemperamentFrequency(double relativePositionInScale /* ベースとなる音 A4 から何音上か */)
=> A4Frequency * System.Math.Pow(2.0, relativePositionInScale * 2.0 / 12.0);
// 純正律 (just intonation) - Wikipedia
// https://ja.wikipedia.org/wiki/%E7%B4%94%E6%AD%A3%E5%BE%8B
public static double JustIntonationFrequency(double keynoteRelativePositionInScale, int noteIndex /* ドレミファソラシドの各音のインデックス (0~7) */)
=> EqualTemperamentFrequency(keynoteRelativePositionInScale) * JustIntonationRelativeFrequencyScales[noteIndex];
}
// 音符の長さ
enum Duration
{
Whole = 1600 , // 全音符
Half = Whole / 2, // 二分音符
Quarter = Half / 2, // 四分音符
Eighth = Quarter / 2, // 八分音符
Sixteenth = Eighth / 2, // 十六分音符
}
class Program
{
const double relativePositionOfCSharp = -4.0; // C♯(嬰ハ) は A(イ) より4音下
static void Main()
{
//PlayCSharpNote();
//PlayCSharpEqualTemperament();
PlayCSharpJustIntonation();
Console.ReadKey();
}
//// 音名 C♯ の音を演奏 (Beep で)
//static void PlayCSharpNote()
// => Beep(frequency: MusicalScale.EqualTemperamentFrequency(relativePositionOfCSharp), duration: Duration.Quarter);
//// C♯ の音階を演奏 (平均律を Beep で)
//static void PlayCSharpEqualTemperament()
// => new[] { 0.0, 1.0, 2.0, 2.5, 3.5, 4.5, 5.5, 6.0 } // ドレミファソラシドの各音がドから何音離れているか
// .ForEach(note => Beep(frequency: MusicalScale.EqualTemperamentFrequency(note + relativePositionOfCSharp), duration: Duration.Quarter));
// C♯ の音階を演奏 (純正率を Beep で)
static void PlayCSharpJustIntonation()
=> Enumerable.Range(start: 0, count: 8) // ドレミファソラシドの各音のインデックス (0~7)
.ForEach(noteIndex => Beep(frequency: MusicalScale.JustIntonationFrequency(relativePositionOfCSharp, noteIndex), duration: Duration.Quarter));
// 指定された周波数/長さの音を鳴らす
static void Beep(double frequency, Duration duration)
=> Console.Beep(frequency: (int)System.Math.Round(frequency), duration: (int)duration);
}
もう一つ試そう。 Small Basic の機能を使う方法だ。
そのために、まず Visual Studio から NuGet で "SmallBasicLib" をインストールする。
このライブラリーの機能の一つに、MML (Music Macro Language) を演奏するというものがある。 Microsoft.SmallBasic.Library.Sound.PlayMusic というメソッドを使うことでテキストで記述された旋律を演奏できる。
これを使って、平均律で C♯ の音階 (ドレミファソラシド) を演奏してみよう。 詳しい MML の記述方法は、下記ソースコードのコメントを見てほしい。
using Microsoft.SmallBasic.Library;
using System;
class Program
{
static void Main()
{
PlayCSharpMusic();
Console.ReadKey();
}
// C♯ の音階を演奏 (平均率を Small Basic Library で)
static void PlayCSharpMusic() =>
// C♯ (嬰ハ長調) | Wikipedia
// https://ja.wikipedia.org/wiki/%E5%AC%B0%E3%83%8F%E9%95%B7%E8%AA%BF
// シャープ7箇所(F, C, G, D, A, E, B)
// C♯ D♯ F F♯ G♯ A♯ C C♯ (嬰ハ 嬰二 ヘ 嬰ヘ 嬰ト 嬰イ ハ 嬰ハ)
Sound.PlayMusic(notes: "L4 O4 C+ D+ F F+ G+ A+ > C C+");
// Sound.PlayMusic の notes
//
// C, D, E, F, G, A, B: 音名
// R : 休符
// C+: Cは音名、音を半音上げる (♯)
// C-: Cは音名、音を半音下げる (♭)
// Cn: n は音の長さ (1: 全音符、2: 二分音符、2.: 付点二分音符、4: 四分音符、4.: 付点四分音符、…)
// Ln: n はデフォルトの音の長さ
// On: n はオクターブ (デフォルトは4)
// > : 1オクターブ上に
// < : 1オクターブ下に
}
実は C♯、つまり嬰ハ長調の曲というのはそんなに多くないのだが、例えば、バッハ の「平均律クラヴィーア 第1巻 プレリュード 第3番 嬰ハ長調 BWV 848」や「前奏曲とフーガ 第3番 嬰ハ長調 BWV 872」などがそうだ。
例えば下記で聴くことができる (執筆時点)。
試しに、一部だけ C# で書いてみよう。 C♯ の響きを雰囲気だけでも味わっていただければ幸いだ。
using Microsoft.SmallBasic.Library;
using System;
class Program
{
static void Main()
{
PlayBWV848();
//PlayCSharpMusic();
Console.ReadKey();
}
// バッハ 平均律クラヴィーア 第1巻 プレリュード 第3番 嬰ハ長調 BWV 848
// 雰囲気だけ
static void PlayBWV848() =>
Sound.PlayMusic("L16 O5 E+ C+ > G+ < C+ E+ C+" +
"F+ C+ F+ C+ F+ C+" +
"G+ C+ G+ C+ G+ C+" +
"A+ C+ A+ C+ A+ C+" +
"G+ C+ G+ C+ G+ C+" +
"F+ E+ D+ E+ F+ D+" +
"E+ D+ C+ D+ E+ C+" +
"D+ E+ D+ C+ < B+ A+" +
"< B+ G+ D+ G+ B+ G+" +
"> C+ < G+ > C+ < G+ > C+ < G+" +
"> D+ < G+ > D+ < G+ > D+ < G+" +
"> E+ < G+ > E+ < G+ > E+ < G+" +
"> D+ < G+ > D+ < G+ > D+ < G+" +
"> C+ < B+ A+ B+ > C+ < A+" +
"B+ A+ G+ A+ B+ G+" +
"A+ B > > F+ E+ D+ E+" +
"F+ D+ < A+ > D+ F+ D+" +
"G+ D+ G+ D+ G+ D+" +
"A+ D+ A+ D+ A+ D+" +
"B D+ B D+ B D+" +
"A+ D+ A+ D+ A+ D+" +
"G+ F+ E+ F+ G+ E+" +
"F+ E+ D+ E+ F+ D+" +
"E+ F+ E+ D+ C+ < B+");
//// C♯ の音階を演奏 (平均率を Small Basic Library で)
//static void PlayCSharpMusic() =>
// // C♯ (嬰ハ長調) | Wikipedia
// // https://ja.wikipedia.org/wiki/%E5%AC%B0%E3%83%8F%E9%95%B7%E8%AA%BF
// // シャープ7箇所(F, C, G, D, A, E, B)
// // C♯ D♯ F F♯ G♯ A♯ C C♯ (嬰ハ 嬰二 ヘ 嬰ヘ 嬰ト 嬰イ ハ 嬰ハ)
// Sound.PlayMusic(notes: "L4 O4 C+ D+ F F+ G+ A+ > C C+");
// Sound.PlayMusic の notes
//
// C, D, E, F, G, A, B: 音名
// R : 休符
// C+: Cは音名、音を半音上げる (♯)
// C-: Cは音名、音を半音下げる (♭)
// Cn: n は音の長さ (1: 全音符、2: 二分音符、2.: 付点二分音符、4: 四分音符、4.: 付点四分音符、…)
// Ln: n はデフォルトの音の長さ
// On: n はオクターブ (デフォルトは4)
// > : 1オクターブ上に
// < : 1オクターブ下に
}
富山県氷見市で、毎年恒例の勉強会「BuriKaigi2018」を開催しました。
78名もの方に参加していただきました。 雪が多い季節にも関わらず、今年も遠方からも多くのスピーカーや参加者の方がいらしてくださいました。 本当にありがとうございました。
当日の盛り上がりは Twitter などで見ることができます。
「BuriKaigi2018」 | |
---|---|
日時 | 2018/03/02(土) 13:00-18:30 (終了後懇親会) |
会場 |
|
主催・共催 |
Hokuriku.NET 北陸エンジニアグループ |
スポンサー | Forkwell |
詳細 | BuriKaigi2018 | connpass |
|
|
タイトル | 概要 | スピーカー |
---|---|---|
「Visual Studio 2017 新機能wrap up」 | VS2017が登場しておよそ1年が経過したので、リリース後に追加された新機能をまとめてみましょう。 | 森 博之 氏 @hiroyuki_mori Microsoft MVP for Visual Studio and Development Technologies |
|
||
「Introduction to Xamarin.Forms for MacOS - あるいは UWP から Mac App への布石 -」 |
初めてXamarin.Formsを触ってみたのが2014年3月。 UWP、Android、iOSの他にPreviewでMacOSがあるらしい? ので、ちょっと触ってみたのゆるふわセッション。 |
蜜葉 優 氏 @mitsuba_yu Microsoft MVP for Windows Development |
|
||
「ItemsStackPanelの気持ちに寄り添ってパフォーマンスを改善」 | UWPのListViewは ItemsStackPanelがよしなに仮想化してくれます。 ところが、場合によってはうまく仮想化してもらえません。 ItemsStackPanelの気持ちの寄り添って、パフォーマンスを改善してみます。 |
@tmyt Microsoft MVP for Windows Development |
|
||
「コントロールベンダー視点での Command Line Interface (CLI)」 | ここ数年?の CLIの隆盛?を UIコンポーネントベンターの一員として10年間在籍している中から考えます。 | 池原 大然 氏 @Neri78 |
|
||
「デスクトップ アプリがこの先生きのこるには 2018」 | ここ1年間、著名なデスクトップ アプリがWindowsストアに並ぶなど、UWP以外のWindowsアプリ開発を取り巻く環境が変化しました。 2017年を振り返りつつ、デスクトップ アプリ開発の立ち位置について考えてみます。 |
@Grabacr07 Microsoft MVP for Windows Development Microsoft MVP for Visual Studio and Development Technologies |
|
||
「Windows Serverの新しい管理ツールの紹介」 | Windows Serverは、Server Coreで提供されます。 Server Coreは、コンパクトで身軽な反面デスクトップエクスペリエンスが提供されないため手軽なサーバー管理が難しくなります。 オンプレミスの環境であれば、RSATなどを利用できますが、クラウド環境では難しい状況あります。 これらの問題を解決する新しいWebベースのサーバー管理ツール (Honolulu プロジェクト) について紹介します。 |
澤田 賢也 氏 @masayasawada Microsoft MVP for Cloud and Datacenter Management |
|
||
「見える化、言える化、やりきれる化! Dynamics365 北陸へ拡散」 | 吉島 良平 氏 @navisionaxapta Microsoft MVP for Business Solutions 杉本 和也 氏 @sugimomoto Microsoft MVP for Business Solutions |
|
|
||
「C#大好きMVPによるドキドキ ライブ コーディング」 | 石野 光仁 氏 @ailight Microsoft MVP for Visual Studio and Development Technologies 鈴木 孝明 氏 @xin9le Microsoft MVP for Visual Studio and Development Technologies 小島 富治雄 氏 @Fujiwo Microsoft MVP for Visual Studio and Development Technologies 室星 亮太 氏 @ryotamurohoshi Microsoft MVP for Visual Studio and Development Technologies |
|
|
タイトル | 概要 | スピーカー |
---|---|---|
「JavaOne 2017フィードバック - JDKリリースモデル変更とJava EEのEclipse Foundationへの移行」 | 昨年10月初旬に開催されたJavaOne 2017はJava SE 9とJava EE 8が大きなテーマでしたが、開催に合わせて重要な発表が2つありました。 このセッションでは、その2つの発表 - JDKリリースモデル変更とJava EEのEclipse Foundationへの移行、EE4Jプロジェクトの設立について、その内容をご説明します。 今後Javaの活用がどう変わるのかがわかります。 |
伊藤 敬 氏@itakash Oracle |
「モジュール移行の課題と対策」 | Java SE 9で導入されたProject Jigsawにより、JARファイルの扱いの煩雑さから解放されました。 その一方でモジュールを使用していないアプリケーションであっても、モジュール導入の影響によりビルドできなかったり、動作しなかったりする問題があります。 また、モジュールを導入するにしても、現状ではモジュールに対応したライブラリと非モジュールのライブラリを併用しなくてはなりません。 本セッションではモジュールについて説明し、モジュールを使用する際の課題とそれに対する対応についても解説していきます。 | 櫻庭 祐一 氏@skrb |
「Running Kubernetes on Azure(仮)」 | Azure 上で Kubernetes の動かし方 Docker などのコンテナに興味のある方向け。 | 山本 誠樹 (やまもと まさき) 氏 @nnasaki Microsoft MVP for Microsoft Azure |
「日本語入力の落とし穴」 | 日本語を受け取るアプリケーションがおちいりがちな落とし穴について紹介する。 「テキスト入力をハンドリングするアプリケーションを作る人」「日本語入力がうまくできないアプリケーションを修正したい人」「独自の日本語入力を作りたい人」などに役に立つと思う。 |
@mzp |
日本語入力の落とし穴 #burikaigi | みずぴー日記 | ||
「JetBrainsでもっと楽しくコーディング、ワークフロー」 | IDEベンダとして有名なJetBrainsですが、IDEのみならずチームツールもリリースしています。 JetBrainsのIDEはもちろんのこと、10名までは無償で使えるチームツールを組み合わせたワークフローまでご紹介いたします。 |
山本ゆうすけ氏 @yusukey 株式会社サムライズム |
「Excel芸2018」 | Scalaは関数型プログラミングもできる、マルチパラダイムなプログラミング言語です。 本発表では、関数プログラミングによって何ができるようになるかを、SIerにとってなじみが深いであろう話題を交えて紹介します。 |
Excel絶対殺すマン @bleis |
「Asciidocの紹介」 | 文書は気楽に記述でき、バージョン管理しやすいと便利です。 今回はソフトウェア開発者が記述するような文書のために開発されたAsciidocというツールを紹介します。 Javaさえ入っていれば使えるので、Windowsでも気軽に使うことができます。 セッションでは、環境構築から文法、そして便利な周辺ツールなどを紹介します。 Learning Object Asciidocによる文書管理 Groovyによるハック |
@kyon_mm |
タイトル | スピーカー | |
---|---|---|
スポンサービデオ | Forkwell | |
|
||
リモートデスクトップエンジニアの生態2018 | 金子雄一 氏 @oskaneko Microsoft MVP for Enterprise Mobility |
|
|
||
Office 365ってな~に? 最新のWindowsでOffice製品を適切に使うために知っていて欲しい3つのこと |
さくしま たかえ 氏 @RamuMystery Microsoft MVP for Windows and Devices for IT Windows Insider MVP |
|
|
||
データプラットフォーム最新情報をざっくり | 山本 美穂 氏 @mihochannel 日本マイクロソフト株式会社 |
|
|
||
とあるマーケティング部隊とエンジニアとScalaの導入 | @grimrose | |
とあるマーケティング部隊とエンジニアとScalaの導入 | GitHub |
|
|
|
|
Microsoft の『de:code 2018 | 開発者をはじめとする IT に携わる全てのエンジニアのためのイベント』(2018/05/22-23, Tokyo) で、C#/AI 関連の発表をしてきた。
私の資料はこちら。
※ de:code の資料の多くは次の場所で見ることができる。
詳しくは、GitHub にソースコードまたはチュートリアルで公開してある。
Microsoft MVP の有志で、下記のマイクロソフトの技術ドキュメントの日本語化を助ける活動を行っています。
オープンソースですので、日本語としておかしなドキュメントに気付いた場合は、ちょっとずつでも提案していくと良いような気がします。
富山県黒部市宇奈月温泉で、毎年恒例の勉強会「BuriKaigi2019」を開催しました。
60名の方に参加していただきました。 雪が多い季節にも関わらず、今年も遠方からも多くのスピーカーや参加者の方がいらしてくださいました。 本当にありがとうございました。
当日の盛り上がりは Twitter などで見ることができます。
『BuriKaigi2019』 |
|
---|---|
日時 | 2019/01/26(土) 12:50-18:10 (終了後懇親会) |
会場 |
|
主催・共催 |
Hokuriku.NET 北陸エンジニアグループ |
ゴールド スポンサー | Forkwell CData Software Japan |
詳細 | BuriKaigi2019 | connpass |
タイトル | 概要 | スピーカー | |
---|---|---|---|
キーノート「.NET の今 ~ 最新アップデートと 2019 年の展望」 | VS2017が登場しておよそ1年が経過したので、リリース後に追加された新機能をまとめてみましょう。 | 井上 章 氏 @chack411 マイクロソフト コーポレーション グローバル ブラックベルト |
|
|
|||
「C# ドキドキ ライブ コーディング」 | 石野 光仁 氏 @ailight 鈴木 孝明 氏 @xin9le Microsoft MVP for Developer Technologies 小島 富治雄 氏 @Fujiwo Microsoft MVP for Developer Technologies, 室星 亮太 氏 @ryotamurohoshi |
||
|
|||
「.NET Core で見る Client Apps」 | .NET Core 3.0で追加されるWindows Client Appsについての現状やマイグレーションのためのお役立ち情報を共有したいと思います。 | 森 博之 氏 @hiroyuki_mori Microsoft MVP for Visual Studio and Development Technologies |
|
「見える化、言える化、やりきれる化! Dynamics365 北陸へ拡散 Part 2」 | デジタルビジネスの勝者になる為の肝、D365/PowerPlatformを活用したデータ戦略について解説します。 |
吉島良平(室長) 氏 @navisionaxapta Pacific Business Consulting,Inc. 取締役/戦略事業推進室、Microsoft MVP for Business Solutions 2015-2017、Microsoft MVP for Business Applications 2018 杉本 和也 氏 @sugimomoto CData Software Japan Lead Engineer、Microsoft MVP for Business Solutions 2017-2019 |
|
「App Center」 | 津守 優 氏 @tmyt | ||
「Windows Server 2019」 | Windows Server の推奨管理コンソールが Windows Server 2019 より Windows Admin Center へ変更になります。新しい管理コンソールの紹介を中心に Windows Server 2019をカスタマイズしていきます。 | 澤田 賢也 氏 @masayasawada Microsoft MVP for Cloud and Datacenter Management |
|
タイトル | 概要 | スピーカー |
---|---|---|
「Oracle Code One 2018 の概要」 | JDKライセンスモデルの変更(最新版) | 伊藤 敬 氏 @itakash Oracle |
「Java on Kubernetes 全部デモ」 | 今 Kubernetes は急速な勢いでエコシステムを拡大しています。マイクロサービス開発や、新機能追加、もしくは変更が多いようなシステムにおいて Kubernetes はその威力を大いに発揮します。本セッションでは Java アプリケーションの開発から Kubernetes へのデプロイまで、具体的にどのような形で開発を進めるていくのかを、デモを通じてご紹介します。 | 寺田 佳央 氏 Yoshio Terada @yoshioterada 日本 Java ユーザ・グループ / マイクロソフト・コーポレーション |
「Project Loom - 軽量スレッドと限定継続」 | これまで、Javaのスレッドはプラットフォームのスレッドを使用してきました。これに対し、Project LoomではJVMが管理する軽量スレッドのFiberを導入します。 Fiberを利用することで、今まで実現できなかったスケールのマルチスレッドを実現することが可能です。さらに、現敵的ではあるものの、継続も導入されます。本セッションではFiberや継続の機能を紹介し、それらの実現するための仕組みについても解説します。 | 櫻庭 祐一 氏@skrb |
Project Loom - 軽量スレッドと限定継続 | Speaker Deck | ||
「おれの書いたPHPがこんなに速いわけがない(PHP7.3比3倍)」 | TruffleでPHPっぽい文法のスクリプト言語を書いてみたら、なんの努力をしなくても、かなり速くなったと言われてるPHP7.3の3倍速で動く処理系ができてしまいました。という話の詳細と、種明かしをします。 | きしだ 氏 @kis |
「Microsoft AzureではじめるJava開発」 | 大中浩行 氏 @setoazusa Microsoft MVP for Developer Technologies |
|
Microsoft AzureではじめるJava開発 | Slides | ||
スポンサー セッション 「Java クライント実装におけるAPIスタイル頂上決戦! 野良REST vs GraphQL vs OData vs OpenAPI (Swagger)」 | 皆さん、API使っていますか!? 世界のAPI Management 市場は2022年までに現在の2倍(3,000億円)の市場規模になると予想されていて、今後今まで以上にAPI を使ったマッシュアップは重要性を増すようです! でも、一口にAPIと言っても、単純にRESTという切り口だけでは収まらなくなってきました。そんな多様化され、今後ますます増え続けるAPIのマッシュアップを迅速に行うために、知っておいて損は無いJava開発者のためのAPIスタイル・エコシステムの活用方法をお伝えします! | CData Software Japan 杉本 和也 氏 @sugimomoto CData Software Japan Lead Engineer、Microsoft MVP for Business Solutions 2017-2019 |
タイトル | スピーカー |
---|---|
スポンサー ビデオ | Forkwell |
「オンラインIDEで爆速オンボーディングと、サンプルコード共有」 | 池原 大然 氏 @Neri78 |
「2018年ダイジェスト」 | 金子雄一 氏 @oskaneko Microsoft MVP for Enterprise Mobility |
「プログラミング教育必修化(仮)」 | ToshiyukiKashio 氏 |
プログラミング教育必修化 | Speaker Deck | |
「公共交通でのオープンデータ活用(仮)」 | TOMINARI_Takayuki 氏 |
「micro:bit さわってみた。」 | 蜜葉 優 氏 @mitsuba_yu |
「Friendly と TestAssitantPro を使った Windows アプリテスト自動化」 | 石川 達也 氏 @StoneGuitar777 Microsoft MVP for C# |
「消費税軽減税率制度」 | Hajime Mugishima 氏 @mugi_uno |
消費税軽減税率制度 | Speaker Deck | |
某500万リツイートのお話。ツイッターAPI の話などなど。 |
杉本 和也 氏 @sugimomoto CData Software Japan Lead Engineer、Microsoft MVP for Business Solutions 2017-2019 |
|
|
※ 「『2019 Microsoft MVP Global Summit』に参加してきた | 出発前~ポートランド編 (2019/03/15-17)」の続き。
『2019 MVP Global Summit』に参加してきた。
13回目の参加となる。
マイクロソフト には、MVP (Microsoft Most Valuable Professional) というアワードがあるが、約1年に一度、世界中の MVP が米国ワシントン州レドモンドにあるマイクロソフト本社およびその近くに招待され、大規模な技術カンファレンスが行われる。
毎年2000名規模の MVP および Regional Directors が世界中から参加する。今回日本からは100名余りが参加した。
たくさんのセッションでマイクロソフトのプロダクト チームの方々から直に話を聴くことができた。 また、マイクロソフトの方や世界中の MVP と交流を持つことができた。 貴重な機会をいただいて、本当に感謝している。
シアトル・タコマ国際空港 に到着。
空港から滞在先の Hyatt Regency Bellevue に移動。
Summit のレジストレーションを済ませる。
近くで買い物。
ベルビュー スクエア にある Microsoft Store で Surface Go 用のアクセサリーを購入した。
ホテル近くのスーパー マーケット「QFC (Quality Food Centers)」へ。
夜はアジアの MVP の皆さんとのパーティなど。
ホテルで朝食後、マイクロソフトが用意してくれたバスでマイクロソフト キャンパスへ。
マイクロソフト本社の広大な敷地内にあるカンファレンス センターなどで、たくさんの本格的な技術セッションが3日間に渡り、英語で行われる。 セッション内容は秘密保持契約の関係で公開できないため、以下は差しさわりのない食事の風景などに限られる。 技術的な内容に一切触れないので、ただ飲み食いして遊んでいるだけのように見えるかも知れないが、けしてそうではないのでご安心願いたい。
会場内では、自由にコーヒーや炭酸飲料、軽食などをとることができる。
ランチ。
マイクロソフト キャンパス内にある Company Store や Microsoft Visitor Center を訪れた。 ここには、マイクロソフトの最初のソフトウェアのソースコードや最初のハードウェアなども見ることができる。
夜はパーティ。
この日もホテルで朝食後、マイクロソフト キャンパスへ。
今回は C# のプログラムマネージャの Mads Torgersen 氏と写真を撮らせてもらった。 彼は今回で14回目のサミットだと言っていた。
キャンパスでのランチは、毎年どんどん美味しくなっている。
会場の近くの Microsoft Treehouse も見てきた。
夜はパーティの後、近所のレストランで夕食。
この日もホテルで朝食後、マイクロソフト キャンパスへ。 米国に来てだいぶ経つのに、時差ボケが残っている。
英語を聞き続けると、脳に睡眠導入ホルモンが生じる病気になってしまったかもしれない。
— Fujio Kojima (@Fujiwo) March 20, 2019
相槌に “Yes.”、“No.”、“Right.”、 “Okay.”、”I agree.” ばかり言ってるので、ネイティブな人がよく使う次のやつも使ってみたい。
— Fujio Kojima (@Fujiwo) March 20, 2019
“Make sense.”
“Kind of.”
“I don’t think so.”
“Not at all.”
最後の夜もパーティ。 ほとんどの参加者が集まる最大のパーティーだった。
※ 続きは、「『2019 Microsoft MVP Global Summit』に参加してきた | シアトル~帰国編 (2019/03/21-23)」。
※ 「『2019 Microsoft MVP Global Summit』に参加してきた (2019/03/17-21)」の続き。
『2019 MVP Global Summit』の後、ワシントン州シアトル ダウンタウンに移動して一泊したので、その様子を記しておきたい。
MVP の尾崎さんと Uber でシアトルへ移動し、ホテルにチェックイン。
先ずは、定番の Pike Place Market へ。 現存するマーケットでは全米で最も歴史が長く、シアトルで一番の人気観光スポット。
次に、シーフードが美味しい Ivar’s に向かった。 ここのクラムチャウダーは毎年外せない。とても美味しい。
T-Mobile Park (旧 Safeco Field) の壁には、この日引退したイチローの姿が。
市内では、あちこちで Lime の自転車や車を見かけた。 これは、スマートフォン アプリで借りられて、好きなところに乗り捨てできるサービスだ。
次に、Living Computers: Museum + Labs に行った。 ここは初めて訪れたが、歴史的な古いコンピューターが沢山置いてある。 Xerox の Alto や Apple I、Lisa などが、実際に動いていて、触ることもできるのが感動的だった。
ディジタル・イクイップメント・コーポレーション のメインフレームなども沢山動いていた。
Uwajimaya へ。 ここには、日本の食材が大量にある。
ここには、紀伊国屋書店もある。
最後の晩の食事は、MVP の尾崎さん、亀川さん、杉本さんと Metropolitan Grill でステーキ。 今まで食べたステーキの中で一番だった。
翌朝、シアトル・タコマ国際空港へ、シアトルの公共交通機関である SoundTransit の Link light rail で移動。
空港の Ivar’s がなくなってしまったので、別のシーフード店で朝食。
搭乗した。
— Fujio Kojima (@Fujiwo) March 22, 2019
機内では、映画を観て過ごした。
帰りの飛行機で隣の人が、食事や甘いものを全部断ってエコノミーなのにスコッチを6回くらい頼んでて、ちょっと面白かった。
— Fujio Kojima (@Fujiwo) March 23, 2019
成田まで帰ってきた。後5時間くらいで帰宅。
— Fujio Kojima (@Fujiwo) March 23, 2019
7泊9日の米国の旅から帰宅。
— Fujio Kojima (@Fujiwo) March 23, 2019
20時間以上掛かった。
『2019 MVP Global Summit』に参加する前に、オレゴン州ポートランドに行ってきた。
ポートランドには 2016年 MVP Global Summit のときから毎年訪れていて、今回で3回目。とても素敵な街だ。
アメリカ合衆国オレゴン州最大の都市で、全米一住みやすい都市と言われている。 シアトルに比べると都市部がこじんまりしている。
次のような特徴がある。
出国前からポートランド滞在までを記しておきたい。
来週 Microsoft MVP Global Summit というのがあるのか。https://t.co/R2yM6G2HC2
— Fujio Kojima (@Fujiwo) March 13, 2019
参加してみようかな。
今週の渡米に備えて、英会話をマスターした。
— Fujio Kojima (@Fujiwo) March 13, 2019
(C++ をマスターした的な意味で)
米国でネットに接続する方法は、フリーWiFi、海外Wi-Fiレンタル、国際ローミングなど、いくつかの方法があるが、今回は、MOST SIM の T-Mobile 版 を日本の Amazon で購入し、iPhone 6s で使用した。 12日間使い放題で3千円台だった。
日本にいる間に業者にアクティベーションしてもらい、行きの機内で入れ替えた。 SIMカードを取り出すためのピンも付属していた。
特に設定もプロファイルの削除や設定も必要なく、そのまま快適に LTE で接続することができ、とても快適だった。 電話番号や SMS も使え、テザリングも問題なかったのでお薦めだ。
PC は、Surface Go を渡米前日にセットアップし、持って行った。 小さくて軽くてとても快適だった。
出発。
羽田から成田へ移動中。
— Fujio Kojima (@Fujiwo) March 15, 2019
成田空港でビールを飲みながらフライト待ち中。
— Fujio Kojima (@Fujiwo) March 15, 2019
顔認証ゲートから出国した。
— Fujio Kojima (@Fujiwo) March 15, 2019
搭乗した。
— Fujio Kojima (@Fujiwo) March 15, 2019
ポートランドは Trimet や Portland Streetcar などの公共交通機関がとても便利で、縦横無尽に走った路面電車やバスなどが一日5ドルで乗り放題になっている (2時間なら2.5ドル)。 今回は、日本の Suica などにあたる hop カードを利用した。
ホテルにチェックインした後、「日本国外にある中でも最も美しく本格的な日本庭園」と言われている Portland Japanese Garden へ行った。
その後は、市内を見て回ったり、スーパーで飲食物を買ったり、夕食に行ったりした。
世界のベストビール都市第1位に輝いたポートランドの市内には70以上のブルワリーがあって、その数も世界一だ。
翌朝、毎週土曜日に開催される ポートランド州立大学 の Formers Market へ。 市内あちこちで開かれる Formers Market だが、ここのが一番大規模だ。 様々な地元のオーガニック フードなどが楽しめる。
次に Pioneer Place へ。 ここには、Microsoft Store や Apple Store もある。
市内を散策。
夕方は、ブルワリーとディスティラリーの見学ツアーに参加。 自分以外はカナダ人4人だった。
その後は、次の日から一緒に 2019 MVP Global Summit に参加する MVP の杉本さんと一緒に食事をした。
シアトル行きに乗るべくポートランド国際空港へ移動。
空港でも DESCHUTES BREWERY のビールを楽しむことができる。
※ 続きは、「『2019 Microsoft MVP Global Summit』に参加してきた (2019/03/17-21)」。
毎年恒例の BuriKaigi などで4人でやっている C# ライブ コーディングセッションを Microsoft の de:code 2019 でやります。
毎年恒例の BuriKaigi などで4人でやっている C# ライブ コーディングセッションを Microsoft の de:code 2019 でやってきました。
#decode19 のドキドキ・ライブコーディングに来ていただきありがとうございました #MW51
— むろほし (@RyotaMurohoshi) May 30, 2019
準備はドキドキというかビクビクで、本場中も内心ヒヤヒヤだったのですが、楽しんでいただけたようでとても嬉しいです!
大舞台に連れてきてくださった大先輩方、そしてチャックさんありがとうございました。 pic.twitter.com/rib8sRT9r8
いつもお世話になっているマイクロソフトの井上章 (@chack411) さんのお誘いで実現したものです。 おかげさまでとても素敵な体験になりました。 もちろん、毎回楽しい企画をしてくださる石野 (@AILight) さん、一緒に解答者として登壇した鈴木 (@xin9le) さん、室星 (@RyotaMurohoshi) さん、にも感謝です。
今回は、Blazor を使用したプログラミングがお題で、次の2つをやりました:
「七並べ」対決の私の作戦は次の通りです:
99名の皆様が聴きにきてくださり、大変に盛り上がってくださいました。 本当にありがとうございました!
当日の盛り上がりの様子の一部は、以下のリンクで読むことができます。
de:code 2019 の動画や資料などはこちらから。 (ただし、我々のセッションのものはありません)
アンケート結果の NSAT (Net Satisfaction) がとても良かったので、メモ。
NSAT | |
---|---|
講師 | 198 |
コンテンツ | 193 |
全体 | 195 |
参考: NSAT の求め方
- Positive の比率 (パーセンテージ) = P
- 4段階評価、5段階評価の場合: 1番上の評価をした人の比率
- 10段階評価の場合: 1番上と上から2番目の評価の比率の合計
- Negative の比率 (パーセンテージ) = N
- 4段階評価の場合: 下2つの合計
- 5段階評価の場合: 下3つの合計
- 10段階評価の場合: 下4つの合計
- NSAT = P - N + 100 (200点満点、平均100点)
Microsoft MVP Award を再受賞しました。15年目になります。
いい歳の大人をこうして褒めてくれるマイクロソフトに感謝です。
Microsoft MVP となって、多くの素敵なITエンジニアの皆様との交流の機会が増えました。 皆様いつもありがとうございます。
昨日 (2019/09/23)、.NET Core 3.0 正式版がリリースされた。
現在これに関連して、.NET Conf 2019 というオンライン カンファレンスが開催されている。
一部は、YouTube で見ることができる。
Microsoft 製の新たな .NET である .NET Core だが、.NET Core 3.0 では、Windows デスクトップ アプリケーション (Windows Forms と WPF) がサポートされた。
.NET Core 3.0 のためのソフトウェアの開発には、現在 Visual Studio 2019 の最新版である Ver.16.3.0 が必要だ。
Visual Studio Installer からアップデートできる。
但し、現在 .NET Core の Windows Forms のデザイナーには未対応であるようだ。
(なお .NET Core は、最初の .NET である .NET Framework に比べて様々な最適化がなされているため、より高パフォーマンスとなっている)
.NET 5 は、.NET Core 3 の次期バージョンだ。
現在 3つある Microsoft の .NET (.NET Framework, .NET Core, Xamarin) が 1 つに統合される予定。
これ以降、.NET Framework は保守のみとなり、新機能は追加されなくなる。
C++/CLI は .NET Core 3.1 でサポートされる予定だ。
新たな ASP.NET (.NET での Web アプリケーション プラットフォーム) のフレームワークである Blazor だが、サーバーサイドの Blazor は、既に .NET Core 3.0 でサポートされている。
サーバーサイドの Blazor は、C# と Razor 構文でコーディングでき、クライアント サイドとの同期はフレームワークによって行われる (内部的には JavaScript を用いて SignalR で通信) という技術だ。
クライアント サイド Blazor (Blazor WebAssembly) は、WebAssembly で .NET が動作し、C# のアプリケーションがクライアント サイドの Web ブラウザ上で動作するというユニークなものだが、こちらは、現在まだ Preview 版。
2020年5月のリリースが予定されている。
C#8.0 がリリースされた。
null 許容参照型や非同期ストリームなどがサポートされる。
非同期ストリームでは、非同期型の IEnumerable
※ 「.NET Core 3.0 正式版リリース」の続き。
先日 .NET Core 3.0 が正式にリリースされ、C# 8.0 が使えるようになった。
一方で、.NET Framework の最新版は 4.8 だ (2019/09/25 現在)。
現時点での、最新版の .NET を使用する方法をまとめてみよう。
下記からインストール。
※ Developer pack と Language pack の両方
Visual Studio Installer で Visual Studio 2019 Ver.16.3 以上にバージョンアップ
最新版の Visual Studio に Blazor テンプレートのインストールが必要
次を参照
プロジェクト ファイル (*.csproj など) に <LangVersion>8.0</LangVersion> を記述
(null 許容参照型も有効にするには <Nullable>enable</Nullable> も記述)
例.
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
...
<PropertyGroup>
...
<LangVersion>8.0</LangVersion>
<Nullable>enable</Nullable>
...
※ .NET Core 3.0 アプリケーションのデフォルトの C# のバージョンは 8.0
I wrote a simple library for reading and writing CSV (the Values Comma-Separated or Character-Separated Values).
The csv format file is sometimes necessary because it can be displayed / edited with Excel and is simple. There are other libraries that read and write csv already, but I tried to make it simpler.
With this library, you can read and write plain object collections (IEnumerable<Something>) in csv format.
First, prepare something IEnumerable<TElement>:
An IEnumerable<TElement> something IEnumerable<ToDo> toDoes = new ToDoList();
For example this class:
class ToDoList : IEnumerable<ToDo> // sample collection { public IEnumerator<ToDo> GetEnumerator() { yield return new ToDo { Id = 1, Title = "filing tax returns", Deadline = new DateTime(2018, 12, 1) }; yield return new ToDo { Id = 2, Title = "report of a business trip", Detail = "\"ASAP\"", DaySpan = new DaySpan(3), Priority = Priority.High }; yield return new ToDo { Id = 3, Title = "expense slips", Detail = "book expenses: \"C# 6.0 and the .NET 4.6 Framework\",\"The C# Programming\"", Priority = Priority.Low, Done = true }; yield return new ToDo { Id = 4, Title = " wish list ", Detail = " \t (1) \"milk\"\n \t (2) shampoo\n \t (3) tissue ", Priority = Priority.High }; } IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); }
You can write this as:
const string csvFileName = "todo.csv"; await toDoes.WriteCsvAsync(csvFileName);
The resulting csv file looks like this:
Id,Title,Deadline,Done,Priority,Details,DaySpan 1,filing tax returns,2018/12/01 0:00:00,False,Middle,,0 2,report of a business trip,2017/07/12 13:13:01,False,High,"""ASAP""",3 3,expense slips,2017/07/12 13:13:01,True,Low,"book expenses: ""C# 6.0 and the .NET 4.6 Framework"",""The C# Programming""",0 4, wish list ,2017/07/12 13:13:01,False,High," (1) ""milk"" (2) shampoo (3) tissue ",0
You also can read a csv file like this:
IEnumerable<ToDo> newToDoes = await CsvSerializer.ReadCsvAsync<ToDo>(csvFileName);
Public properties with both "get" and "set" of each element in the collection are read and written to csv files.
When writing, it is converted to a string with the "ToString()" method regardless of the type.
When reading, the string type is left as is, and enum (enumeration type) is read as the value of the string. For other types, it tries to change the string to a value using "TryParse" or "Parse". Types that cannot do either of these will not be read.
Properties that have both "get" and "set" and are of one of the following types are read:
For example, the above ToDo class is like this:
// Sample element type // Properties marked with ✔ will be read / written // Properties marked with ✖ won't be read / written class ToDo { public int Id { get; set; } // ✔ public string Title { get; set; } = ""; // ✔ public DateTime Deadline { get; set; } = DateTime.Now; // ✔ public bool Done { get; set; } // ✔ public Priority Priority { get; set; } = Priority.Middle; // ✔ user-defined enum [ColumnName("Details")] public string Detail { get; set; } = ""; // ✔ [ColumnName ("Details")] changes the column name in the csv file to "Details" public DaySpan DaySpan { get; set; } // ✔ User-defined type with "Parse" (without "TryParse") [CsvIgnore()] public string Option { get; set; } = ""; // ✖ Ignored because [CsvIgnore()] is attached public string Version => "1.0"; // ✖ Ignored because it is only a get property }
The user-defined types used in the above ToDo class are as follows:
// User-defined enum example enum Priority { High, Middle, Low } // Example of a user-defined type with "Parse" (without "TryParse") struct DaySpan { public int Value { get; private set; } public DaySpan(int value) => Value = value; public static DaySpan Parse(string text) => new DaySpan(int.Parse(text)); public override string ToString() => Value.ToString(); }
csv can be read and written even if it has no header part (eg the first line of the csv file above).
However, if there is a header part, it can be read by collating the header part even if the column is switched, but if there is no header part, it cannot be read if the column is switched.
Example of writing without a header:
await toDoes.WriteCsvAsync(csvFilePathName: csvFileName, hasHeader: false);
Csv file created:
1,filing tax returns,2018/12/01 0:00:00,False,Middle,,0 2,report of a business trip,2017/07/06 18:08:13,False,High,"""ASAP""",3 3,expense slips,2017/07/06 18:08:13,True,Low,"book expenses: ""C# 6.0 and the .NET 4.6 Framework"",""The C# Programming""",0 4, wish list ,2017/07/12 13:13:01,False,High," (1) ""milk"" (2) shampoo (3) tissue ",0
To read a csv file without a header:
IEnumerable<ToDo> newToDoes = await CsvSerializer.ReadCsvAsync<ToDo>(csvFilePathName: csvFileName, hasHeader: false);
The character code can be changed (default is UTF8).
CsvSerializer.Encoding = Encoding.GetEncoding(0);
The delimiter can also be changed (default is ',').
CsvSerializer.Separator = '\t';
You can also use stream instead of file name.
using (var stream = new FileStream(csvFileName, FileMode.Create)) await collection.WriteCsvAsync(stream);
You can leave the stream open after reading or writing by specifying leaveOpen.
using (var stream = new FileStream(csvFileName, FileMode.Create)) await collection.WriteCsvAsync(stream: stream, bufferSize: 1024, leaveOpen:true, hasHeader: true);
In addition to asynchronous methods, there are also synchronous methods.
toDoes.WriteCsv(csvFileName);
These libraries are open to NuGet and can be installed from Visual Studio.
Source code is available at:
The projects included are:
日本時間で昨日、「.NET Conf: Focus on Blazor」というオンライン カンファレンスが開催され、Blazor に関する新たな発表がなされた。
現在、セッション動画を観ることができる。
Blazor は、C#/.NET で Web アプリケーションを開発できるプラットフォームとして、.NET Core 3 で正式リリースされた。
従来の ASP.NET ではサーバーサイドを C#/.NET、クライアントサイドを JavaScript (や TypeScript) で記述していたが、Blazor ではサーバーサイドとクライアントサイドを統一した言語 (C#) で記述できるのが大きなメリットだ。これにより、単一のコードでモデルが記述できるようになる。
今回、Blazor に関して、以下のように多くの新技術が発表になった:
毎年冬に富山で開催されている BuriKaigi (*)。
今回も、90名もの方にご参加いただき、大いに盛り上がりました。
昨年までは2トラックでしたが、今回は3トラックになりました (ルームA, B, C)。
リンクなどをまとめておきます。
(*) BuriKaigi とは何かということは次に詳しく書かれています: Burikaigi という毎年冬のイベント | hikaruworld | Medium
『BuriKaigi 2020』 | |
---|---|
日時 | 2020年2月1日(土) 13:00〜19:00 (終了後懇親会) |
会場 | 富山県民会館 (懇親会会場: 割烹 扇) |
#BuriKaigi #BuriKaigiC https://t.co/rSCc4Nqcea pic.twitter.com/BvdLN5oLKa
— Fujio Kojima (@Fujiwo) February 1, 2020
#BuriKaigi #BuriKaigiC 森さんのジョシュア ツリーのお話。 https://t.co/LO2rNRe5pt pic.twitter.com/oegNAwpRAg
— Fujio Kojima (@Fujiwo) February 1, 2020
#BuriKaigi #BuriKaigiA https://t.co/srTUaBsInF pic.twitter.com/4fpqBZMK7D
— Fujio Kojima (@Fujiwo) February 1, 2020
#BuriKaigi #BuriKaigiA マイクロソフト井上章さんのキーノート。なんと Windows DNA のお話から。 https://t.co/jALoFO3lKy pic.twitter.com/56lVsuXH8m
— Fujio Kojima (@Fujiwo) February 1, 2020
#BuriKaigi #BuriKaigiA 室長さんと杉本さんの Dynamic なセッション。 https://t.co/vmNnjYs6Qz pic.twitter.com/vbn4YDXJtx
— Fujio Kojima (@Fujiwo) February 1, 2020
ブリ会議恒例!
— Kazuya Sugimoto @CData Software Japan (@sugimomoto) February 1, 2020
ドキドキライブコーディング!
満員御礼!
#burikaigi pic.twitter.com/VzUQ33vHEP
ライブコーディング合戦始まった#burikaigi #burikaigiA pic.twitter.com/kueuMIJIoq
— ちゅき[2/8 わんくま大阪ですよー] (@Chuki) February 1, 2020
#burikaigi #burikaigiA
— あると沙樹/Studioさきあると (@sakiaruto) February 1, 2020
C# ドキドキ ライブ コーディング!!
石野 光仁 氏 @ailight
鈴木 孝明 氏 @xin9le
小島 富治雄 氏 @Fujiwo
室星 亮太 氏 @ryotamurohoshi pic.twitter.com/5sMYfcgop2
ドキドキライブコーディング楽しい!#burikaigi #burikaigiA pic.twitter.com/9EgBOYB4Pj
— jun@Jun-nyan(sɹǝunɾ) (@juners) February 1, 2020
まさかの写真入れ替えに対してCPU負荷で小島さんが逆転!#burikaigi #burikaigiA pic.twitter.com/PKTkRdn6l3
— jun@Jun-nyan(sɹǝunɾ) (@juners) February 1, 2020
#burikaigi 石野さん(@AILight )たちによる「C# ドキドキ ライブ コーディング!!」。後半は4名の方々が事前に作ってきたプログラムによるスピード対決です(゚∀゚)
— オーニシ@2/15富山IT勉強会Zabbix入門 (@onishi_feuer) February 1, 2020
みんなしてチートしてておもろいwww pic.twitter.com/fBfOKF1nWX
スライド写真多めですが、勉強会編もアップします(ほぼ未選別)
— jun@Jun-nyan(sɹǝunɾ) (@juners) February 2, 2020
#burikaigi
2020/02/01 Burikaigi2020 勉強会編https://t.co/D7m394hKGR
アップロード終わったのでまずは 懇親会の写真をアルバムで上げます。 #burikaigi
— jun@Jun-nyan(sɹǝunɾ) (@juners) February 2, 2020
2020/02/01 Burikaigi2020 懇親会編https://t.co/p7TWVCnYds
#BuriKaigi 懇親会の様子。 https://t.co/bMwt6cpmw0 pic.twitter.com/wJ3ETkK0Ln
— Fujio Kojima (@Fujiwo) February 4, 2020
ブリしゃぶの準備#burikaigi pic.twitter.com/8ftFGcZIfv
— なぎせ ゆうき (@nagise) February 1, 2020
これは良い幻想的#BuriKaigi pic.twitter.com/ZqeKexvxzK
— りなたむ MVP 🇯🇵 / Ryota / #PowerAddict (@R_t_A_n_M) February 1, 2020
#BuriKaigi https://t.co/NLyWTsezf7 pic.twitter.com/CeNsOh4wjx
— Fujio Kojima (@Fujiwo) February 1, 2020
「C#の新機能勉強会 ~C#7、8の新機能を活用して速く安全なプログラムを書こう~」の資料をアップした。
C#7、8 では、struct (値型) 関連の便利な機能追加が多いので、その辺りに注力してみた。
マイクロソフトの最新の技術について、以下のようなオンラインイベントで情報が提供されました。
これらのイベントで公開された/公開予定の技術についてご紹介いたします。
以前から発表されている通り、次の .NET では次のようになります。
"One .NET" ということで、.NET が現在の .NET Core に統合され、.NET 5 となります。
※ .NET 5 に含まれないもの
ちなみに .NET Framework は 4.8 が最終バージョンです。今後は保守フェーズに入り、機能の新規追加はされなくなります。
.NET Framework のサポート自体は Windows 10 がサポートされている間は続くと思われます。
将来性から考えると、新規に .NET で開発する場合は、.NET Core を用いるべきだと考えられます。
上記のとおり、現在ある .NET Framework、.NET Core、Xamarin は一つの .NET に統合されます。
但し、2020 年 11 月の .NET 5 リリース時点では Xamarin の統合はプレビュー扱いです。
.NET 6 で完全に統合される見込みです。
.NET 6 は LTS (Long term support: 長期サポート) 版となる予定なので、新規開発に利用するのは、.NET 6 からが無難かも知れません。
Microsoft は既に Windows への囲い込みを行っていませんので、新たな開発方法としては Windows 以外の OS もサポートするマルチ プラットフォームなものを提供していくことになります。
注目すべきは、以下の二つです。
将来的にはどちらもネイティブアプリケーションも開発できるものですが、MAUI は従来の Windows アプリケーション開発技術の延長にある技術、Blazor は Web アプリケーション開発技術の延長にあり、とりあえずは Web 開発用です。
ひとつずつ説明します。
MAUI というアプリケーション開発技術が発表されました。
MAUI の特徴は次の通りです :
XAML (Extensible Application Markup Language):
現在すでに Blazor Server という、C# だけで Web アプリケーションが開発できるサーバーサイド技術が .NET で使用できますが、この技術が拡張され、Web アプリケーションだけでなくネイティブアプリケーションも作成できるようになることが予定されています。
Blazor の特徴は次のとおりです:
今回新たにリリースされたのは、Blazor WebAssembly 3.2.0 (GA) です。
近い将来この Blazor が拡張され、ネイティブ アプリケーション (Web アプリケーションでない Windows アプリケーション/Mac アプリ /iOS アプリ/Android アプリ) も作れるようになります。
新たな Windows アプリケーション ライブラリーとして WinUI 3 が発表になりました。
以下が発表されました:
Web 版 (クラウド版) の Visual Studio Code/Visual Studio です。
Visual Studio Codespaces は、以前 Visual Studio Online と呼ばれていたものです。
ネイティブアプリケーションだった Office が Web 版になり、Microsoft 365 になったように、様々なツールがクラウド版になっていきます。
C# 9.0 が発表されました。
Serverless や AI/Data などを中心にアップデートされています。
Free Tier
AutoScale
Azure Synapse Link: データベース分析サービス
Power Apps や Power Automate、Power BI、Dynamics 365、Microsoft 365 の新たな統合機能の発表
Microsoft Teams の多数のアップデートの予定が発表
・ Windows Terminal 1.0
次のような機能があります。
Microsoft Store からインストールできます。
タブごとに Windows Subsystem for Linux、コマンドプロンプトや PowerShell など
タブの内部をペインに分割する機能
WSL は、Windows に組み込まれた virtual な Linux 環境です。
Microsoft Store からインストールできます。
WSL2 で新たに GPU がサポートされます。 CUDA 利用できたり、GPU 版 Tensorfow が動作できたりします。
winget という Windows Package Manager (Preview 版) がリリースされました。
Linux のようにコマンド ラインから簡単にアプリケーションなどがインストールできます。
参考:Windows Package Manager Preview | Windows Command LineProject Reunion というプロジェクトが発表されました。
Windows の API は現在次のように 2 つ ありますが、これを統合しようというプロジェクトです。
YouTube で xin9le さんとコーディング ライブをやりました。
お題は、C# で連結リスト LinkedList<T>。
テストを書きながら少しずつ実装していくというものになっています。
Visual Studio の Live Share と Live Unit Testing を使い、2人で快適にコーディングすることができました。