kazuk は null に触れてしまった

C# / .NET 系技術ネタ縛りでお送りしております

カテゴリーアーカイブ: C#

async/await はTaskには依存してないです


(追記)mataliloさんに await は依存してないけど async はっ?てツイッターで指摘されて気づきました。async は依存してます。メソッドの内容を Task に包みます。async はそういう意味で現状存在するTaskという選択肢にデフォルトで依存しています。単純に試した範囲内で同一名前空間、同名のクラスに+いくらかのメソッドというレベルの実装では置き換えできませんでした。すいません。(/追記)

 

F#の立ち位置 – 猫とC#について書く代わりにHaskellとF#について書くmatarilloの日記 – haskell

を読んで、色々とちょっと待ってよ感があったので書いてみます。

C#は色々詰め込みすぎってのはまぁそう。色々詰め込みすぎた結果として直行性を失ってる部分もある事はある。

ラムダの中で yield できないとかそういうのは確かに直行性を失ってる。catch ブロック内で async 使えないとかもその通り。

んでもね、async/await が Task にべったりくっついてて剥がしようがないってのは間違った話なんでそれを元に話が膨らんでいくのはちょっと待てなわけです。

 

さて、async/await を適用する場合の要件とかは以下の記事で書いてある通りです。自分が読む限りで間違いはありません。

連載:C# 5.0&VB 11.0新機能「async/await非同期メソッド」入門(最終回):第3回 非同期メソッドの内部実装とAwaitableパターンの独自実装 (2/2) – @IT

bleis さんは 並列/並行基礎勉強会でasync/awaitをDisってきた – ぐるぐる~ で「現状の async/await が Task と結びついているのは、Awaitable パターンが要求するシグネチャに戻り値の型に対する規定がないことから来ています。 つまり、Awaitable パターンを実装する Awaitable/Awaiter に「Task」が出てこないのに、裏で勝手に「Task」にラップされてしまうのです。」と書いてますが、そんな事は起こりません、やられていません。

順を追って確認しましょう。

メソッドの戻り値型であるTaskは Awaitable として返された物で Awaitable の利用方法である Awaiter の取得に答えます。要するにメソッドの戻り値として素直に戻り何もラップ等はされません。TaskはAwaitableとして利用するのに必要十分なメソッド(GetAwaiter)を実装していますのでそのようなラップ操作も必要ありません。 (Task<TResult>.GetAwaiter Method)

Task を返しているのはメソッドその物で、C#は GetAwaiter およびその後のAwaiterに言語仕様でのawaiterを満たすメンバが存在する為 await が適用可能であると判定しているだけです。

さて、await が適用されるとC#は Task の GetAwaiter を呼びだすコードを生成します。TaskのGetAwaiter はランタイムライブラリの TaskAwaiter<T> (System.Runtime.CompilerServices namespace)を返します、TaskAwaiter は Task を Awaiter としてラップする為のメソッドを実装しています。 .NET Framework ランタイムライブラリが Task を Awaiter としてTaskAwaiter でラップしているのであり Task でラップされているのではありません。そしてC#は TaskAwaiter を言語仕様で決めた通りに Awaiter として利用しているだけです。

C#言語はもう限界かもといってる文脈で出てくる事柄が何を何がラップするのかのされるの係り受けが違うし、C#言語でなくランタイムが主体だし、勝手にと言ってる事がメソッドシグネチャ通りにTaskAwaiter<T>を返したりって事で、問題の一文はぶっちゃけマルっと違います。

 

そして、TPL が C# での async/await に便宜を計ったのは Task のGetAwaiter ただ一点これだけで async/await の使い方でよく出てくる ContinueWith その他は async/await 登場前の C# 4.0 / .NET 4.0 から存在します。(Task クラス (System.Threading.Tasks) – .NET Framework 4.0) 、async/await の為にランタイムライブラリに追加されたのは TaskAwaiter であり Task を Awaiter として扱う為にラップします。

このように TPL とランタイムライブラリの間も過度に依存しないように実装されており、TPLの寿命という問題でC#言語の言語仕様そのものは揺るがないでしょう(ユーザーコードはいっぺんにTPLと共に寿命を迎えてしまう事になりますが)。C#の言語仕様そのものは TPL には全く依存性は無く、TPLとランタイムの間は C# 言語仕様での async/await のサポートの為に必要最小限に近い良く設計された依存性を持っていると思います。

 

主語がC#言語なのかランタイムなのか、TaskでラップするのかTaskがAwaiterにラップされるのか裏で勝手になのかシグネチャ通りやんなのか、数点の違いですが違うものは違いますという事で、盛り上がってる人達には落ち着け落ち着けと。んで、話題上で関連するランタイムとTPLがべったりくっついてて剥がしようがないとかもなく、良く設計された依存性でつながっている様に自分には見えますって事で、妄想をマジ受けし過ぎなんじゃないでしょうか。

んでC#は直行性とか一貫性を失ってる部分も無いわけじゃないけど、C#がダメだの限界だとかの話は根っこの理解違ってての話が広まるにも程があるだろ程度には看過しがたい状態かなと

Doc of code 販売開始のお知らせ


 

滅多にやらない宣伝記事が増えまして申し訳ありませぬ。

というわけで Doc<Code> については最後の宣伝記事になるでしょう。

販売サイトがオープンしましたので、「買ってくれ!」以上のこの記事には意味はございません。

http://kazuk.azurewebsites.net/

 

んで、今月中はキャンペーンを設定します。

日本時間での今月中にライセンスを購入いただくと、ライセンスの付与期間が倍でライセンスキーが発行されます。

1年ライセンスを買っていただくと 2年後に無効になるライセンスキーが送られるという事になります。

販売初期限定という事で、どうかよろしくお願いいたします。

 

会社等での購入をお考えの方は kazuk.dll@kazuk.jp にご連絡ください。見積書、納品書による対応のほか、銀行振り込みでの入金その他は現状メールでの対応とさせて頂きます。

Doc<Code> というツールをリリースします


技術特化 blog なんで、滅多にやらない事なのですが、宣伝させてください。

宣伝かよ

はい、宣伝です。軽く失注続きで本気にこのままだと路頭に迷う事態なので一人ならどうという事は無いのですが嫁子供居る身なので必死なわけで、個人で作って使ってるツールの類に値段を付けて並べてみようかとの第1弾です。

タダで手に入る物で全部回して主義の方はこの先読んでも意味ないのでお帰り下さい。

Doc<Code> “doc of code” とは

.NET Framework アセンブリとその XML ドキュメントファイルを投げ込むとドキュメントを閲覧できる ASP.NET アプリケーションです。

同様なXMLドキュメントコメントの整形を行うアプリケーションとしては Sandcastle があります。Sandcastle プロジェクト自体は現状 Sandcastle Help File Builder でメンテナンスされているようですが、若干プロジェクトに不安定要素があり、ちょっとややこしい事になっているようです。

Sandcastle との違いは、あちらはビルドプロセスでのバッチ処理でHTML、およびヘルプファイルを作成する枠組みですが、Doc<Code> では、ASP.NET アプリケーションとして要求に応じてアセンブリを随時解析してXMLドキュメントと結合表示する仕組みですので、簡単に言えばビルド時に XMLドキュメントファイルの生成以上の追加処理を必要としません。結果的にビルド時間に対するインパクトを殆ど0にする事ができます。

要するにスロービルドの原因である、誰も見ないようなドキュメント生成を、オンライン解析変換処理にする事で開発スループットをより伸ばせるツールという事です。

インストールに必要な物は?インストールの手順は?

IIS 7.5 以降(Express でも可) 、 .NET Framework 4.5が必要です。 VS2012で普通に開発してる環境なら入っている物ですね。

DocOfCode の紹介とインストールデモ等

普通に NuGet からパッケージ入れるだけで入ります。

どういう使い方が想定されていますか?

普通に開発現場でドキュメントを見る用途、および、オープンソースな物を作ってる人がAPIリファレンスをインターネット上に公開するとかに使って良い様に作っています。

インターネット公開サイトに使う場合に、アセンブリを勝手にアップロードされないようにしたい等は、AssemblyController とその関連Viewそのものを外してしまって下さい。(この場合、アップロードはファイルを直接 App_Data に配置する事でもできますし、アセンブリの一覧管理はプロバイダになっていますので、プロバイダを実装する事で管理方法そのものを変える事ができます)

表示のデザイン等に関わる View および、それに当たる css 等はすべてパッケージ内にありますので、NuGet パッケージを取りこんだうえで修正してもらえれば幾らでもカスタマイズできると思います。

使い方

単純に動かすと能書きの書かれた Index ページの上部にアセンブリと設定のリンクがありますので、アセンブリを選んでもらうと、アセンブリをアップロードするためのページへ遷移します。

アップロード時に指定するタイトルはそのままアセンブリの情報ページのパスとなります、アセンブリ内の名前空間や型、メンバ情報はすべてその配下でアクセスされます。

アップロード済みのファイルと同じタイトルでアップロードを行うと、アセンブリファイルおよび、ドキュメントコメントのXMLファイルの入れ替えになります。

後は飛び回って好きにドキュメントをみて下さいという事です。 Code Contracts で XML ドキュメントコメントを出すようにしておけばそれなりに契約内容も表示されます。

名前空間に関するドキュメントコメント

ドキュメンテーションコメントのXML仕様的には N: で名前空間についてのドキュメントを置けることになっているため、このドキュメントタグが付いた要素があれば表示されるようになっています。また、 NDoc / sandcastle と同様に NamspaceDoc という型に対するドキュメントがあれば、名前空間のドキュメントとして表示されるようにしています。

ドキュメンテーションコメント内のタグ

推奨されるドキュメンテーションコメントタグの殆どはレンダリングできるようになっています。

また、各種HTMLタグはそのままHTMLタグとして表示するようになっていますので、HTMLの aタグをドキュメントコメントに記述しておけばリンクになりますし、imgタグを記述しておけばイメージも表示されます。(当然に img はどっか Web サーバにホストしないと見れませんと思いますけど)

高度な使い方とスケーラビリティ等

Doc<Code> は解析対象アセンブリとドキュメントをすべてメモリ上に読み込んで処理を行う為、スケーラビリティのネックは基本的にはメモリ容量という事になります。また、閲覧されるドキュメントをできるだけ高速にレンダリングしたいため、メモリに読み込んだモジュールやドキュメントをできる限りメモリ保持しようとしますのでアセンブリを登録すれば登録したほどメモリを使います。開発中のドキュメント参照支援等で .NET Framework のすべてのアセンブリとドキュメントを閲覧できるサーバを立てるとかはメモリがきついとかが根本的なネックになるでしょう。

結果的に別々のサーバに、このアセンブリはこっちのサーバ、このアセンブリはこっちのサーバといった形で配置する事になります。これらのサーバ間でドキュメントのリンクを通す事がサーバリンク機能で実現できます。

App_Data/LinkedServers.txt ファイルにリンクされるサーバ名:ポート番号を記述しておくと、該当サーバに存在しないアセンブリへのドキュメントのリンクが自動で解決されます。たとえば A サーバに mscorlib を配置して、Bサーバに自前のライブラリのドキュメントを配置し、BサーバのLinkedServers.txt にAサーバを記述しておくと、Bサーバでドキュメントをレンダリングする場合に String 等 mscorlib 由来の型は A サーバへのリンクとしてレンダリングされます。

Doc<Code>サーバ間は Web API で保持しているアセンブリの問い合わせを行っていますので、Aサーバへのアセンブリの追加などを行った時にBサーバ側は特に何もしないでも自動で反映されます。

注:サーバリンクを使ってサーバ間のリンクをする場合、それぞれのサーバにライセンスが必要です。

解析系の独自拡張の実装

Microsoft CCI に関する知識は必須ですが、アセンブリ、名前空間、型、メンバのそれぞれについて、追加で解析処理を行うロジックを入れるフックポイントが用意されています。

これにより、メンバメソッドのサイクロマチック複雑度を計算するロジックを追加すると、メンバの情報取得時にそれが呼び出され、ロジックが出力した結果は View まで引き渡しされます。

今後の開発ロードマップ

現状では各種メタデータおよび、ドキュメントコメントの表示側に注力している形になっていますが、将来的にはドキュメントコメントの入力、編集ができるようにする事を計画しています。

というのも、綺麗に見えるようになれば欲が出るので、使用例とかの Example をドキュメントコメントに色々書きたくなったりするのですが、それをやるにはコメントの中にコードを書かなければならないし、コメントの中に書かれたコードの中で XML 的な制約で < や > をそのまま書けないし色々と痛々しい思いをしなければなりません。

ドキュメントを充実させたいという思いと、ソースをそんなに汚したくないという思いや色々な葛藤の結果としてドキュメントコメントを実際として機能的に捨てているという現場も多いでしょう。

その辺りに対して、ドキュメントとして入れたい物をソース外からドキュメントコメントXMLに出力できる様にするビルド時ツールとか色々絡めて、十分なドキュメントを書いてもコードファイルが汚れない、それを素早く見れて修正を入れたりとかを Web 上のツールとコード上の双方でできる様にするのを次のマイルストーンとして考えています。

お値段は?

インストールされるサーバにつき1か月300円をめどに設定しています。

販売サイトの方を準備中だったりしますが、 1ヵ月300円、3ヵ月900円、6ヵ月1600円、1年で3000円の4通りのラインナップの予定です。

販売方法としては PayPal でのクレジットカード決済で購入するとメールでライセンスファイルが送られてくるという形式、ライセンスファイルはサーバへ配置が必要です。

また、直接 kazuk.dll@kazuk.jp の方にメール頂ければ見積書、納品書等、会社関係で必要な物は出せる形ができます。

んでいつリリースなのよ

とりあえず、NuGet パッケージについては今月中には出します。販売サイトは月明け早々にも立ち上がり予定、実際のライセンス販売開始は来月10日からを予定しています。(このあたり、多少のずれはあるかもしれません)

Microsoft Code Digger をさっそくハックした


昨夜リリースされたばかりの Microsoft Code Digger をさっそくハックしてみました。

明け方の段階ではつかえねーコレだったのですが、ハックによってそこそこ使えるようになった感じです。

何ができる物ですか?

簡単に言えば Pex の低機能版です。一メソッドを選択し、コンテキストメニューから Generate Inputs / Outputs Table を行うとPexでの動作パスの列挙および、その動作パスに入るパラメータ値、そのパラメータでの呼び出し結果がリストアップされます。

    public class Class1
    {
        public int Test( int x,int y)
        {
            checked
            {
                return x/y;
            }
        }
    }

というTestAddを Generate Inputs/Outputs すると以下のように表示されます。

image

UI上ではそれだけです。これ以上の何もできません。

結果が気に入らなければコードを直せば良い、気に入ったならそのコードで良いんでしょ。別にすることないよねって鼻くそをホジってる感じです。

何ができない物ですか?

Code Digger 単体ではこのコードが将来に渡って同一の結果を返し続けるように保障する事はできません。

この一覧を元に単体テストを作って、ビルドサーバでそれを実行するようにすればそれはきっと実現する事ができるでしょう。Pexにはそれは備わっていましたが Code Digger には含まれません。

実際の所として現状追認のテストコードが一杯できるだけで、Pexでのテストコード生成は決して便利とは言いきれなかったのですが、それにしてもバッサリ切ってしまわれるとちょっとアレです。

カッとなってハックした

というわけで、Code Digger の出力をちょっと眺めてみたらテストコード自体は生成しているという事が解ったので、それを元に C# での単体テストコードを再構成する T4 テンプレートを書いてみました。

現物ファイルは gist にあります。

https://gist.github.com/kazuk/5449781

ファイルをダウンロードしたうえで Code Digger と組み合わせて使ってみましょう。

ポータブルクラスライブラリと単体テストのプロジェクトを作ります。

image

デフォルト名でビシバシひどいですが、こんなもんです。

足し算では単調すぎるので、割り算でやってみましょう。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace PortableClassLibrary1
{
    public class Class1
    {
        public int Test( int x,int y)
        {
            checked
            {
                return x/y;
            }
        }
    }
}

というコードに対して

image

という結果が得られました。

さっきの gist から取ってきたttを単体テストプロジェクトに突っ込みます。

最初の <#@ assembly #>の行を

<#@ assembly name=”$(SolutionDir)\PortableClassLibrary1\bin\Debug\PortableClassLibrary1.dll”#>

と書き換えて保存します。

普通に C# ソースが吐かれますが、一部エラーとなっているはずで、TODOコメントがいくらか出ていると思います。

// info PexFrameworkDir = C:\Users\kazuk\AppData\Local\Microsoft\VisualStudio\11.0\Extensions\z2qe4t0q.wnk
// TODO: add reference Microsoft.Pex.Framework.dll to this Project. path shown previous line

TODOの二つ目が上記内容です。どこにインストールされるかはちょっと予想がつかないので、パスが表示されています。ここにある Microsoft.Pex.Framework.dll を参照設定してください。(ソースコードを共有している場合に人様が自分のAppData\Local を参照できないとかで困らないようにソリューション内の適当な場所にコピーする等気は使って下さいね。)

これでエラーは消えるはずです。

もう一つのTODOである名前空間まで調整すれば特にもうする事はありません。

テストは生成されていますので、実行してみましょう。

image

もともとCode Digger で失敗したよと表示されているテストは失敗するように生成されました。

テストをいじったのにT4 から再生成されては意味がないので、ソースコードをまっとうなファイル名を付け替えて保存すれば完了です。

最後に例外で失敗してる所とかをそれを望むなら PexRaisedExceptionのtypeof通りに ExpectedException で想定通りと言い聞かせればいいでしょう。

注意事項

複数の Code Digger の結果が残ってると一気に生成するので生成メソッドが重複する事があります。bin\Debug配下にreportsとして結果は出ていますので、不要な物は適宜削除するか、さっくりデバッグしたい時などは名前空間を変える等で重複をごまかしておけば良いんじゃないかな。

まとめ

普通に使うにはちょっと機能不足な気もするけど、機能が無いならハックすれば良いじゃないで何とかなってみました。

Pex は楽しいツールなんでうまく使って下さい。ではでは!ハッピーな開発を!

とある鈍器と動的再生成- C# Advent Calender 2012


どーも、どーも、どーも。

 Kazuhiko Kikuchi

Kazuhiko Kikuchi @kazuk

C# er であり .NET erな人。(IL erという噂もある) ILDASMで殴る人

でございます。

まぁ、そんなわけで、ILDASMという鈍器を日夜振りかざして( ILDASM Hoge.dll /Out=Hoge.il して、Hoge.il を適当に書き換えて ILASM に食べさせてIL書き換えとか)いるのも結構疲れるわけでございまして、もうちょっと簡単にならないかな的な物をシコシコ作ってこさえましたので、Advent Calender という機会を使っての宣伝でございます。

どこにありますか?

https://github.com/kazuk/ProjectOosaki にソース一式あります。

実行環境は制限がありますか?

.NET Framework 4.5 が必要です。(現状コードは4.0でも動きますが、今後4.5で追加されたメソッドを使う予定なので動かなくなります。)

何ができるものですか?

ILメソッドの逆アセンブルと、動的アセンブリへの再アセンブルができます。

この逆アセンブルから再アセンブルの間にIL列にちょっかい出しをしたり、ILのトークン値の解決に割り込んでトークンの置き換え等ができます。また、再アセンブルをしないIL列の内容チェック等により静的解析で行われるようないくらかの処理ができます。

結果的にはオリジナルメソッドと全く同一ないしは意図的に加工した意図通りのILをもつメソッドが出来上がりますので、トークンの書き換えによってMock/Stubフレームワークでやるような DateTime.Now の呼び出しをほかのメソッドに振り向けるなどの細工ができます。(サンプルでもやってます

逆アセンブラの実行

https://github.com/kazuk/ProjectOosaki/blob/master/Oosaki.Msil.Samples/DisassemblerSamples.cs

逆アセンブルは MethodInfo から GetMethodBase したうえで GetILAsByteArray で取り出したIL命令を解析します。

逆アセンブル結果は内部的にはILバイト列そのものと、各命令のオフセット算出および分岐ターゲットラベル、例外フレームを重畳したint値の列からなります。(その他パラメータ等付随データがいくらかあります)

呼び出し命令の検出等のIL命令の検索等の静的解析はこの逆アセンブル結果に対する処理として実装することができます。

逆アセンブル結果のIL列の順序はオフセットの int 値の列によって定義されますので、必要であればIL列に命令を挿入する事もできますし、IL命令の順序を変える事もできます。

ILアセンブラの実行

https://github.com/kazuk/ProjectOosaki/blob/master/Oosaki.Msil.Samples/ReassembleAndTransformSamples.cs

ILアセンブラは逆アセンブル結果に Assemble メソッドとして実装されています。逆アセンブル結果はMethodInfo を返しますので、Invokeを呼び出せば実行する事ができます。

現状では内部実装に System.Reflection.Emit.ILGenerator を利用していますが、 System.dll の全メソッドの逆アセンブルと再アセンブルを試行した結果として意図通りのIL列を特に例外処理に関して正しく出力できない事が解っていますので完全に別実装に置き換え予定でいます。

ILの動的逆アセンブルと再アセンブルでできるようになる事

ILの動的逆アセンブルと再アセンブルでできるようになる事は結構多岐にわたります。

IL命令列に意図的な加工を施す事で、既存テストが Fail すればテストは正しいという判断を下す物はミューテーションテストと呼ばれます。この手法は結構以前にMSDNマガジンで紹介されているのですが、実際にやっているという人は見たことがありません。

ミューテーションの力: .NET Framework による単純なミューテーション テスト システムの作成

上記では ildasm / ilasm を利用してハーネスを駆動していますが、動的にオンメモリで実行可能であればIL列の書き換えや実行に要する時間を大幅に削減できます。

また、メソッド内の分岐グラフの節点にその節点が実行された事を通知するメソッド呼び出しを埋め込めみ実行する事でカバレッジを測定する事ができるでしょう。カバレッジ測定には CLR のプロファイリングAPIを利用するものもありますが、インストルメンテーションというIL解析から必要なマーカーポイントの打ち込みを行って実行アセンブリを再生成するプロセスによってこれを行うタイプのカバレッジツールと同様の手法となります。

非同期マルチスレッド処理の競合点の検出や競合のシミュレーションもやりようによっては可能です。競合点となる操作はフィールドへのアクセスですので、フィールドの参照操作の前後にロック確認用メソッドの呼び出しを挿入すれば、適切なロックをせずに実行されるフィールドアクセスが存在するかを実行確認する事が可能になります。(ラムダを利用すると、メソッドのローカル変数がクロージャークラスのフィールドに変換される事を知っていますか?そして、この変換されたフィールドへのアクセスがスレッド競合の危険に晒されていることを意識してメソッドを実装していますか?それってあなたのチームの全員が?)

というわけで

まだまだ作りかけの物です、Samples に実装されている内容は常に動くようにという方針でやっていますが再アセンブル処理のベースを完全に再実装しなおすつもり等タスクは結構残っています。んなわけで、開発者募集です。自分自身これで利益得てないので無報酬ですが。

github に晒してる物に pull request 等投げてもらえればマージするかもしれませんし、しないかもしれません(中身と気分に応じてやります宣言)、要望、不具合等 issue に書いてくれれば実現するかもしれませんししないかもしれません。

自分の物としてこちらを気にせず色々いじくりまわしたい人は fork して好き勝手にいじってくれていいです。NuGetにはもうちょっと完成度を上げてから流したいと思っていますのでforkの方から流すのはご遠慮ください。

pull request する際のコーディング規約等

あんまり無いです、緩いです。

  1. 他者の権利、ライセンスを侵さないソースでお願いします
  2. #region は使わないでください。
  3. public メソッドには Code Contracts による契約を入れてください
  4. public な要素については標準的な命名規則に従ってください
  5. 動作確認に使えるテストコードを含めてください
  6. 既存および追加されるテストの完走を確認ください(issue報告の為の不具合再現用テストコードは Fail している物をpull request してもらって構いません)
  7. 利用者に開放される機能の追加は Samples 配下にテストコードの形で機能の利用方法として参照できるコードを収めてください
  8. メソッド名、プロパティ名、パラメータ変数名等名前の類にはそこそこ気を使って下さい。スコープが5行未満のローカル変数については説明的である必要はありませんが、スコープがそれを超えるものについては何が格納されているのかを示す命名をしてください。

まとめではない事

私、菊池和彦は個人事業主として .NET Framework , ASP.NET , Windows , Windows Azure 上のシステムの設計開発、およびそれに関連するチューニング、コンサルティングやアドバイザリーサービスをやっております。お仕事のご用命等ありましたら電子メールにて kazuk.dll@kazuk.jp へお気軽にお願いします。決して安いとは言いませんが値段なりのお仕事をさせて頂きますのでよろしくお願いします。

an Quick Start for Roslyn (2)–IText と TextSpan


というわけで Roslyn でのパース結果の詳細な見方、主に位置情報について。

前回は SyntaxNode を列挙して軽く眺めてみただけですので、その先の詳細を見ていきましょう。

まず、SyntaxNode には全く文字列は保持されていません。SyntaxNode には Span と FullSpan というTextSpan型のプロパティがありこれらのプロパティには開始位置と長さが保持されていて SyntaxTree の IText 上の範囲の文字列が SyntaxNode の文字列を表現します。

IText の取得方法

まずダメな方法です。

各 SyntaxNode には GetFullTextAsIText() があります、このメソッドはその要素の内容を示す IText を返します。結果のIText の中での位置 0 はその要素の先頭を示しますので、もとになるITextとは別のものです SyntaxNode の位置を示すSpan/FullSpanプロパティの値は意味を持ちません。

続いてイケる方法です。

各SyntaxNodeはSyntaxTreeへの参照を持っています。SyntaxNode.SyntaxTree プロパティ、このSyntaxTreeは TryGetRoot メソッドでルート要素を取得したSyntaxTreeへの参照を保持しています。SyntaxTreeのGetTextはITextを返しますので、このITextを利用します。

また IText は StringText クラスで実装されていますので、 文字列を元に IText を構築することができます。IText をパラメータとして ParseCompirationUnit によってSyntaxTreeを取得することができます。このSyntaxTree のGetTextはもとになったStringTextを返します。

            var text = new StringText(@"using System;

namespace RoslynTest
{
    public class Program
    {
        public static void Main( string[] args )
        {
            Console.WriteLine( Console.ReadLine() );
        }
    }
}
");
            var tree = SyntaxTree.ParseCompilationUnit(text);
            var itxt= tree.GetText();
            Console.WriteLine(object.ReferenceEquals(text, itxt));
            Console.ReadLine();

結果: True

結果的に SyntaxNode の SyntaxTree プロパティは ParseCompilationUnit の返した SyntaxTree であり、SyntaxTreeから取得された IText は ParseCompilationUnit に渡された、または内部で生成されたITextで、これらは常に同一の参照を返します(imuttableであるといえます)ので元にしたIText を利用する、SyntaxTreeからITextを取得するのは同じ事で、SyntaxNodeからSyntaxTreeを参照してGetTextでITextを利用するのも同じことです。

僕は自分でStringTextで生成したIText (コード中のtext変数)を直接に参照するようにしました。

.NET Framework は純粋さを前提にできないので、各SyntaxNodeがSyntaxTreeの同一の参照を返すという仮定はできないはずだし、SyntaxTreeのGetTextが返すITextが常に同一という仮定もできないはずなので、同一の参照を使い続けるという仮定を実現させるにはできるだけ早い時期に取得したIText をそのまま使う必要があります。また new を明示的にしているのでnullではないという仮定も成立するはずなので null チェックを省略させる意味もあります。枝葉の最適化にコダワル所じゃないのですけど脳内で降りた判定を明かしておくと「同じこと」という物に微妙な効率差が有りそうと踏んで選んでることだけはなんか書きたかったので書いときます。

オプティマイザの観点でいうと仮定できないというと語弊があって、楽観的に仮定してみて仮定に危うさを感じる要素が一つでも見つかればその仮定を取りやめるのが普通だったりします。この楽観的仮定を最後まで通してあげるとオプティマイザが効率的に処理するコードに落としてくれるわけですので、危うさを臭わせないほうが早いコードが出るという事になります。

TextSpanとSyntaxNodeのSpan/FullSpanの違い

さて、ITextがあり、ITextと関連性のあるTextSpanがあればstring IText.GetText( TextSpan ) によって実際の文字列を取得する事ができます。SyntaxNode は Span と FullSpan の二つのTextSpan型プロパティを持っていてそれぞれ構文要素そのものと、その構文要素に付随するデリミタを保持します。

TextSpan は operator == / operator !=の適切な演算子オーバーロードを持っていますので、これらの違いが見える部分だけを抽出してみましょう。

        // 概要:
        //     Determines if two instances of TextSpan are different.
        public static bool operator !=(TextSpan left, TextSpan right);
        //
        // 概要:
        //     Determines if two instances of TextSpan are the same.
        public static bool operator ==(TextSpan left, TextSpan right);

前回のコードを元に Span と FullSpan が異なる要素だけテキストを取得して表示しているのが以下になります。

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using Roslyn.Compilers;
using Roslyn.Compilers.CSharp;
using Roslyn.Services;
using Roslyn.Services.CSharp;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            var text = new StringText(@"using System;

namespace RoslynTest
{
    public class Program
    {
        public static void Main( string[] args )
        {
            Console.WriteLine( Console.ReadLine() );
        }
    }
}
");
            var tree = SyntaxTree.ParseCompilationUnit(text);
            var itxt= tree.GetText();
            Console.WriteLine(object.ReferenceEquals(text, itxt));
            Console.ReadLine();

            Stack<SyntaxNode> nodes = new Stack<SyntaxNode>();
            SyntaxNode node;
            tree.TryGetRoot(out node);
            nodes.Push(node);

            while (nodes.Count != 0)
            {
                node = nodes.Pop();
                if (node.Span != node.FullSpan)
                {
                    Console.WriteLine("Full:\t"+node.FullSpan +"\'"+ text.GetText(node.FullSpan)+"\'");
                    Console.WriteLine("Span:\t"+node.Span +"\'"+ text.GetText(node.Span)+"\'");
                }

                foreach (var child in node.ChildNodes())
                {
                    nodes.Push(child);
                }
            }
            Console.ReadLine();
        }
    }
}

わかりやすい例としては以下の様な表示が含まれます。

Full:   [136..190)’            Console.WriteLine( Console.ReadLine() );

Span:   [148..188)’Console.WriteLine( Console.ReadLine() );’

FullSpan には前方のスペースおよび、後方の改行が含まれますが、Spanには含まれない事が解ります。

それって何行目?その行は?

IText には int GetLineNumberFromPosition(int position )があり、これにより指定の位置の行の番号を得る事ができます。

行番号、position ともに0始まりのようです。

            SyntaxNode node;
            tree.TryGetRoot(out node);
            Console.WriteLine(node.FullSpan.Start);
            Console.WriteLine(text.GetLineNumberFromPosition(node.FullSpan.Start));

の出力は 0, 0 です。

同様に IText には ITextLine GetLineFromPosition(int position ) があり、指定の位置を含む行を取得することができます。

ITextLine の実装である TextLine は ToString をオーバライドしていないので単純に ToString すると残念な結果である型名だけが出ます。GetText で内容を取得すると、行末の改行を含まない文字列が返されます

ITextLine はいくつかのプロパティを持っていて、これらで行の範囲を示します。

Extent は行の改行を含まない範囲を示すTextSpanを返します。ExtentIncludeLineBreakは同様に改行を含む範囲を返します。Start、Endはそれぞれ行頭の位置、行末の位置を示します。EndIncludeLineBreakは改行を含む行末です。

(2)のまとめ

シンタックスツリーの操作をやるって言っておきながら、書いてみたら長くなったので回を分けるというやつで、今回は SyntaxNode の位置情報である TextSpan と、位置情報と文字列の関係を示す IText 、位置と行の関係を示す ITextLineをもとに SyntaxNode の位置を解説してみました。

次回もシンタックスツリーの操作にはきっと入れずにシンタックスに付随する SyntaxTrivia についての話となると思います。

an Quick Start for Roslyn (1)


というわけで、プロジェクトテンプレートに並行して Roslyn を触り始めてみました。

Roslyn が何であるかについては ufcpp さんが既に書かれてた記事 があるのでそちらを見てもらうとして、Compiler 内部のコンポーネントをユーザーが利用することができる物という認識で良いと思います。追加してシンタックスツリーを作るとそれのテキスト化(ソースコード化)等、いわゆる言語サービスに類するもの、これまでは Visual Studio と C# コンパイラーの内部でしか利用できなかった物が比較的簡単に利用できるようになっています。

自分としてはこれは言語内DSL等に使える他結構色々と使いでのあるツールなのですが、何しろコンパイラーという巨大な仕組みの内部が公開されたという事は大量のクラス、メソッドが表に出てきたわけで見るのも大変である事は事実で、とっかかりを得るのは結構苦労しそうだなと。根が優しい人なので、この辺のとっかかりについて書いておく事で後の人にもっと奥まで進んで貰えるかな等というわけで書いてみます。

disclaimer

この記事は Roslyn の製品版が出る以前に書かれていますので、RTM版では一部の手順や実装が異なる事が十分にあり得ます。blog記事なんて生ものだと思ってる人なので追補とかする気は全然ありませんので、遠い未来から突っ込みされても困ります。「あー、困った困った。」で流しちゃうと思いますけどご了承ください。

インストール

2012/06/16 現在で最新の Roslyn は以下からダウンロードできます。(2012/6/5 リリースのものです)

Microsoft “Roslyn” CTP 

普通にインストールすれば入ると思います。

インストールすると Roslyn のプロジェクトテンプレートが利用可能になります。

image

含まれているプロジェクトテンプレート

Console Application は普通のコンソールアプリケーションですが、それ以降は vsixmanifest が含まれている事からお察しの通り(お察しできないかもしれませんが)、Visual Studio 拡張のプロジェクトです。

以下プロジェクト作成直後のソリューションエクスプローラーの表示をペタペタと。

imageimageimageimageimage

まぁとっかかりとしての選択肢は Console Application 一択でしょう。 VS拡張を実装するのは追加の知識が大量に必要になってしまうので別個の知識として身に着けてからでないと必要知識の山の前に簡単に遭難できますので。

コンパイラーパイプライン

最初に Roslyn をダウンロードしたページの「Related resources」にある Microsoft “Roslyn” Project Overview› を見ると最初の方にある図を引用させてもらいます。これを読みながら一緒に見てもらうとわかりやすいかも。

CompilerPipeline

コンパイラーの内部処理は Parser で文字列なソースから構文木への変換がかかり、該当ソースで定義された要素が Symbols 、参照しているアセンブリが Metadata Importで取り込まれ、Binder で構文木から実際のコードに相当するモデルが構築され、IL Emitter がそれをPEアセンブリとして書き出すわけで、この各段階と各段階をまたぐデータ構造(構文木、シンボルストア…etc)が Roslyn で API 化されています。

Parserの呼び出しと簡易的なシンタックスツリーの表示

Parser には SyntaxTree の公開するスタティックメソッド、ParseCompilationUnit を介してアクセスします。構文木をスタックに積みながら要素の種別とテキストでの内容を表示するサンプルを以下に示します。

using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Text; using Roslyn.Compilers; using Roslyn.Compilers.CSharp; using Roslyn.Services; using Roslyn.Services.CSharp; namespace ConsoleApplication1 { class Program { static void Main(string[] args) { Stopwatch stopWatch = new Stopwatch(); stopWatch.Start(); var tree = SyntaxTree.ParseCompilationUnit( @"using System; namespace RoslynTest { public class Program { public static void Main( string[] args ) { Console.WriteLine( Console.ReadLine() ); } } } "); stopWatch.Stop(); Console.WriteLine(stopWatch.Elapsed); Console.ReadLine(); Stack<SyntaxNode> nodes = new Stack<SyntaxNode>(); SyntaxNode node; tree.TryGetRoot(out node); nodes.Push( node ); while (nodes.Count != 0) { node = nodes.Pop(); Console.WriteLine(node.Kind); Console.WriteLine(node.GetText()); foreach (var child in node.ChildNodes()) { nodes.Push(child); } } Console.ReadLine(); } } }

 

表示としては以下の様になりました。

image

パースに21ms ぐらい要してる様に見えますが初回実行コスト(Roslyn のアセンブリの遅延ロードやJITコンパイル)が含まれていてこの速度は十分に早いと言っていいと思います。

IEnumerable<SyntaxNode> の foreach だけで実装できている事からわかる通りで簡単に LINQ の餌食になりそうに見えます。ただし、LINQ は再帰的なツリー構造に必ずしも強くないので多少の補完ライブラリが必要でしょうかね。

(1)のまとめ

内容としては Roslyn の ParseCompilationUnit を回して構文木を得た、軽く内容を表示してみて解析ができていることを確認した段階です。

少なくともコードは完全にSyntaxTreeに分解されていますので、既存コードに含まれがちな foreach での多段ネストループを検出するとかはこれで十分にできます。「コード的にはバグじゃないけどこのコードの実装者の頭ちとおかしいんじゃない?」を検出する検出器は検出条件が構文的に決まってしまえばこの段階で作れますのでぜひ作ってみてください。

アセンブリを参照設定していれば使えるわけですので msbuild のカスタムタスクで検出器を回すことができればあなたのソースのビルド時にチェックするとかもきっとできるでしょう。

 

この構文木を編集するなどを(2)でやってみたいと思います。単純な構文変換であればこの処理で十分ですが、意味を維持するという場合には相応の処置が必要なので意味を含む変換は(3)以降になると思います。

Metro Style Application 開発の為の Visual Studio Project Template(1)


Visual Studio 2012 RC には Metro Style Application のためのプロジェクトテンプレートが幾つか含まれています。

これらのプロジェクトテンプレートからプロジェクトを起こしてみての解説を2回ぐらい書きましたが、今回はこのプロジェクトテンプレートの実体がどのように定義されているか、その内容を確認するとともに今後の開発で使うプロジェクトテンプレートの準備などに焦点を当ててみたいと思います。

標準のプロジェクトテンプレートのインストール場所

Visual Studio 2010 RC においてプロジェクトテンプレートの標準のインストールパスは以下の通りです。(Windows 8 RP x64にインストールした場合)

C:\Program Files (x86)\Microsoft Visual Studio 11.0\Common7\IDE\ProjectTemplates\CSharp\Windows Metro style

このフォルダを開くと1041フォルダ配下に実際のプロジェクトテンプレート毎にフォルダがあり、プロジェクトテンプレートの実体が格納されています。

imageimage

このファイル群をベースに、プロジェクトテンプレートを修正してもいいですし、プロジェクトテンプレートを新規に作成してもいいでしょう。

Visual Studio SDK によるプロジェクトテンプレートの作成

プロジェクトテンプレートを作成するには簡単な方法としてはある程度作りこんだプロジェクトを元に Visual Studio から [ファイル]-[テンプレートのエクスポート]があるのですが。根がマゾいので簡単な方法は使わずにプロジェクトテンプレートを一から作成する事にしましょう。

この作業を行うには Visual Studio SDK が必要になります。

Download: Visual Studio 2012 RC SDK – Microsoft Download Center – Download Details

をインストールしてください。インストールを完了すると Visual Studio からプロジェクトの作成時に機能拡張配下からプロジェクトテンプレートが作成できるようになります。

image

今回はMetro Style Application の為のクラスライブラリを単体テストプロジェクトと共に生成し、ビルドのカスタマイズ等も(比較的)簡単にできるようビルドのカスタマイズプロジェクトも設定するという形でちょっと規模の大きいプロジェクトテンプレートを作成してみます。

Metro Style Application の為のプロジェクト要件の確認

ここで既存のプロジェクトテンプレートの内容を確認しましょう。プロジェクトテンプレートの中核を担うのは vstemplate という拡張子がついたファイルで、このファイルをもとに関連ファイルが取り込まれてプロジェクトが生成されますので Metro Style Application としての基本的な設定事項を把握しなければ話が始まりませんが、歴代の vstemplate はその詳細まで解説された事は殆ど皆無じゃないかな程度に情報量が少ないので、自分達の手元にあるVSにインストールされた vstemplateを見るのを最初にするのをおすすめです。

標準のプロジェクトテンプレートの ClassLibrary の vstemplate の内容とプロジェクトテンプレートを作成する場合の vstemplate には以下の要素に違いがある事がわかります。

<TemplateGroupID>WinRT-Managed</TemplateGroupID>
<RequiredFrameworkVersion>4.5</RequiredFrameworkVersion>
<TargetPlatformName>Windows</TargetPlatformName>
<RequiredPlatformVersion>8</RequiredPlatformVersion>
<CreateInPlace>true</CreateInPlace>

実際には当然に TemplateID などは違うのですが、そりゃテンプレートが違えばちがうわなで今回はスルーします。

TargetPlatformName や RequiredPlatformVersion 等が明らかに増えていますので、これを持つ vstemplate を作ります。

Metro Style Application の為の csproj 設定

いやー増えてますねー、Any CPU, x86, x64, ARM それぞれに Debug/Release でビルドするので当然ですが。全部ビルドしようとかすると単純に時間は20~30%増しって計算にちょっとgkbr

さて、大きな変更がありますね、vs2010までの csproj では Microsoft.CSharp.targets を見ていた物が $(MSBuildExtensionsPath)\Microsoft\WindowsXaml\v$(VisualStudioVersion)\Microsoft.Windows.UI.Xaml.CSharp.targets を見ています。

XAMLのコンパイルタスクが必要なのはわかるのですが、このターゲットの解決に Visual Studio Version が入るってことは本気に Visual Studio インストールしないと(SDKだけだと)ビルドもできないのかも。ビルドサーバを用意する上でこれはちょっと難点ですね。最低限のExpressでも入れとけばビルドできるといえばそうなのかもしれませんけど。(ビルドしかできないかもしれませんけどね)

細かい詳細は基本的には丸コピー以外の選択肢は殆ど無い気がします、各 Configuration 毎に最低限のビルドオプションを設定したうえでソースはCompile ItemGroupにというのが基本的なクラスライブラリプロジェクトの構造です。

次の段階として、関連するテストを収容するための単体テスト用プロジェクトもこのテンプレートに取り込むのですが、順番にやるのが良いでしょう、ここで一旦 (1) として区切りをつけようと思います。

(1) までのまとめ

とりあえず github に push しときました。

リポジトリはこちら https://github.com/kazuk/MetroAppBase

ここまでの内容のコミットはこちら https://github.com/kazuk/MetroAppBase/commit/28e89e2623d54991cf9bcb57ea69dc5d270f0a29

予習したい人の為に (2) で何をするかという事で MSDN ドキュメントの参照リンクなど

方法 : 複数プロジェクトのテンプレートを作成する

(2)ではMetroスタイルアプリケーションのテストプロジェクトについて調べた上で、このプロジェクトテンプレートに組み込む事でクラスライブラリを作成と同時に単体テストを活用した開発ができる様にしようと思います。

まとめでは無いこと

MS的には Visual Studio 2012から TFS Express を使ってねとか色々あるみたいなんですが、少なくともこれまで経験から 「TFS は肌に馴染みませんでした。」(いいなーと思うところも無いわけではないけど、VSが落ちるとか落ちるとか落ちるとかで寧ろ足を引っ張られる印象の方が強く)というわけでVS2012での自分の開発環境での採用は無い感じです。

VS2012世代で自分の目論む開発環境ですが、基本的には git / github をソース管理のメインに据えてこれをビルドするビルドサーバは TeamCity か Jenkins か検討中、現状は VS2012のUltimateを90日試用期間で使ってますが(OSがCP/RP/RTMと行く流れで再インストールになりながらそれぞれ最大90日使えればまぁお得!)、最終的には Premium 辺りに落ち着くんじゃないかと思っています。

ソース管理の基本が TFS から git/github になる事で自分の開発環境整備(包丁を研ぐ段階)の物が表に出せるし出てしまうので晒しでやっていこうかなとか。git/github に出してる物は MIT ライセンスのつもり(どこに書くんだろう)なのでお好きにパクッテって頂戴。

githubでの pull request とか貰った事ないので、なんか楽しい変更があれば pull request も遠慮なく。

個人事業主になりまして昼間は他のお仕事しながら夜のお勉強タイム確保+お子ちゃま台風上陸という私事もありまして一日一時間も勉強に取れないので遅々として進まずの可能性もなきにしもですが今後うん十年仕事する上での準備なので着実に書いていければなーという感じです。

Metro Style Application Code Samples–XAML Data Binding Samples


Windows 8 Code Samples and Examples in C#, VB.NET, C++, JavaScript

とりあえずインストールした Visual Studio 2012 RC で Metro Style App のプロジェクトを作ると

// The Split App template is documented at http://go.microsoft.com/fwlink/?LinkId=234228

って感じでリンクされている LinkId 234228 でたどり着くサンプルギャラリー。

質はわからないけど量は十分と言っていい気がするし、一覧を眺めてみた限りでは内容も多岐にわたっている気がする。このあたりは Microsoft本気だなと思わないでもない。

日本語での情報提供は翻訳の都合で遅れるのが常なので世界についていく気なら英語だろうがコードだろうが気合で読むのみで色々見るのをおすすめ。龍に乗るなら首根っこ、尻尾掴んでも振り落とされるのが落ちだ。

というわけで、もっとも基本っぽい XAML Data Binding sample をチェックしてみるなど。コンパイル&実行は全く問題なく完了、普通に実行しながらソースを確認することをお勧めです。

チェックの目的は WPF や Silverlight との相違点等を確認しておきたいということなので、 WPF / Silverlight のソースをあまり見たことが無い人はもっとチュートリアル的な物から見たほうが良いかも。

 

Scenario1 ~ Scenario7 までの xaml と xaml.cs が本体と言っていい感じ。

Scenario1.xaml XAMLのデータバインディングの { Binding … Mode=[バインディングモード] } のバインディングモードの TwoWay, OneWay, OneTime の違いを見せようというもの。 WPF / Silverlight との違いは特に見えず。

簡単にさっくり解説すると以下の通り。

TwoWay の場合、テキストボックスに入力した内容はスライダーに反映され、スライダーの内容はテキストボックスに反映される。このような双方向のバインディングを行う場合には TwoWay を使う。もっともよく使うモードだと思う。

OneWay の場合、スライダーの操作はテキストボックスに反映されるけど、テキストボックスの修正はスライダーに反映されない。このような一方向のバインディングを行う場合には OneWay を使う。テキストボックスのような入力を受けるコントロールと結びつけると変に感じるが表示専用の物につなぎ合わせるにはこっちの方がお得というもの。

OneTime の場合、コントロールの初期化時に一発反映したら後はやらないよという物。変更前の値を表示しておきたいという用途には便利だけどそれ以外での局面では全く役に立たない系。

 

Scenario2 は IValueConverter によってスライダー値を F から SUPER STAR! の段階を示す文字列に変換してテキストボックスに変換するもの。 E への変換が無いねとか余計。

この変換は S2Formatter.cs にて宣言されている Windows.UI.Xaml.Data.IValueConverter の実装によって変換されている。

<StackPanel.Resources>
    <local:S2Formatter x:Key=”GradeConverter”/>
</StackPanel.Resources>

で S2Formatter を GradeConverter で宣言、

Text=”{Binding ElementName=sliderValueConverter, Path=Value, Mode=OneWay,
              Converter={StaticResource GradeConverter}}”

で GradeConverter を利用している。 Mode が OneWay なので S2Formatter での変換も一方向しか実装されていない。

S2Formatter.cs の実装内容はあまりマネしてほしくないところ、メソッド内のローカル変数に _ でプリフィックスをつけるとか基本として見たことのないネーミングスタイルなので注意。

 

Scenario3 は Emploee というクラスにデータバインディングするというもの。 Emploee は Scenario3.xaml.cs からMainPageのDataContextとして渡されています。

単純には DataContext に渡したオブジェクトとデータバインディングされますよという内容とみればいいでしょう。

 

Scenario4 は Team.cs で定義された Teams リストの3番目の要素にデータバインディングを行うという物。

Binding のPathシンタックスでの ‘[‘ + インデックス + ‘]’がインデックスが整数(配列ないしはリスト)、その他(コレクション)でバインディングができるという事を示しています。

 

Scenario5 は同じく Team.cs で定義された Teams リストにバインディングしますが、 Border の Background を Team のColor にバインディングすることで背景色を変えるというもの。 Text や Value 以外でもバインディングできるので、見た目をバインディングで変えられるということを示しているだけですね。

 

Scenario6 は同じく Teams にバインディングしますが、ListItem のGroupStyleを介してHeaderTemplate を与えることで、Key 要素ごとにヘッダーを表示するというもの。

var result = from t in teams
             group t by t.City into g
             orderby g.Key
             select new { Key = g.Key, Items = g };

のLINQクエリによってグループ化された物のKeyをグループヘッダーとして表示しています。LINQクエリとグループヘッダテンプレートの合わせ技でそこそこ簡単にできますよってところです。

 

Scenario7は同じくTeams にバインディングしますが、選択要素の削除を実装しています。ハンドラの実装は以下のもの。

void BtnRemoveTeam_Click(object sender, RoutedEventArgs e)
{
    if (ocTeams.Count > 0)
    {
        int index=0;
        if (lbTeams.SelectedItem != null)
            index = lbTeams.SelectedIndex;
        ocTeams.RemoveAt(index);
    }
}

データバインディングですべてが行われていますので単純にコレクションから要素を削除すれば要素が消えますってもの。

選択されてなかったら最初の要素を消すという乱暴な実装に見えますが良いんでしょうか?コピペしないでね?なサンプルは勘弁してほしいですね。

気になるところとしては

<Button x:Name=”btnRemoveTeam” Content=”Remove team”/>

と Click ハンドラの結び付け、コンストラクタで以下を書いてますが、Commandパターンとかは入ってない感じですかね?コードビハインドにコードを書きたくないタイプの人なのでこういうのがずらずらしちゃうのはちょっといやんな感じです。

btnRemoveTeam.Click += BtnRemoveTeam_Click;

 

まとめ

そんなに大きく WPF / Silverlight と異なる印象はありません。名前空間の配置など実装は当然に変わっていますが大きくは変わっていないということが解っただけでした。

テクノロジの特性なのかもしれませんが、周辺がいろいろと冗長です。XAMLなどは説明したい部分が埋没していると思われるぐらいです。何を説明しようとしているのか、それを読み取って見ないと迷子になりますのでポイントをかいつまんで読みましょう。かいつまんで読むことができる人(ポイントのわかってる人)はこのレベルのサンプルは見ないというか必要ないかもな気もしないでもないですけど。

Scenario2 で現れる IValueConverter はお察しの通り大量に書くことになるでしょう。データ値と見た目のフォーマットの変換処理はほとんどの場合 IValueConverter で実装することになります。これの実装の為の項目テンプレートが標準のプロジェクトテンプレートにはありませんので、項目テンプレートを切り出ししておく事をお勧めです。ただしこのサンプルに含まれる IValueConverter の実装は変数名のネーミングがいまいちで普通のコーディング規約にはマッチしないはずなのでもう少し良い物をベースに選ぶか納得いく形に書き換えてから、コーディングのヒントをコメントにふんだんに盛り込んだ項目テンプレートを作るのが良いでしょう。

Visual Studio 2012 にはネーミングルールによる変数名の警告や補正の仕組みは入らなかった模様、必要な人は ReSharper を入れるとルールに沿ってない場合には警告してもらえるし QuickFix で大抵はいい名前に直してもらえますのでこの機会に投資する気ならした方がいいかもしれません。(ちょっとReSharper以外に浮気しようかと思ってVSギャラリー見回してみたのですがグッとくる物は見当たらなかったです)

良いサンプルだとは思いますが、コレクションからの削除の実装が選択されていなければ先頭を消すとか鵜呑みにしてはいけない内容を多分に含んでいますのでご注意を。

ASP.NET MVC プロジェクトのサブシステム分割とマージ


なんとなく出来ちゃったという物のおすそわけ blog.

 

何を解決する物ですか?

ある意味 ASP.NET MVC による大規模アプリケーション開発をサポートします。

大規模というのは、サブシステム分割が必要となるレベルであり、例えば Controller が数十からそれ以上に達する可能性の高いシステムです。

このような大規模アプリケーションを作る場合、ASP.NET MVC の Area 等を利用してアプリケーション内で分割するのは一つのアプリケーションの規模の増大を招くだけであり、規模の暴力に対してのアプローチとはなりえません。

根本的にはアプリケーションをサブシステムに分割し、各個サブシステムを統合するという手順が望ましいわけです。

単純に msbuild プロジェクトファイル一個でこの分割によって分かれたアプリケーションの統合が出来ましたよって事です。

ソースと動作原理

Build.Utilities\HugeWebApplication.targets

<?xml version="1.0" encoding="utf-8" ?>
<Project 
  ToolsVersion="4.0" 
  DefaultTargets="Build" 
  xmlns="http://schemas.microsoft.com/developer/msbuild/2003">

  <ItemGroup>
    <_copyContentSourceContent Include="@(Content)" Exclude="**/*.config" />
    <_copyContentsOutputFiles
      Include="@(_copyContentSourceContent->'$(CopyContentOutputPath)\%(identity)')" />
  </ItemGroup>

  <Target Name="CopyProjectContents">
    <Message Text="$(MSBuildProjectName) からコンテンツをコピーします 出力先 $(CopyContentOutputPath)" />
    <Copy SourceFiles="@(_copyContentSourceContent)"
          DestinationFiles="@(_copyContentsOutputFiles)" SkipUnchangedFiles="true" />
  </Target>

  <Target Name="MargeProjectContents" AfterTargets="BeforeBuild" >
    <MSBuild 
      Targets="CopyProjectContents" 
      Projects="%(ProjectReference.Identity)" 
      Properties="CopyContentOutputPath=$(MSBuildProjectDirectory)" />
  </Target>
</Project>

動作原理としては MargeProjectContents ターゲットが BeforeBuildの前に実行され ProjectReference で参照しているプロジェクトに対して CopyProjectContents ターゲットを実行します。

CopyProjectContents ターゲットは呼び出されたプロジェクト上で定義された Content を呼び出し元プロジェクトのディレクトリに Copy します。

結果として空のASP.NETプロジェクトで MVC プロジェクトをプロジェクト参照すれば MVC プロジェクトの Contents は空のASP.NETプロジェクトに転送されます。bin に MVC プロジェクトの出力が配置されるのはプロジェクト参照のデフォルトの挙動で、そこに対しては何も変更していませんので bin にMVCアプリケーションのアセンブリは配置されます。

使い方

ソリューション上に ASP.NET MVC アプリケーションを複数作成し、統合先となる空の ASP.NET アプリケーションを作成します。統合先プロジェクトから各 ASP.NET MVC アプリケーションをプロジェクト参照します。

各 ASP.NET MVC アプリケーション、および 統合先 ASP.NET アプリケーションの csproj の末尾に以下の一行を追加します。

  <Import Project="..\Build.Utilities\HugeWebApplication.targets" />

後はビルドするだけです。

image image

スクリーンショットではWebApplication1 が MvcApplication1 / MvcApplication4 を参照しています。

ビルド結果では WebApplication1 の配下に Views その他の Content がコピーされている事が解ります。

コピー対象としてかち合う事が見え見えの web.config 等 config 類は含ませていません。

Global.asax/.cs どうするかなーとか解決すべき課題が無いわけではありませんが、ここまでの労力が msbuild のtargetsファイル 20行ってのは上々の戦果といっても悪くないかなーと。

統合先として今回は空のASP.NET アプリケーションで紹介しましたが、ASP.NET MVC アプリケーションでも構わない=依存する基盤部の取り込みを各部でする為に応用も出来るという事も備考まで。

やってみたらコレ困るわな事があればコメントにどーぞです。

という訳で、ハッピーなクリスマスを!