XboxInfoTwit - ver.2.4.0.3
- 2012-05-13
Xbox.comが内部的にちょっと変わっていて動かなくなっていたので、それに対応しました。前々から報告されている不具合などに関する修正などは一切入ってません、すみません。最近割と普通に忙しくて全く手をかけられない状態でして……。落ち着いたらその時には必ず。
MetroStyleDeveloper #03の告知
- 2012-04-30
最近は告知ばかりで、まともにブログ記事書いてなくてすみませんすみません。Windows Developer Daysでのセッションはお陰様で立ち見が出るほどに大盛況で、うれしかったです。資料に関しては、公開できるように調整中です(いちおー会社身分で出てるので、公開するときは会社サイトのほうになるかしら、と思っています)。中々面白い感じに仕上がっているので、なるべく早いうちに出したいところです。
さて、WDDのキーノートで 4月25日に開催されたMicrosoft Windows Developer Days にて、Windows8ベースで開発したグループスの試作アプリが発表されました ということで、シアトルクエストという試作アプリが公開されました。その詳細を、5/12に開催されるMetroStyleDeveloper #03で発表します!(予定)。会場、弊社ですお。
というわけで、気になった方はそちらも是非是非来ていただけたらと思います。
Windows Developer Daysで登壇します
- 2012-04-17
4/24-25に開催されるMicrosoft Windows Developer Daysで登壇します。セッション一覧より、Day 2の11:30-13:00、Room Fでタイトルは「LINQ to Everything : データソース・言語を超える LINQ の未来」です。
これなのですが、私の勤め先のgloopsがWDDのプラチナスポンサーで、そのスポンサー用のセッション枠を私が頂いた、みたいな形です。スポンサー枠なのに完全に趣味のLINQの話しかしない気満々とか非常にアレ。そんなわけなので是非来てください。ランチセッションなのでお昼も出るので、ご飯食べながらゆるゆると聞いてもらえればと思ってます。
弊社からは、他にSQL Azure MVPの大和屋さんがgloops提供人気ソーシャルゲームでトラフィック限界に挑戦!SQL Azure Federation検証結果公開というタイトルで4/24に出ますので、そちらも是非是非どうぞ。
Microsoft MVP for Visual C#を再受賞しました
- 2012-04-02
去年の報告から一年、再受賞することができました。活動はあまり変わらずC#とLINQについてブログに書いて、小さめのライブラリを作って公開して、それと今回は少し、外での活動が増えましたね。つい一昨日もSilverlightを囲む会でReactive Extensions v2.0としてお話しました。
日本の海外に比べて弱いところ、先端的な技術の紹介であったりディープに踏み込んだ使い方であったり、何かを作って公開することであったり。それらを埋めていきたいな、と思っています。そういったことを通してC#の良さを伝えられていければ何よりです。
近況
さて、しかし色々何かと停滞中です……。ライブラリは更新していない、ツールもバグ報告スルー中、Pull Requestすら放置、原稿……、などなどなどなど。はい、どうみてもダメ人間です。やりたいこと、はいっぱいあって、やらなければならないこと、もそれなりにあって、それらがかちあってどっちも出来ず仕舞いで時間を浪費してばかり。今年はその辺もしっかり整理して、より多くの何かを届けていきたいところです。実際のとこダラダラTwitterやってる時間が一番長くてマズいのは確定的に明らか。Twitterやってる時間をSkyrimに回したほうがよっぽど有意義ってものですよ!しかもなんと超絶楽しみにしていたSkyrimは未だ未開封という、もうアレ。悲しいですね。
ソーシャルゲームとわたくし
- 2012-03-28
最近のWeb業界の風潮にならって、ちゃんとロクロ回しました!
猫も杓子もみんなソーシャルゲーム業界に行くんだなあ、とかポケーッと眺めていましたが、まさか私も行くとは全然思ってもいませんでした。というわけで、1月から転職したと書きましたけれど、株式会社gloopsで働いています。転職理由とかは、インタビューの通りです。C#を世間(といっても、技術系の人に、ですね)に強くアピールするにはどうすればいいのか、といったら、一番大事なのはそれで書かれたメジャーなアプリケーションなのです。PerlではてなやMixi、livedoorなどを思い浮かべる、RubyでCookpadを、ScalaでFoursquareやTwitterを、そういった憧れにも似た気持ちを浮かべさせるようなアプリケーションがなければいけなくて。
Stack OverflowはC#, ASP.NET MVCだし、TIOBEのプログラミング言語ランキングでは三位など、海外でのC#の地位は十分に高い。のですが、国内ではそれと比べれば全くもってない。日本で誰もが知る会社の誰もが知るアプリケーション、それがC#で書かれている。そういう状態にならなければ、日本で強く普及は無理だな、と。
ギョーム系アプリでは十分に強いじゃん、とかそうじゃなくて、私としてはもっと学生とかも憧れてガンガン使う感じであって欲しいんですよ。Visual Studioは無料版ありますし、学生向けの支援(DreamSparkなど)もやってはいますが、あまりリーチできてないのではないかなあ、って。そういうのって内からやりたいって気持ちが湧いてきて初めて支援があって嬉しい!になるわけで。
まあその領域だったら、やっぱゲームですよゲーム!なんというか、Unityブームのほうがずっと請求してるかもなのですかねー。というわけで第77回codeseek勉強会&第17回日本C#ユーザー会 勉強会 : C#とゲームでは、C#とゲーム全般を取り扱って、そのなかで弊社CTOの池田もソーシャルゲーム枠でセッション持ちますので、残席まだありますので是非是非どうぞ(宣伝)
ゲームもいいのですが、デスクトップアプリケーションもいいし(最近はあんまし流行らないですって?そうかもですがー)、モバイルアプリもいいし(Windows Phone 7よりも、MonoTouchやMono for Androidのほうが受けますな)、そして、ウェブアプリもいい。C#の持つ強みや範囲というのは、本当に、もっともっと知られて欲しいなって。
そんなようなことは入社初日にも言ったりなどしていて、今も当然変わってませんし、3ヶ月働いてきて、実現できる会社であると実感しています。2012 グループス MLB開幕戦の冠協賛や、最近はCMも増えてきたりなど、露出も増えてきて、勢いありますね。その勢いを止めない、加速させるためにも、まだまだ人が必要というわけで、求人バナーもかなり見かけてるんじゃないかと思います。というわけで弊社ではエンジニアを絶賛募集中なのでC#で書きたい!という人は是非是非お願いします(宣伝)
あと、私自身の目的としてはもう一つあって、日本ローカルだけじゃなく世界にも通用する技術力を掲示したいという欲求があります。その面でも、世界に向けても前進している(gloops International CEO冨田由紀治氏インタビュー - GAME Watch)のは、マッチしていました。とはいえ、まずは日本、です。そもそも全然半人前だと痛感する毎日で寝言は寝てから言え状態なので、日々鍛錬ですね。
ソーシャルゲーム
このサイトの前身はゲーム攻略サイト(Xbox系)で、2002年からです(このサイトのアレゲな配色はその頃から全く変わってないから、というのが大きな理由です)。また、特に好きなGeometry Warsなんて世界ランキングでもかなり上位だったりニコ動にプレイ動画を投稿したりする程度には、一応そこそこハードなゲーマーを自称していたのですが、最近はめっきりゲームとは遠ざかってしまいました。あうあう。完全にプログラミング系サイトですしねー。
というわけで、元ゲーマーとでもしませうか。で、ソーシャルゲーム。ゲーマーといったらソーシャルゲームは割と忌み嫌うという感じの!ふむ。まあでも、ほら、最近はアイドルマスター シンデレラガールズが(色々な意味で)話題だったり、それはそれで独特に面白さってのはあるんですね。いや、モゲマス面白いですよ、ガチャ地獄とか除いても普通に。必ず人と繋がり合う(それが衣装泥棒であっても)、それも緩やかに非同期に(MMORPGや対戦系は同期的ですから)というのは、独特なものがあると思います。
正直いって、まだ完全に面白さには繋がってないとは思いますが(特にゲーマー向けには)、このシンプルで非常に限られた中からゲーム性(と○○)を引き出すチャンスは転がっている、かもしれません。ずっとガラケー向けに貧相な画面とボタン連打だけで変わらない、わけでは、ない。スマートフォンの時代は来ているし、HTML5の流れだってあるし、それはソーシャルゲームだって同じなのです。
フロントエンドはモバイル向けというのを考慮しながらもリッチしなければならないし、バックエンドはハイパフォーマンスに耐えなければならない。課題は大量にありますが、だからこそ技術者としては挑戦しがいのある面白いテーマが山のように転がっています。例えばC# 5.0が非同期でハイパフォーマンスといったって、別にそんなのそこまで必要じゃないしなー、で終わってしまう状況もいっぱいあると思うのです。でも、弊社では、今すぐにでも必要なのだというぐらいに切羽詰ってる。C#の能力をフルに使いきることが求められる。これは楽しいですね?はい、楽しいです。
会社員ですからー
インタビュー記事が求人記事なので、会社員として宣伝しました!なので基本的にイイ事しか言いませんが、勿論イクナイ面もそれなりにあります。例えば最近の私の記事を見ると某テクノロジーをやたらDisってますが、なんでなんでしょうかねー、ふふり。とはいえ、不満に思うなら自分でぶち壊して再構築すればいいし、それが許される(勿論ちゃんと能力を証明したうえで)環境だとは思います。スキルある人が何の制約もなく存分に腕をふるえるのなら、素敵な話ではないでしょうか。
ちなみに私のスキルはC#/LINQに偏っていてウェブとかASP.NETの知識は並なので、毎日勉強です、人はそれを付け焼刃とも言う。
Visual Studio 11の非同期("C#, ASP.NET, Web Forms, MVC")
- 2012-03-18
世の中ひどぅーきひどぅーきと騒ぐばかりで、猫も杓子もNode.js。でもですね、【デブサミ2012】16-A-5 レポート ソーシャルアプリケーションにおけるNode.jsの活かし方(1/2):CodeZineなんかを見ても、そこで独自に作りこんでる例外処理だの非同期フロー管理だのは、そりゃあ必要ですよね、まずはそこから始めるのは当然ですよね、と思いつつC#は最初から備えているんですよね。むしろ色々とC#のほうが、とか思ったりするわけですが(勿論Node.jsのほうがGoodなものもありますが)、こんなところで嘆いていても始まらないのでC#流の非同期の活かし方を見ていきましょうか。
HttpTaskAsyncHandler
ASP.NETの非同期ハンドラはIHttpAsyncHandlerなわけですが、VS11ではそれをTask(つまりC# 5.0 async/await)で扱いやすくした基底クラス、HttpTaskAsyncHandlerが用意されています。例えばTwitterの検索を叩いて返すだけどのものは以下のようになります。
public class TwitterSearchHandler : HttpTaskAsyncHandler
{
public async override Task ProcessRequestAsync(HttpContext context)
{
var term = context.Request.QueryString["q"];
var json = await new HttpClient().GetStringAsync("http://search.twitter.com/search.json?q=" + term);
context.Response.ContentType = "application/json";
context.Response.Write(json);
}
}
普通と違うのはasyncとawaitだけなので、特に混乱もなく同期→非同期に乗り換えられると思います。非常に簡単。
HttpClientも.NET 4.5からの新顔で、WebClientの後継的な位置付けでしょうか。細かいコントロールも可能で、かつ、WebRequestよりも簡単で、非同期にもきっちりマッチしている。というかHttpClientには同期的なメソッドは用意されていません。これからの非同期世代に完全準拠した新しいクラスということですね。
そして、テスト用のサーバー立てるのも非常に簡単で。Visual Studioで新規で空のASP.NETサイトプロジェクトを作って、↑のハンドラ足して、Ctrl + F5すればIIS Expressが立ち上がって、もうそれだけでOKなわけですよ。超簡単なわけですよ、マジでマジで。
こないだ、RIA アーキテクチャー研究会 第3回でのセッションではそうして作ったHttpTaskAsyncHandlerで、 context.Response.StatusCode = 404 にしてエラーを返した状態を再現したりしながらデモしていました。
今回はTaskを中心にしましたが、Rxを中心にしたものをSilverlightを囲む会in東京#6で3/31に話す予定なので、まだ募集中なので是非来て下さい。また、Rx v2.0に関してはReactive Extensions v2.0 Beta available now! - Reactive Extensions Team Blog - Site Home - MSDN Blogsで超詳細に書かれていますね。私もちょいちょいと書きたいことは溜まってるのですが中々にぐぬぬぬ。
非同期ページ
今更Web Formsとか超どうでもいいって感じが世界全体に漂ってるし真面目に色々と腐ってると本気で思うしDataSetとWeb Formsは今となっては.NET三大汚点の筆頭かなとか思ったり思わなかったり適当に言ったり呪詛を吐いたり、もう色々アレなのですが、それでも現実とは戦わなければならないのです!
というわけでVS11のASP.NET Web Formsの非同期の強化でも見てみましょう。C# 5.0でasync/awaitが入るのでASP.NET MVCのほうは非同期コントローラーでヒャッホイなのですがWeb Formsも一応対応してきました、一応ね、一応。
// Web.config
<appSettings>
<add key="aspnet:UseTaskFriendlySynchronizationContext" value="true" />
</appSettings>
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="~/WebForm1.aspx.cs" Inherits="WebApplication8.WebForm1"
Async="true" ViewStateMode="Disabled" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title>Async Test</title>
</head>
<body>
<form id="form1" runat="server">
<asp:TextBox ID="WordTextBox" runat="server" />
<asp:Button ID="SearchButton" runat="server" Text="Button" OnClick="SearchButton_Click" />
<asp:Repeater runat="server" ID="TwitterStatuses" ItemType="dynamic">
<ItemTemplate>
<p>
<asp:Label runat="server" Text="<%#: Item.from_user %>" /><br />
<asp:Label runat="server" Text="<%#: Item.text %>" />
</p>
</ItemTemplate>
</asp:Repeater>
</form>
</body>
</html>
// namespace WebApplication8
public partial class WebForm1 : System.Web.UI.Page
{
protected void SearchButton_Click(object sender, EventArgs e)
{
var task = new PageAsyncTask(async () =>
{
var word = WordTextBox.Text;
using (var stream = await new HttpClient().GetStreamAsync("http://search.twitter.com/search.json?q=" + word))
{
var json = System.Json.JsonObject.Load(stream);
TwitterStatuses.DataSource = json["results"];
}
DataBind();
});
RegisterAsyncTask(task);
}
}
非同期ページの利用には Async="true" 属性をつける必要があります。.NET 4.0まではつけていない場合は、同期的に動作するようになっていたのですが、.NET 4.5からはエラーになるように挙動が変更されています。また、PageAsyncTaskを利用する場合はWeb.configにUseTaskFriendlySynchronizationContext = true する必要もあるっぽいです。
これ自体はテキストボックスに検索語を入れてボタンを押すとひどぅーきでTwitter検索して表示する、というだけのDoudemoii代物です。PageAsyncTaskが引数にTaskを受け入れるようになったので、そこでasyncなラムダ式を突っ込んでやればいい、というわけで、まあまあ簡単と言えなくもなく仕上がっています。理想的には/直感的にはasync void SearchButton_Clickと書けるようになるべきなのですが、そうはいかないようです、残念。
JSONは.NET 4.5からお目見えのSystem.Jsonを使いました。これ、AsDynamic()とするとdynamicで扱えるのでサクサクッと使えて便利です。また、そのdynamicとして使える性質を活かして、dynamicのままバインドしてみました(AsDynamicはコード上dynamicにキャストするというだけで、JsonValueはそのもの自身がdynamic = IDynamicMetaObjectProviderなのです)。System.JsonはNuGet - System.Jsonにもあるので、.NET 4ではそれを使えばいいでしょう。DynamicJsonはお払い箱で。
それとRepeaterのItemType="dynamic"。これでItem.from_userといったように、dynamicに使えるようになっています。匿名型をバインドしたい時なんかも、同じようにItemType="dynamic"にしてしまうといいかな、と思ったんですが、それは出来ませんでした。あともう一歩、気を利かせてくれても良かったですねえ。
まあ、VS11からは、念願のバインディング式の中でIntelliSenseが効くようになっていて、それはRepeaterのItemTypeも例外ではないので、ちゃんと型作ってあげるのも良いとは思います。あと%:でHtmlEncodeもしてくれますのも良いところ。
ViewStateMode="Disabled"で無駄なViewStateは生成しないようにするのも大事。これは.NET 4.0からですね。EnableViewStateとは別物という紛らわしさが残っているのも、まあなんともかんとも。ところでPageのViewStateModeをDisableにしてしまうと、this.ViewState[]が使えなくなってしまうので、マスターページからの、asp:Contentにしかけたほうがいいかもです。
EventHandlerTaskAsyncHelper
ASP.NETの非同期関連はMSDNマガジンのWickedCode: ASP.NET の非同期プログラミングを使ったスケール変換可能なアプリケーションにまとまっていますが、そこにあるとおり非同期ページの実現方法にはもうひとつ、AddOnPreRenderCompleteAsyncを使う方法があります。それにもTask用のやり方がありますので、見てみましょう。
var helper = new EventHandlerTaskAsyncHelper(async (_, __) =>
{
var word = WordTextBox.Text;
using (var stream = await new HttpClient().GetStreamAsync("http://search.twitter.com/search.json?q=" + word))
{
var json = System.Json.JsonObject.Load(stream);
TwitterStatuses.DataSource = json["results"];
}
DataBind();
});
AddOnPreRenderCompleteAsync(helper.BeginEventHandler, helper.EndEventHandler);
EventHandlerTaskAsyncHelperを作り、それのBeginとEndをAddOnPreRenderCompleteAsyncに渡してあげます。ちょっとPageAsyncTaskより面倒ですね。まあ、でも、どちらでもいいでしょう。大した違いはありません。二つやり方があるとどちらにすればいいのかと迷ってしまうのが良くないところなんですよねえ、しかもどちらも似たようなものだと……。
非同期モジュール
Moduleについても見てみましょう。感覚的にはAddOnPreRenderCompleteAsyncと一緒で、EventHandlerTaskAsyncHelperを作り、追加したいイベントにBeginとEndを渡します。
public class MyModule : IHttpModule
{
public void Init(HttpApplication application)
{
var helper = new EventHandlerTaskAsyncHelper(async (sender, e) =>
{
var app = (HttpApplication)sender;
var path = app.Server.MapPath("~/log.txt");
using (var fs = new FileStream(path, FileMode.Append, FileAccess.Write, FileShare.Read, 4096, useAsync: true))
using (var sw = new StreamWriter(fs, Encoding.UTF8))
{
await sw.WriteLineAsync("Request:" + DateTime.Now);
}
});
application.AddOnBeginRequestAsync(helper.BeginEventHandler, helper.EndEventHandler);
}
public void Dispose() { }
}
AddOnXxxAsyncは沢山あるので、追加したいイベントを選べばいいでしょう。また、非同期でファイルを扱いたい時は、useAsync: trueにするのが大事です。デフォルトはfalseになっているので、Begin-Endをしても非同期にならない(というかスレッドプールを使った挙動になってしまう)そうです(と、プログラミング.NET Frameworkに書いてあった)。
非同期コントローラー
一応ASP.NET MVCでも見てみましょうか。TwitterのPublicTimelineを表示するだけのものを(テキストボックスすら作るのが面倒になってきた)
public class PublicTimelineController : AsyncController
{
public async Task<ActionResult> Index()
{
using (var stream = await new HttpClient().GetStreamAsync("https://twitter.com/statuses/public_timeline.json"))
{
var json = System.Json.JsonObject.Load(stream);
return View(json);
}
}
}
<!DOCTYPE html>
<html>
<head></head>
<body>
@foreach (var item in Model)
{
<p>
@item.user.screen_name
<br />
@item.text
</p>
}
</body>
</html>
AsyncControllerの自然さと、きゃーRazor最高ー抱いてー。
まとめ
HttpTaskAsyncHandlerにせよEventHandlerTaskAsyncHelperにせよ、中身は割とシンプルにTaskでラップしただけなので、それを自前で用意すればTask自体は.NET 4.0に存在するので、async/awaitは使えませんがそれなりに簡単に書けるようにはなります。とりあえず私はWeb Forms用のものを仕事で使うために用意しました。コードは会社で書いたものなので上げられませんが!というほど大したものでもないので上げちゃってもいいんですが上げません!Web Formsにはとっととお亡くなりになってもらいたいので。延命措置禁止。
Web Formsだって悪くないものだ、全力で頑張ればほら、こんなに出来るじゃないか、ということは容易い、ことはまったくなく全力なわけですが、しかし可能ではあるんですね、モバイル対応だろうがハイパフォーマンスサイトだろうが。きっとたぶん。でもね、なんかもうIE6にも対応しつつHTML5サイトです、とかやるぐらいに不毛感漂ってるし、その労力は別のとこに向けたいですよね、っていうか別のとこに向けばどれだけ幸せになれるだろうか、と思ってしまうのです。
考えてみると、こうもうぇぶけーな話を書くのも初めてな気がする。近頃はお仕事がそっち方面なので、出せる範囲でちょいちょい出してこうかと思います。とにかく結論としてはWeb Formsちゃんは、もう沢山頑張ったと思うのでそろそろ逝ってもらって構いません。
LINQのWhereやSelect連打のパフォーマンス最適化について
- 2012-03-08
Where連打していますか?それともパフォーマンスの悪化を心配して&&連結にしていますか?LINQの仕組み&遅延評価の正しい基礎知識 - @ITではWhere+Selectに対して
「WhereSelectEnumerableIterator」となっていて、名前のとおり、WhereとSelectが統合されていることです。これは、「Where」->「Select」が頻出パターンなので、それらを統合することでパフォーマンスを向上させるためでしょう。
と書きましたが、では連打の場合はどうなっているでしょうか。見てみましょう。
var seq1 = Enumerable.Range(1, 10)
.Where(x => x % 2 == 0)
.Where(x => x % 3 == 0);
// どうでもいいんですが、これはVisual Studio 11 Betaです。VS11最高ですよ!
@ITの記事では、sourceに格納されて内包した形の連鎖となっている、と書きました。しかしseq1のsourceはRangeIteratorで、Where連打のはずなのに、すぐ上の階層が元ソースとなっています。そして、predicateの名前がCombinePredicates。はい、その通りで、2つの条件式が連結されています。確認してみましょう。
var pred = (Func<int, bool>)seq1.GetType().GetField("predicate", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(seq1);
Console.WriteLine(pred(2)); // false
Console.WriteLine(pred(3)); // false
Console.WriteLine(pred(6)); // true
というわけで、Where連打はpredicateが連結されて一つのWhereに最適化されることが確認できました。LinqとCountの効率でICollectionやIListの場合の特別扱いなケースがあることを紹介しましたが、Whereに関しても同様な特別扱いが発生するというわけです。
Selectの場合
Whereの他にSelectの場合も、同じような最適化を行ってくれます。
var seq2 = Enumerable.Range(1, 10)
.Select(x => x * 2)
.Select(x => x + 10);
var selector = (Func<int, int>)seq2.GetType().GetField("selector", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(seq2);
Console.WriteLine(selector(2)); // 2 * 2 + 10 = 14
Console.WriteLine(selector(5)); // 5 * 2 + 10 = 20
sourceのすぐ上がRangeIteratorで、selectorにCombineSelectorsとして格納されていました。なお、型名であるWhereSelectEnumerableIteratorのとおり、現在はpredicateはnullですが、前段にWhereを書けばpredicateに格納されて、やはりWhere+Selectの最適化となります。では、後段にWhereを書いた場合は……?
最適化されない場合
Where+SelectとSelect+Whereは異なるものです。見てみましょう。
var whereSelect = Enumerable.Range(1, 10)
.Where(x => x % 2 == 0)
.Select(x => x * 2);
var selectWhere = Enumerable.Range(1, 10)
.Select(x => x * 2)
.Where(x => x % 2 == 0);
Where+SelectはWhereSelectEnumerableIteratorのpredicateとselectorにそれぞれデリゲートが格納され、ひとまとめに最適化されていますが、Select+WhereはsourceがRangeIteratorではなくWhereSelectEnumerableIteratorであるように、普通に階層の内包構造となっています。Selectの後にWhereは最適化されません。まあ、そりゃ値が変形されているのだからpredicateがひとまとまりになるわけがなく、当たり前ではあります。
次にインデックスが使えるオーバーロードのケースを見てみましょう。
var whereIndex = Enumerable.Range(1, 10)
.Where(x => x % 2 == 0)
.Where((x, i) => i % 2 == 0);
var selectIndex = Enumerable.Range(1, 10)
.Select(x => x * 2)
.Select((x, i) => i * 2);
// GetEnumeratorしないとpredicate/selectorとsourceがnullです
// これはyield returnによる生成なためです
whereIndex.GetEnumerator();
selectIndex.GetEnumerator();
これもひとまとめにしようにも、しようがないので、当然といえば当然ですね。
IQueryableの場合
IQueryableだとどうなのか、というと……
// LINQ to SQL - AdventureWorks sample
var ctx = new AdventureWorksDataContext();
var query = from model in ctx.ProductModel
where model.Name == "hoge"
where model.ProductModelID == 100
select model.Instructions;
Console.WriteLine(query);
// 結果
SELECT [t0].[Instructions]
FROM [Production].[ProductModel] AS [t0]
WHERE ([t0].[ProductModelID] = @p0) AND ([t0].[Name] = @p1)
というわけで、LINQ to SQLはand連結されますね。ここで注意なのが、どういう挙動を取るのかは全てクエリプロバイダの解釈次第です。例えばLINQ to Twitterはwhere連打ではダメで、&&で連結しなければなりません。
Reactive Extensionsの場合
Reactive Extensionsの場合も見てみましょうか。Rx-Main(1.0.11226)では、というと
var rx = Observable.Range(1, 10)
.Where(x => x % 2 == 0)
.Where(x => x % 3 == 0);
さっぱりワケワカメですが、とりあえずひとまとめになってないのでは感でしょうか。それにしても本当にワケワカメ。次にRx_Experimental-Main(1.1.11111)は、というと
var pred = (Func<int, bool>)rx.GetType().GetField("_predicate", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(rx);
Console.WriteLine(pred(2)); // false
Console.WriteLine(pred(3)); // false
Console.WriteLine(pred(6)); // true
_predicate発見!Experimental版では挙動が改善され、ひとまとめにされているようです。IDisposable<Generate>は、Rangeの生成がGenerateメソッドによってなされているからですね。しかし、やはり読み取りにくい。
Rx v2
3/5にReactive Extensions (Rx) v2.0 Betaの配布がスタートしています。NuGetではInstall-Package Rx-Main -Preで配布されていますね。改善内容は後日詳しくということでまだ詳しくは出てないのですが、v2というだけあって中身はガラッと変わっています。とりあえず、見てみましょう。
もちろん、predicateはひとまとめにされているのですが、それだけじゃなくて、とにかく見やすい、分かりやすい。しかし、ところどころ変ですね、Observαble(aがアルファ)だのΩだの。v2はソースのキチガイ度が跳ね上がっているのでILSpyとかで覗いちゃえる人は一度見ちゃうといいと思います、頭おかしい。あと、C#でのプログラミング的な小技も効いてたりして、テクニックの学習にもとても良い。
スタックトレースへの影響
このコードのクリアさはスタックトレースにも良い影響を与えています。まず、Rx v1で見てみると
try
{
Observable.Range(1, 10)
.Where(x => x % 2 == 0)
.Take(10)
.Timestamp()
.Subscribe(_ => { throw new Exception(); });
}
catch (Exception ex)
{
Console.WriteLine(ex.StackTrace);
}
場所 ConsoleApplication9.Program.<Main>b__1(Timestamped`1 _) 場所 c:\Users\ne
場所 System.Reactive.AnonymousObserver`1.Next(T value)
場所 System.Reactive.AbstractObserver`1.OnNext(T value)
場所 System.Reactive.AnonymousObservable`1.AutoDetachObserver.Next(T value)
場所 System.Reactive.AbstractObserver`1.OnNext(T value)
場所 System.Reactive.Linq.Observable.<>c__DisplayClass408`2.<>c__DisplayClass40a.<Select>b__407(TSource x)
場所 System.Reactive.AnonymousObserver`1.Next(T value)
場所 System.Reactive.AbstractObserver`1.OnNext(T value)
場所 System.Reactive.AnonymousObservable`1.AutoDetachObserver.Next(T value)
場所 System.Reactive.AbstractObserver`1.OnNext(T value)
場所 System.Reactive.Linq.Observable.<>c__DisplayClass43e`1.<>c__DisplayClass440.<Take_>b__43d(TSource x)
// 以下略
これは酷い。こんなの見ても何一つ分かりはしません。では、Rx v2で試してみると
場所 ConsoleApplication10.Program.<Main>b__1(Timestamped`1 _) 場所 c:\Users\n
場所 System.Reactive.AnonymousObserver`1.Next(T value)
場所 System.Reactive.ObserverBase`1.OnNext(T value)
場所 System.Reactive.Linq.Observαble.Timestamp`1._.OnNext(TSource value)
場所 System.Reactive.Linq.Observαble.Take`1._.OnNext(TSource value)
場所 System.Reactive.Linq.Observαble.Where`1._.OnNext(TSource value)
場所 System.Reactive.Linq.Observαble.Range._.LoopRec(Int32 i, Action`1 recurse)
場所 System.Reactive.Concurrency.Scheduler.<>c__DisplayClass3a`1.<InvokeRec1>b__37(TState state1)
// 以下略
めっちゃよく分かる。Timestamp->Take->Where->Rangeという遡りがしっかり見える。何て素晴らしいんだ!
匿名 vs 有名
さて、どういうことかというと、これ、neue cc - Rx(Reactive Extensions)を自前簡易再実装するで紹介したような、ラムダ式をぶん投げてその場で匿名のクラスを作るAnonymousパターンをやめたんですね。で、代わりに名前付きのクラスを立ててる。だから分かりやすい。
これ、uupaaさんが仰ってるナビ子記法←ググッた先の本人のスライドが、Handsoutがサービス終了で見れないので、紹介のある記事にリンクします-などにも近いところがあるかなあ、と。
ただやっぱ書くのにはコスト高というか匿名で書けることの良さを殺してしまうので、ライブラリサイドだったら検討する、アプリケーションサイドだったらやらない、になってしまうかなあ。ライブラリサイドであってもかなり手間なので、よほど余裕あるとかでないとやらない、かなあ。JavaScriptならともかくC#では、特に……。
Rx v2についてもう少し
詳しい話は詳細が出てから、と思いますが(と言いながらRxJSの話も結局書いてないので、あうあう)、とりあえずObservableへの拡張メソッド郡はExperimentalから変化は特にありません。ただ、Experimentalは既にStableとはかなり違っているので、Stableしか追っかけてない人は、かなり目新しいものを見かけることができると思います。
内部実装は見たとおりガラッと変わって、スタックトレースも見やすくなった、などなどなわけですが、それとあわせてパフォーマンスも相当上がっています。v1で基本的な部分を固めたので、v2ではそういった周辺部分に本気で取り組みだした、ということですね。
まとめ
LINQは細かいところまで配慮が行き届いていて本当に素晴らしいですね。というわけで平然とWhereの連打かましましょう。私もつい昨日にWhere6連打かましたりしてました。
linq.js - LINQ for JavaScriptはさすがにここまではやってないんですが、いずれかはやりたいですね。その前にやらなきゃならないことがありすぎて当面はないですけれど。うーん、なんかもう色々やることありすぎて、かつそれなりに忙しくて、頭が爆発しそうです。はっきしいってヤバい。で、こうしてヤバくなると、硬直しちゃって余計に何もできなくなったり、唐突にこうして息抜き記事を書き出したり、うみみぅ。まあともかく、がんばろふ。
とあるRoslynではないC# Parser、NRefactoryの紹介
- 2012-02-23
ロッズリーン、はっじまらないよ~。というわけでMicrosoft “Roslyn” CTP、Compiler as a Service。NuGet - Roslynでも手に入るので、サンプル類やC# Interactiveとかはなしで、とりあえずScriptingやCompilerを触ってみたい、ということなら、お手軽です。しかし、まあ未実装も少なくなく、まだまだ先は長そうな雰囲気ではある。今すぐ欲しいのに!切実にC# Parserが!というわけで、今日はその良き代替となる(かもしれない)NRefactoryを紹介します。
NRefactoryはNuGet - NRefactoryからも入ります。verは5.0.0.4、「This is an alpha release. Expect bugs and breaking changes in the future.」とのことで、こちらもまだまだこれからのよう(MonoDevelopの新しいC#エディタで使われる予定、だそうです)。とりあえずNuGetで参照してみませう。
参照するとMono.Cecilが入ったり名前空間にMono.CSharpがあったりと、全体的にMonoのCSharp Compilerやその周辺が使われているふいんき。Mono.CSharpは単体でもついこないだNuGet - Mono.CSharpで入れられるようになりましたが、そのまんまだと、なんというかどう使っていいか分からないというか、はいEvalできた、さて、はて?みたいになってしまって。そのへん、NRefactoryはゆるふわで、結構すぐに使い方分かります。とりあえず使ってみませう。
using System;
using System.IO;
using System.Linq;
using ICSharpCode.NRefactory.CSharp;
class Program
{
static void Main(string[] args)
{
var code = File.ReadAllText(@"../../Program.cs");
var parser = new CSharpParser();
var root = parser.Parse(code, "");
var program = root.Descendants.OfType<ICSharpCode.NRefactory.CSharp.TypeDeclaration>().First();
program.Name = "Hogegram";
Console.WriteLine(root.ToSourceString());
}
}
public static class CompilationUnitExtensions
{
static readonly CSharpFormattingOptions DefaultOptions = new CSharpFormattingOptions()
{
// TODO:130のboolを自分の気にいるようなOption(というかVSのデフォ)に近づける
};
public static string ToSourceString(this CompilationUnit compilationUnit, int indentation = 4, string indentationString = " ")
{
return ToSourceString(compilationUnit, DefaultOptions, indentation, indentationString);
}
public static string ToSourceString(this CompilationUnit compilationUnit, CSharpFormattingOptions options, int indentation = 4, string indentationString = " ")
{
using (var sw = new StringWriter())
{
var formatter = new TextWriterOutputFormatter(sw)
{
Indentation = indentation,
IndentationString = indentationString
};
var visitor = new CSharpOutputVisitor(formatter, options);
compilationUnit.AcceptVisitor(visitor);
return sw.ToString();
}
}
}
Program.csを読み込んで、クラス名をHogegramに変更したのを出力する、というだけのものです。今のところ整形した文字列化にはCSharpOutputVisitorを作って、AcceptVisitorしなきゃならないようで面倒ぃので、とりあえず拡張メソッドにしました。デフォルトの整形オプションが気に食わないのですが、フォーマット設定が超細かくて100個以上のboolをON/OFFしなきゃいけないのでとりあえず放置。
構文木の取得自体は超単純で、new CSharpParser()してParse。そしてツリーを辿るのはLINQ to Xmlと同じ感覚でDescendantsやAncestors、Childrenなどなどが用意されているので、一発で分かりますね!そしてOfTypeで自分の欲しいのにフィルタリング、と。この辺は超簡単。そして名前を変えたければ、プロパティに代入するだけ。非常に楽ちんです、素晴らしい……!
vs Roslyn
さて、じゃあRoslynでも同じことをやってみましょう。
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Roslyn.Compilers.CSharp;
class Program
{
static void Main(string[] args)
{
var code = File.ReadAllText(@"../../Program.cs");
var tree = SyntaxTree.ParseCompilationUnit(code);
var program = tree.Root.DescendentNodes().OfType<ClassDeclarationSyntax>().First();
var newNode = new ClassNameRewriter(new Dictionary<ClassDeclarationSyntax, string> { { program, "Hogegram" } })
.Visit(tree.Root);
Console.WriteLine(newNode.ToString());
}
}
public class ClassNameRewriter : SyntaxRewriter
{
readonly IDictionary<ClassDeclarationSyntax, string> replaceNames;
public ClassNameRewriter(IDictionary<ClassDeclarationSyntax, string> replaceNames)
{
this.replaceNames = replaceNames;
}
protected override SyntaxNode VisitClassDeclaration(ClassDeclarationSyntax node)
{
var oldIdentifierToken = node.Identifier;
if (replaceNames.ContainsKey(node))
{
var newNode = node.Update(node.Attributes,
node.Modifiers, node.Keyword,
Syntax.Identifier(
oldIdentifierToken.LeadingTrivia,
replaceNames[node], // ここだけ!
oldIdentifierToken.TrailingTrivia),
node.TypeParameterListOpt, node.BaseListOpt,
node.ConstraintClauses, node.OpenBraceToken,
node.Members, node.CloseBraceToken,
node.SemicolonTokenOpt);
return newNode;
}
else
{
return base.VisitClassDeclaration(node);
}
}
}
構文木の取得はこちらも簡単です、SyntaxTree.ParseCompilationUnitだけ。ツリーの辿り方も似ていて、DescendentNodes、ChildNodes、Ancestors、と、戸惑うことなく使える感じです。OfTypeでフィルタして絞り込みも同じ。書き換えたコードの出力は、こちらはToStringだけでOK、しかもフォーマットルールは何の設定もいらずVS標準と同じ状態になっているので楽ちん。
が、しかし、こちらはクラス名の書き換えが面倒。Expression Treeと同じく基本的にイミュータブルになっているので、書き換えはプロパティに代入するだけ、とはいかず、大掛かりな仕掛けが必要です。というかこの程度のためだけにVisitorとか……。一応ReplaceNodeとかUpdateとかもあるんですが、うーん、まあ、何というか、よくわかってないので深く突っ込まれると窮します:)
まとめ
提供する機能は同じでも、使い心地とかは、両者、結構違くなるのではという印象。どちらも発展途上なのですが、ライセンス的にRoslynは今は使えないのに比べると、NRefactoryはMITライセンスで、アルファ版なのを留意しておけば使えるのではというのが、私的には大きいかなあ。割と切迫してC# Parserが必要なところなので、ここはちょっとNRefactoryを使い込んでみたいなあ、なんて思っています。いや、そこまでDeepに使い倒すというより簡単なコード解析と置換程度なので、しっかり使ってバグ報告、とかの貢献は出来なさそうですんがー。
ImplicitQueryString - 暗黙的変換を活用したC#用のクエリストリング変換ライブラリ
- 2012-02-18
QueryStringかったるいですね、変換するのが。intに。boolに。それ以外に。そのままじゃどうしようもなくストレスフル。そこで、以下のように書けるライブラリを作りました。勿論NuGetでのインストールも可能です。あと、ライブラリといっても例によって.csファイル一個だけなので、導入は超お手軽。
以下はASP.NETの例ですが、NameValueCollectionを使っているものは全て対象になります。QueryStringだけじゃなくてRequest.Formなどにも使えますね。
// using Codeplex.Web;
int count;
string query;
bool? isOrdered; // support nullable
protected void Page_Load(object sender, EventArgs e)
{
count = Request.QueryString.ParseValue("c");
query = Request.QueryString.ParseValue("q");
isOrdered = Request.QueryString.ParseValue("ord");
}
NameValueCollectionへの拡張メソッドとして実装しているので、using Codeplex.Webを忘れないように。ポイントは「型の明示が不要」というところです。クエリストリングを解析したい時って、通常はフィールドで既に変数自体は作っているのではないかと思います。なら、代入時に左から型を推論させてしまえばいいわけです、モダンなC#はわざわざ明示的に型なんて書かない、書きたくない。なのでこのライブラリ、ImplicitQueryStringではParseValueだけで全ての基本型(int, long, bool, string, DateTimeなど)へと型指定不要で代入可能となっています。代入先の型がNullableである場合は、キーが見つからなかったりパースに失敗した場合はnullにすることもサポートしてます。
また、よくあるクエリストリングはUrlEncodeされているのでデコードしたかったりするケースにも簡単に対応しています、UrlDecodeを渡すだけ!他にもEnumへの変換も出来ますし、キーが見つからなかったり変換不可能だったりした場合は指定したデフォルト値を返すParseValueOrDefault/ParseEnumOrDefaultも用意してあります。キーがあるかチェックするContainsKeyも。
enum Sex
{
Unknown = 0, Male = 1, Female = 2
}
enum BloodType
{
Unknown, A, B, AB, O
}
int age;
string name;
DateTime? requestTime; // nullableやDateTimeもサポート
bool hasChild;
Sex sex; // enumもいけます
BloodType bloodType;
protected void Page_Load(object sender, EventArgs e)
{
// こんなQueryStringがあるとして
// a=20&n=John%3dJohn+Ab&s=1&bt=AB
// ageは左から推論してintを返します
age = Request.QueryString.ParseValue("a"); // 20
// UrlDecodeしたstringが欲しい時は第二引数にメソッドそのものを渡すだけ
name = Request.QueryString.ParseValue("n", HttpUtility.UrlDecode); // John=John Ab
// 代入先の型がnullableの場合は、もしキーが見つからなかったりパースに失敗したらnullにしてくれます
requestTime = Request.QueryString.ParseValue("t", HttpUtility.UrlDecode); // null
// キーが見つからなかったりパースに失敗したら指定した値を返してくれます
hasChild = Request.QueryString.ParseValueOrDefault("cld", false); // false
// Enumの変換は数字の場合でも文字列の場合でも、どちらでも変換可能です
sex = Request.QueryString.ParseEnum<Sex>("s"); // Sex.Male
bloodType = Request.QueryString.ParseEnumOrDefault<BloodType>("bt", BloodType.Unknown); // BloodType.AB
// ContainsKeyはキーの有無をチェックします
var hasFlag = qs.ContainsKey("flg"); // false
}
これで、あらゆるケースでサクッと変換することが可能なのではかと思います。ちなみに、ParseValue/ParseEnumでキーが見つからなかった場合はKeyNotFoundExceptionを返します。ParseValueで失敗したらFormatException、ParseEnumで失敗したらArgumentExceptionです。この二つが分かれているのは、int.Parseとかに渡しているだけなので、そちらの都合です。
仕組み
ImplicitQueryStringという名前がネタバレなのですが、単純に暗黙的変換をしているだけです。左から推論とか、ただのハッタリです。neue cc - dynamicとQueryString、或いは無限に不確定なオプション引数についてを書いたときに、わざわざdynamicを持ち出さなくても、暗黙的変換で十分じゃないの?そのほうが利便性も上じゃないの?と思ったのでやってみたら、やはりドンピシャでした。
暗黙的変換は、あまり使う機能じゃあないと思いますし、実際、乱用すべきでない機能であることには違いないのですが、たまに活用する分には中々刺激的で創造性に満ちています。大昔からあるけれど普段使わない機能だなんて、ロストテクノロジーっぽくて浪漫に溢れています。
さて、ただし活用するにはコード中に型を並べなければならないので、汎用的というわけではないのもそうなのですが、人力で書くのもシンドイところ。勿論人力でやる気はしないのでT4 Templateを使いました。
<#@ template language="C#" #>
<#@ output extension="cs" #>
<#@ assembly Name="System.Core.dll" #>
<#@ import namespace="System" #>
<#@ import namespace="System.Linq" #>
<#@ import namespace="System.Text.RegularExpressions" #>
<#@ import namespace="System.Reflection" #>
<#
var methods = typeof(Convert).GetMethods(BindingFlags.Static | BindingFlags.Public);
var converters = methods.Where(x => x.Name.StartsWith("To"))
.Select(x => Regex.Replace(x.Name, "^To", ""))
.Where(x => !x.StartsWith("Base64") && x != "String")
.Distinct()
.ToArray();
#>
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
namespace Codeplex.Web
{
public static class NameValueCollectionExtensions
{
public static ConvertableString ParseValue(this NameValueCollection source, string key)
{
return ParseValue(source, key, null);
}
public static ConvertableString ParseValue(this NameValueCollection source, string key, Func<string, string> converter)
{
var values = source.GetValues(key);
if (values == null) return new ConvertableString(null);
var value = values[0];
return new ConvertableString(converter == null ? value : converter(value));
}
// 中略
}
public struct ConvertableString
{
public readonly string Value;
public ConvertableString(string value)
{
this.Value = value;
}
<# foreach (var converter in converters) { #>
public static implicit operator <#= converter #>(ConvertableString self)
{
if (self.Value == null) throw new KeyNotFoundException();
return <#= converter #>.Parse(self.Value);
}
public static implicit operator <#= converter #>?(ConvertableString self)
{
<#= converter #> value;
return (self.Value != null && <#= converter #>.TryParse(self.Value, out value))
? new Nullable<<#= converter #>>(value)
: null;
}
<# } #>
public static implicit operator String(ConvertableString self)
{
return self.Value;
}
public override string ToString()
{
return Value;
}
}
}
以上のコードから、T4生成部分を取り出すと
public static implicit operator Boolean(ConvertableString self)
{
if (self.Value == null) throw new KeyNotFoundException();
return Boolean.Parse(self.Value);
}
public static implicit operator Boolean?(ConvertableString self)
{
Boolean value;
return (self.Value != null && Boolean.TryParse(self.Value, out value))
? new Nullable<Boolean>(value)
: null;
}
public static implicit operator Char(ConvertableString self)
{
if (self.Value == null) throw new KeyNotFoundException();
return Char.Parse(self.Value);
}
public static implicit operator Char?(ConvertableString self)
{
Char value;
return (self.Value != null && Char.TryParse(self.Value, out value))
? new Nullable<Char>(value)
: null;
}
// 以下SByte, Byte, Int16, Int32, Int64, ..., DateTimeと繰り返し
といったコードがジェネレートされます。清々しいまでのゴリ押しっぷり。一周回ってむしろエレガント。ConvertableStringのほうでKeyNotFoundExceptionを出すのがイケてないのですが、まあそこはShoganaiかなー、ということで。さて、それはそれとして、やっぱVisual Studioに組み込みで、こういった自動生成のテンプレートエンジンが用意されているというのは非常に嬉しいところです。
まとめ
特にASP.NETを使っている方々にどうぞ。ASP.NET MVCの人はしらにゃい。なのでいつもの私だったら.NET 4.0専用にするところが、今回は.NET 3.5でも大丈夫!Visual Studio 2008でも動作確認取りました!
それにしても実際ASP.NETを使いこなしてる人はクエリストリングの取得はどんな風にやっていたものなのでしょうかー。何かしらの手法がないとシンドすぎやしませんか?そう思って検索してみたんですが、どうにも見つからず。さすがに ParseValueAsInt() とかって拡張メソッドぐらい作ってやり繰りは私も大昔していたし、それでintに対応しておけばほぼほぼOKではありますが(拡張メソッドがなかった時代はさすがに想像したくない!)。そう考えると、ちょっとこれはやりすぎ感も若干。でも、一度できあがればずっと使えますしね。というわけでImplicitQueryString、是非是非使ってみてください。本当に楽になれると思います。
dynamicとQueryString、或いは無限に不確定なオプション引数について
- 2012-02-14
どうもこんばんわ、30時間も寝てないわー。まあ、お陰で真昼間の就業中にうつらうつらしてましたが!ちなみにGRAVITY DAZEにはまって延々とプレイしてたから寝てないだけです。PS陣営にくだるなんて!さて、それはそれとしてBlog更新頻度の低下っぷりはお察し下さい。そんなこんなで最近の仕事(仕事ですって!仕事してるんですって!?)はASP.NETなんですが、色々アレですね。ふふふ。ソウルジェムは真っ黒と真っ白を行ったり来たりしてて楽しいです。まあ、ようするに楽しいです。楽しいのはいいとしてBlogが停滞するのはいくないので、電車でGRAVITY DAZEやりながらQueryStringうぜえええええ、とか悶々としたので帰り道に殺害する算段を整えていました。
QueryStringって、qとかnumとかiとかsとか、それそのものだけじゃ何を指してるかイミフなので、C#的にはちゃんと名前をつけてやりたいよね。だから、ただシンプルに触れるというだけじゃダメで。あと、コンバートもしたいよね。数字はintにしたいしboolは当然boolなわけで。さて、そんな時に私たちが持つツールと言ったら、dynamicです。C#とスキーマレスな世界を繋ぐ素敵な道具。今日も活躍してもらうことにしましょう。たまにしか出番ないですからね。
private int id;
private int? count;
private string keyword;
private bool isOrdered;
protected void Page_Load(object sender, EventArgs e)
{
// id=10&c=100&q=hogehoge&od=true というクエリストリングが来る場合
// dynamicに変換!
var query = this.Request.QueryString.AsDynamic();
id = query.id; // キャストは不要、書くの楽ですね!(キーが存在しないと例外)
count = query.c; // 型がnullableならば、存在しなければnull
keyword = query.q; // stringの場合も存在しない場合はnull
isOrdered = query.od(false); // メソッドのように値を渡すと、キーが存在しない場合のデフォルト値になる
}
ASP.NETの例ですが、どうでしょう、まぁまぁ良い感じじゃあなくて?ちなみに最初はnull合体演算子(??)を使いたかったのですが、DynamicObjectでラップした時点で、そもそも存在がnullじゃないということで、何をどうやってもうまく活用できなくて泣いた。しょうがないのでメソッド形式でデフォルト値を渡すことでそれっぽいような雰囲気に誤魔化すことにしました、とほほほ。
dynamicとオプション引数
クエリストリングを生成することも出来ます。
// 戻り値はdynamicで空のDynamicQueryStringを生成
var query = DynamicQueryString.Create();
// オプション引数の形式で書くと……?
query(id: 100, c: 100, q: "hogehoge", od: true);
Console.WriteLine(query.ToString()); // id=10&c=100&q=hogehoge&od=true
といった感じで、面白いのがオプション引数の使い方。匿名型渡しのように、スマートにKey:Valueを渡すことを実現しています。dynamicなので引数名は完全自由。個数も完全自由。非常にC#らしくなくC#らしいところが最高にCOOL。匿名型とどちらがいいの?というと何とも言えないところですが、面白いには違いないし、面白いは正義。C#も工夫次第でまだまだ色々なやり方が模索できるんですよー、ってところです。
実装など
眠いのでコードの解説はしません(ぉ。DynamicObjectの実装方法はneue cc - C# DynamicObjectの基本と細かい部分についてでどーぞ。overrideしていくだけなので、別に難しくもなんともないので是非是非やってみてください。ちなみに無限のオプション引数を実現している箇所は binder.CallInfo.ArgumentNames.Zip(args, (key, value) => new { key, value }) です。
public static class NameValueCollectionExtensions
{
public static dynamic AsDynamic(this NameValueCollection queryString)
{
return new DynamicQueryString(queryString);
}
}
public class DynamicQueryString : DynamicObject
{
NameValueCollection source;
public DynamicQueryString()
{
this.source = new NameValueCollection();
}
public DynamicQueryString(NameValueCollection queryString)
{
this.source = queryString;
}
public static dynamic Create()
{
return new DynamicQueryString();
}
public override bool TryGetMember(GetMemberBinder binder, out object result)
{
var value = source[binder.Name];
result = new StringMember((value == null) ? value : value.Split(',').FirstOrDefault());
return true;
}
public override bool TryInvoke(InvokeBinder binder, object[] args, out object result)
{
foreach (var item in binder.CallInfo.ArgumentNames.Zip(args, (key, value) => new { key, value }))
{
source.Add(item.key, item.value.ToString());
}
result = this.ToString();
return true;
}
public override bool TryConvert(ConvertBinder binder, out object result)
{
if (binder.Type != typeof(string))
{
result = null;
return false;
}
else
{
result = this.ToString();
return true;
}
}
public override string ToString()
{
return string.Join("&", source.Cast<string>().Select(key => key + "=" + source[key]));
}
public override IEnumerable<string> GetDynamicMemberNames()
{
return source.Cast<string>();
}
class StringMember : DynamicObject
{
readonly string value;
public StringMember(string value)
{
this.value = value;
}
public override bool TryInvoke(InvokeBinder binder, object[] args, out object result)
{
var defaultValue = args.First();
try
{
result = (value == null)
? defaultValue
: Convert.ChangeType(value, defaultValue.GetType());
}
catch (FormatException) // 真面目にやるならType.GetTypeCodeでTypeを分けて、例外キャッチじゃなくてTryParseのほうがいいかな?
{
result = defaultValue;
}
return true;
}
public override bool TryConvert(ConvertBinder binder, out object result)
{
try
{
var type = (binder.Type.IsGenericType && binder.Type.GetGenericTypeDefinition() == typeof(Nullable<>))
? binder.Type.GetGenericArguments().First()
: binder.Type;
result = (value == null)
? null
: Convert.ChangeType(value, binder.Type);
}
catch (FormatException)
{
result = null;
}
return true;
}
public override string ToString()
{
return value ?? "";
}
}
}
コンバートに対応させるために、要素一つだけのDynamicObjectを中で用意しちゃってます。そこがポイント、といえばポイント、かしら。GetDynamicMemberNamesはデバッガの「動的ビュー」に表示されるので、Visual Studioに優しいコードを書くなら必須ですね。
まとめ
dynamicはC#と外の世界を繋ぐためのもの。今日もまた一つ繋いでしまった。それはともかくとして、一番最初、DynamicJsonを実装した頃にも言ったのですが、dynamicはDSL的な側面もあって、普通に楽しめますので、ちょっと頭をひねって活用してみると、また一つ、素敵な世界が待っています。
今回はコード書くのに2時間、この記事を書くのに1時間、でしたー。
C#のO/Rマッパーのパフォーマンス測定 Part2
- 2012-01-17
以前にneue cc - C#のMicro-ORM(Dapper, Massive, PetaPoco)についてで計測したのですが、@shibayan先生がEF 4.1のDbContextのRaw SQL Queriesはどうなの?とTwitterで言ってたのを見かけたので、再度測ってみました。ていうか私はDbContextとObjectContextの違いすら分かってないんですが、DbContextは軽量な感じっぽいそうです、はい。
ベンチマークは前回と引き続きdapper-dot-netのリポジトリにあるベンチを使用しました。それにEFのバージョンを4.2に上げて、DbContextのSqlQueryを追加。また、id:taediumさんの作られているSomaは最新バージョンの1.6にしておきました。そして私の作成しているMicro-ORMであるDbExecutorのベンチも引き続き載せています。Visual Studio 2005時代のデータアクセステクノロジである型付きDataSetも加えてあります。
Mapper Query (non-buffered) took 55ms
Dynamic Mapper Query (buffered) took 56ms
Dynamic Mapper Query (non-buffered) took 56ms
hand coded took 57ms
DbExecutor ExecuteReader(Hand Coded) took 59ms
Dapper.Cotrib took 60ms
OrmLite QueryById took 60ms
DbExecutor Select took 60ms
Mapper Query (buffered) took 61ms
PetaPoco (Fast) took 62ms
PetaPoco (Normal) took 63ms
DbExecutor SelectDynamic took 63ms
Dynamic Massive ORM Query took 64ms
DbExecutor ExecuteReaderDynamic(Hand Coded) took 64ms
BLToolkit took 82ms
Simple.Data took 87ms
Linq 2 SQL Compiled took 96ms
DataSet took 108ms
SubSonic Coding Horror took 116ms
Entity framework CompiledQuery took 120ms
NHibernate SQL took 125ms
NHibernate Session.Get took 128ms
NHibernate HQL took 135ms
Soma Find took 164ms
NHibernate Criteria took 170ms
Linq 2 SQL ExecuteQuery took 207ms
Linq 2 SQL took 597ms
NHibernate LINQ took 610ms
Entity framework ExecuteStoreQuery took 634ms
Entity framework DbContext SqlQuery took 670ms
Entity framework ESQL took 725ms
Entity framework took 900ms
Entity framework No Tracking took 903ms
SubSonic ActiveRecord.SingleOrDefault took 3736ms
hand codedがExecuteReaderを手で回した手書き、「Mapper Query」はDapperのことです。複数種類があるのはオプション違い。DbExecutor(太字にしています)も同様に4種類で測っています。上位陣は何回も測ると適当に入れ替わりますし、速度的にも500回ブン回して数msとか、ほとんど誤差範囲でいいのではかと思います。
というわけで、ええと、EntityFrameworkの遅さが目立ちますね、CompiledQueryは割といいのですが、むしろそうしないと絶望的。特に、文字列で生SQLを書くはずのExecuteStoreQueryやSqlQueryがクソみたいに遅いのはどういうことなのかと問いつめたい。更に、軽量なはずのDbContextのSqlQueryよりもObjectContextのExecuteStoreQueryのほうが速いとか、頭痛くなります。オマケ機能だと思ってテキトーなのではかと思われる気がかなりします、MSもっと本気出せ。
DataSetが割と健闘しちゃってるのが、DataSet嫌いな私としては何とも言い難い感じです(笑)
まぁ、DbExecutorが速さと使い勝手を両立しているので、Micro-ORMでいいならDbExecutor使うといいですよ、はい。メンテしてないって?はい、そうですね……。割と真面目な話、色々機能拡張したいというかしなければならない必然性とかが迫っていたりしたりしなかったりするので、近いうちに再度動き出すつもりではいます。なので使ってみるといいと思います。
XboxInfoTwit - ver.2.4.0.2
- 2012-01-16
Xbox.comがリニューアルしたので、それに対応しました。今回は3日ほど本気で気づいていなくて対応が遅れてごめんなさい。最近はコメントやリクエストも放置気味で、大変反省しています。転職して少し忙しくなって、あまり気が回らなくて、という言い訳カッコワルイ。もう少し頑張ります。例によって全くテストしてないので、こいつ放置気味だしどうせ反応してくれないしいいかー、とか思わず、どうか変なところあったら報告お願いいたします。
あ、あと、今回からエラー時に前回の状態をリセットしないように変更しました。どういうことかというと、何らかのエラー(Xbox.comが不調だったり←よくある、Twitterが不調だったり)によって状態がリセットされた結果として、Power Onの投稿が連投されたりしてナンジャコリャー、といったような状態になることが防げます。多分。恐らく。きっと。
2011年を振り返る
- 2011-12-30
一年、ありがとうございました。何とか生き延びれました。ブログも冬眠せず続けられましたし、よきかなよきかな。
今年は色々ありました。やはり一番大きかったのはMicrosoft MVP for Visual C#の受賞ですね。と、いっても、今まで通りブログを書いてライブラリを書いて、というわけで、別に今までと何が変わったわけでもありません。ただ、肩書きとして威力があったりなかったり、というのはあります。あと、出す情報に関してはある程度は責任というか、適当すぎることは書けないかな、という意識は持ちました。
良くも悪くもといえば、やはりラベルが貼られると、ラベルに沿って動いてしまうというのが人の性かもしれません。少しC#贔屓が強くなりすぎたかもねー、とか。ちょっと反省。それと、他の言語を全然学べなかったなあ、というのもよくなかった。今年はちょっと色々なことに追われすぎたというかRx-WP7-Async-Roslynと、C#だけでいっぱいいっぱいだったのです……。結局Roslynはあまり追えてないし。
@ITでの連載 Reactive Extensions(Rx)入門 - @IT がスタートしたり、C#ユーザー会で基礎からのCode Contracts、すまべんでReactive Extensionsで非同期処理を簡単に、Silverlight SquareでReactiveProperty - slintokyo4といった、幾つかの勉強会で発表させて頂いたりなど、結構動き出した年でもありました。
ゲーム
死ぬほどプレイ、しなかった……。積みゲーどころか買ったけど封すら開けなかったり。こんな私は想像できなかったなあ。しかも別に時間がないわけじゃなくてさあ、だらだらとネット見てTwitter見てるだけなんですよ。ただ無駄に時間を捨てているだけで。楽しみだったはずのSkyrimにすら手を出していない。ふぅー、来年はちゃんとメリハリつけて付き合いたいものです。ねえ、だらだらネット見てるだけって、だらだらTV見てるだけと何も違わないぢゃあないか。
C#
ライブラリも色々作りました。その時の関心ごとに応じて、徹底的に調べて、不満をライブラリ作って解消する、というのが基本的なスタイルでした。ユニットテストの書き方が気に入らないからChaining Assertionを作り、DBへの生クエリ周りが気に入らないからDbExecutorを作り、INotifyPropertyChangedが気に入らないからReactivePropertyを作った。関心ごとが見つかったら、深くダイブして、新しいやり方を見つけ出す。なんていうとご立派ですが、本当に新しいこと、なんてないのですよね。既存の断片は幾らでも見つかる。だから、それらもまた徹底的に調査して、そして、自分の気にいるように融和させていく。
ずっとlinq.js - LINQ for JavaScriptをlowerCamelCaseに変更する!と思ってたのですが未だにできてはいなくて。よくないですね。ちょうど今日、新しいReactive Extensions for JavaScriptが出ました。詳しくは年明け早々に紹介したいと思いますが、これはちゃんとlowerCamelCaseになってます。いつまでも遅れていちゃマズい。
来年
ここで言うことか?という話ではありますが、12月で会社を退職し(て)ました。1月からは新しい会社で働くことになります。次の会社でもC#をメインにやっていきます(ということで雇われるわけでもありますので)。しっかり成果を出していきたいし、事例やコードなんかも、出せるならガシガシ出したいと思っています。その辺のことは追々。
来年は、MVP絡みで米MS本社でのMVP Global Summitもあり、新しい会社で働く(初めての転職です)ことにもなるしで、私としてもかなり転機な年になるのではという予感がしています。不安もありますが、同時に楽しみでもあります。2010年を振り返るで、来年には更なる成長のために動き出したい、などと言っていましたが、一応はしっかりとスタートを踏み出せたようです。
そんな感じですが、来年もよろしくお願いします。
RxとパフォーマンスとユニットテストとMoles再び
- 2011-12-21
C# Advent Calendar 2011、順調に進んでいますね。どのエントリも力作で大変素晴らしいです。私はこないだModern C# Programming Style Guideというものを書きました。はてブ数は現段階で45、うーん、あまり振るわない……。私の力不足はともかくとしても、他の言語だったらもっと伸びてるだろうに、と思うと、日本のC#の現状はそんなものかなあ、はぁ、という感じではあります。はてブが全てではない(むしろ斜陽?)とはいえ、Twitterでの言及数などを見ても、やっぱまだまだまだまだまだまだ厳しいかなあ、といったところ。Unityなどもあって、見ている限りだと人口自体は着実に増えている感じではありますけれど、もっともっと、関心持ってくれる人が増えるといいな。私も微力ながら尽力したいところです。
ところで、id:ZOETROPEさんのAdvent Calendarの記事、Reactive Extensionsでセンサプログラミングが大変素晴らしい!センサー、というと私だとWindows Phone 7から引っ張ってくるぐらいしか浮かばないのですが(最近だとKinectもHotですか、私は全然触れてませんが……)おお、USB接続のレンジセンサ!完全に門外漢な私としては、そういうのもあるのか!といったぐらいなわけですが、こうしてコード見させていただくと、実践的に使うRxといった感じでとてもいいです。
記事中で扱われているトピックも幅広いわけですが、まず、パフォーマンスに関しては少し補足を。@okazukiさんの見せてもらおうじゃないかReactive Extensionsの性能とやらを! その2のコメント欄でもちょっと言及したのですが、この測り方の場合、Observable.Rangeに引っ張られているので、ベンチマークの値はちょっと不正確かな、と思います。
// 1000回イベントが発火(発火の度に長さ3000のbyte配列が得られる)を模写
static IObservable<byte[]> DummyEventsRaised()
{
return Observable.Repeat(new byte[3000], 1000, Scheduler.Immediate);
}
// 配列をバラす処理にObservable.Rangeを用いた場合
static IObservable<byte> TestObservableRange()
{
return Observable.Create<byte>(observer =>
{
return DummyEventsRaised()
.Subscribe(xs =>
{
Observable.Range(0, xs.Length, Scheduler.Immediate).ForEach(x => observer.OnNext(xs[x]));
});
});
}
// 配列をバラす処理にEnumerable.Rangeを用いた場合(ForEachはIxのもの)
static IObservable<byte> TestEnumerableRange()
{
return Observable.Create<byte>(observer =>
{
return DummyEventsRaised()
.Subscribe(xs =>
{
Enumerable.Range(0, xs.Length).ForEach(x => observer.OnNext(xs[x]));
});
});
}
// SelectManyでバラす場合
static IObservable<byte> TestSelectMany()
{
return DummyEventsRaised().SelectMany(xs => xs);
}
static void Main(string[] args)
{
// ベンチマーク補助関数
Action<Action, string> bench = (action, label) =>
{
var sw = Stopwatch.StartNew();
action();
Console.WriteLine("{0,-12}{1}", label, sw.Elapsed);
};
// 配列をばらすケースは再度連結する(ToList)
bench(() => TestObservableRange().ToList().Subscribe(), "Ob.Range");
bench(() => TestEnumerableRange().ToList().Subscribe(), "En.Range");
bench(() => TestSelectMany().ToList().Subscribe(), "SelectMany");
// 配列をばらして連結せず直接処理する場合
bench(() => TestSelectMany().Subscribe(), "DirectRx");
// byte[]をばらさず直接処理する場合
bench(() => DummyEventsRaised().Subscribe(xs => { foreach (var x in xs);}), "DirectLoop");
// 実行結果
// Ob.Range 00:00:02.2619670
// En.Range 00:00:00.2600460
// SelectMany 00:00:00.2701137
// DirectRx 00:00:00.0852836
// DirectLoop 00:00:00.0152816
}
得られる配列をダイレクトに処理するとして、Observable.Rangeで配列のループを回すと論外なほど遅い。のですが、しかし、この場合ですとEnumerable.Rangeで十分なわけで、そうすれば速度は全然変わってきます(もっと言えば、ここではEnumerable.Rangeではなくforeachを使えば更に若干速くなります)。更に、これは配列を平坦化している処理とみなすことができるので、observerを直に触らず、SelectManyを使うこともできますね。そうすれば速度はほとんど変わらず、コードはよりすっきり仕上がります。
と、いうわけで、遅さの原因はObservable.Rangeです。Rangeが遅いということはRepeatやGenerateなども同様に遅いです。遅い理由は、値の一つ一つをISchedulerを通して流しているから。スケジューラ経由であることは大きな柔軟性をもたらしていますが、直にforeachするよりもずっとずっと遅くなる。なので、Enumerableで処理出来る局面ならば、Enumerableを使わなければなりません。これは、使うほうがいい、とかではなくて、圧倒的な速度差となるので、絶対に、Enumerableのほうを使いましょう。
また、一旦配列をバラして、再度連結というのは、無駄極まりなく、大きな速度差にも現れてきます。もし再度連結しないでそのまま利用(ベンチ結果:DirectRx)すれば直接ループを回す(ベンチ結果:DirectLoop)よりも5倍程度の遅さで済んでいます。このぐらいなら許容範囲と言えないでしょうか?とはいえ、それでも、遅さには違いないわけで、避けれるのならば避けたほうがよいでしょう。
ZOETROPEさんの記事にあるように、ここはばらさないほうが良い、というのが結論かなあ、と思います。正しくは上流ではばらさない。一旦バラしたものは復元不可能です。LINQで、パイプラインで処理を接続することが可能という性質を活かすのならば、なるべく後続で自由の効く形で流してあげたほうがいい。アプリケーション側でバラす必要があるなら、それこそSelectMany一発でばらせるのだから。
例えばWebRequestで配列状態のXMLを取ってくるとします。要素は20個あるとしましょう。最初の文字列状態だけを送られてもあまり意味はないので、XElement.Parseして、実際のクラスへのマッピングまではやります。例えばここではPersonにマッピングするとして、長さ1のIObservable<Person[]>です。しかし、それをSelectManyして長さ20のIObservable<Person>にはしないほうがいい。ここでバラしてしまうと長さという情報は消滅してしまうし、一回のリクエスト単位ではなくなるのも不都合が生じやすい。もしアプリケーション的にフラットになっていたほうが都合が良いのなら、それはまたそれで別のメソッドとして切り分けましょう。
成功と失敗の一本化
ZOETROPEさんの記事の素晴らしいのは、通常のルート(DataReceived)と失敗のルート(ErrorReceived)を混ぜあわせているところ!これもまたイベントの合成の一つの形なわけなんですねー。こういう事例はWebClientのDownloadStringAsyncのような、EAP(Eventbased Asynchronous Programming)をTaskCompletionSourceでラップしてTaskに変換する 方法: タスクに EAP パターンをラップする←なんかゴチャゴチャしていますが、TrySetCanceled, TrySetException, TrySetResultで結果を包んでいます、というのと似た話だと見なせます。
WebClientではEventArgsがCancelledやErrorといったステータスを持っているのでずっと単純ですが、SerialPortではエラーは別のイベントでやってくるのですね。というわけで、私もラップしてみました。
public static class SerialPortExtensions
{
// 面倒くさいけれど単純なFromEventでのイベントのRx化
public static IObservable<SerialDataReceivedEventArgs> DataReceivedAsObservable(this SerialPort serialPort)
{
return Observable.FromEvent<SerialDataReceivedEventHandler, SerialDataReceivedEventArgs>(
h => (sender, e) => h(e), h => serialPort.DataReceived += h, h => serialPort.DataReceived -= h);
}
public static IObservable<SerialErrorReceivedEventArgs> ErrorReceivedAsObservable(this SerialPort serialPort)
{
return Observable.FromEvent<SerialErrorReceivedEventHandler, SerialErrorReceivedEventArgs>(
h => (sender, e) => h(e), h => serialPort.ErrorReceived += h, h => serialPort.ErrorReceived -= h);
}
// DataReceived(プラスbyte[]化)とErrorReceivedを合成する
public static IObservable<byte[]> ObserveReceiveBytes(this SerialPort serialPort)
{
var received = serialPort.DataReceivedAsObservable()
.TakeWhile(e => e.EventType != SerialData.Eof) // これでOnCompletedを出す
.Select(e =>
{
var buf = new byte[serialPort.BytesToRead];
serialPort.Read(buf, 0, buf.Length);
return buf;
});
var error = serialPort.ErrorReceivedAsObservable()
.Take(1) // 届いたらすぐに例外だすので長さ1として扱う(どうせthrowするなら関係ないけど一応)
.Do(x => { throw new Exception(x.EventType.ToString()); });
return received.TakeUntil(error); // receivedが完了した時に同時にerrorをデタッチする必要があるのでMergeではダメ
}
}
成功例と失敗例を合成して一本のストリーム化。また、DataReceivedはそのままじゃデータすっからかんなので、Selectでbyte[]に変換してあげています。これで、ObserveReceiveBytes拡張メソッドを呼び出すだけで、かなり扱いやすい形になっている、と言えるでしょう。パフォーマンスも、これなら全く問題ありません。
MolesとRx
と、ドヤ顔しながら書いていたのですが、とーぜんセンサーの実物なんて持ってませんので動作確認しようにもできないし。ま、まあ、そういう時はモックとか用意して、ってSerialDataReceivedEventArgsはパブリックなコンストラクタないし、ああもうどうすればー。と、そこで出てくるのがMoles - Isolation framework。以前にRx + MolesによるC#での次世代非同期モックテスト考察という記事で紹介したのですが、めちゃくちゃ強力なモックライブラリです。パブリックなコンストラクタがないとか関係なくダミーのインスタンスを生成可能だし、センサーのイベントだから作り出せないし、なんてこともなく自由にダミーのイベントを発行しまくれます。
[TestClass]
public class SerialPortExtensionsTest : ReactiveTest
{
[TestMethod, HostType("Moles")]
public void ObserveReceiveBytesOnCompleted()
{
// EventArgsを捏造!
var chars = new MSerialDataReceivedEventArgs() { EventTypeGet = () => SerialData.Chars };
var eof = new MSerialDataReceivedEventArgs() { EventTypeGet = () => SerialData.Eof };
// SerialPort::BytesToRead/SerialPort::Readで何もしない
MSerialPort.AllInstances.BytesToReadGet = (self) => 0;
MSerialPort.AllInstances.ReadByteArrayInt32Int32 = (self, buffer, offset, count) => 0;
var scheduler = new TestScheduler();
// 時間10, 20, 30, 40でSerialData.Charsのイベントを、時間50でEofのイベントを発行
MSerialPortExtensions.DataReceivedAsObservableSerialPort = _ => scheduler.CreateHotObservable(
OnNext(10, chars),
OnNext(20, chars),
OnNext(30, chars),
OnNext(40, chars),
OnNext(50, eof))
.Select(x => (SerialDataReceivedEventArgs)x);
// 走らせる(戻り値のbyte[]はどうでもいいので無視するためUnitに変換)
var result = scheduler.Start(() => new SerialPort().ObserveReceiveBytes().Select(_ => Unit.Default), 0, 0, 100);
result.Messages.Is(
OnNext(10, Unit.Default),
OnNext(20, Unit.Default),
OnNext(30, Unit.Default),
OnNext(40, Unit.Default),
OnCompleted<Unit>(50));
}
[TestMethod, HostType("Moles")]
public void ObserveReceiveBytesOnError()
{
// EventArgsを捏造!
var chars = new MSerialDataReceivedEventArgs() { EventTypeGet = () => SerialData.Chars };
var eof = new MSerialDataReceivedEventArgs() { EventTypeGet = () => SerialData.Eof };
// SerialPort::BytesToRead/SerialPort::Readで何もしない
MSerialPort.AllInstances.BytesToReadGet = (self) => 0;
MSerialPort.AllInstances.ReadByteArrayInt32Int32 = (self, buffer, offset, count) => 0;
var scheduler = new TestScheduler();
// 時間10, 20, 30, 40でSerialData.Charsのイベントを、時間50でEofのイベントを発行
MSerialPortExtensions.DataReceivedAsObservableSerialPort = _ => scheduler.CreateHotObservable(
OnNext(10, chars),
OnNext(20, chars),
OnNext(30, chars),
OnNext(40, chars),
OnNext(50, eof))
.Select(x => (SerialDataReceivedEventArgs)x);
/* ↑までOnCompletedのものと共通 */
// 時間35でErrorのイベントを発行
MSerialPortExtensions.ErrorReceivedAsObservableSerialPort = _ => scheduler.CreateHotObservable(
OnNext<SerialErrorReceivedEventArgs>(35, new MSerialErrorReceivedEventArgs()));
// 走らせる(戻り値のbyte[]はどうでもいいので無視するためUnitに変換)
var result = scheduler.Start(() => new SerialPort().ObserveReceiveBytes().Select(_ => Unit.Default), 0, 0, 100);
// Exceptionの等値比較ができないので、バラしてAssertする
result.Messages.Count.Is(4);
result.Messages[0].Is(OnNext(10, Unit.Default));
result.Messages[1].Is(OnNext(20, Unit.Default));
result.Messages[2].Is(OnNext(30, Unit.Default));
result.Messages[3].Value.Kind.Is(NotificationKind.OnError);
result.Messages[3].Time.Is(35);
}
}
アサーションに使っているIsメソッドは、いつも通りChaining Assertionです。
Molesがいくら強力だとは言っても、イベントをそのまま乗っ取るのはデリゲートの差し替えなどで、割と面倒だったりします。しかし、FromEventでラップしただけのIObservable<T>を用意しておくと…… それを差し替えるだけで済むので超簡単になります。イベント発行については、TestScheduler(Rx-Testingを参照しておく)で、仮想時間で発行する値を作ってしまうと楽です。こういう、任意の時間で任意の値、というダミーの用意もFromEventでラップしただけのIObservable<T>があると、非常に簡単になります。
あとは、scheduler.Startで走らせると(3つの引数はそれぞれcreated, subscribed, disposedの仮想時間、何も指定しないと…… 実は0始まり「ではない」ことに注意。100,200,1000がデフォなので、0はすっ飛ばされています)、その戻り値で結果を受け取って、Messagesに記録されているので、それにたいしてアサートメソッドをしかける。
実に簡単ですね!Molesの力とRxの力が組み合わさると、イベントのテストが恐ろしく簡単になります。素敵じゃないでしょうか?
まとめ
テストなしで書いてたコードは、Molesでテスト走らせたら間違ってました。TakeWhileの条件が==だったのと、Mergeで結合していたり……。はっはっは、ちゃんとユニットテストは書かないとダメですね!そして、Molesのお陰でちゃんと動作するコードが書けたので恥を欠かなくてすみました、やったね。
Modern C# Programming Style Guide
- 2011-12-16
C# Advent Calendar 2011、ということで、C# 4.0時代のプログラミングスタイルについて説明してみます。モダン、というけれど、某書のように変態的なことじゃなくて、むしろ基本的な話のほうです。こういったものはナマモノなので、5.0になればまた変わる、6.0になればまた変わる。変わります。古い話を間に受けすぎないこと(歴史を知るのは大事だけど、そのまま信じるのは別の話)、常に知識をリフレッシュするようにすること。そういうのが大事よね。でも、だからってモダンに書けなきゃダメ!なんてことはありません。ただ、知ること、少しずつ変えていくこと、そういうのは大事よね、って。
ところでしかし、私の主観がかなり入っているので、その辺は差っ引いてください。
- varを使う
C# 3.0から搭載された型推論での宣言。出た当初には散々議論があって、今もたまに否の意見が出てきたりもしますが、varは使いましょう。積極的に。何にでも。国内的にも世界的にもMicrosoft的にも、var積極利用の方向で傾いているように見えます。また、最近流行りの関数型言語(Haskell, Scala, F#)は、少なくともC#のvarで可能な範囲は全て推論を使いますね←C#のvarはそれらに比べれば遥かに貧弱ですからね。そういったこともあるので、使わない理由もないでしょう。
var person = new Person();
var dict = new Dictionary<string, Tuple<int, Person>>();
var query = Enumerable.Range(1, 10).Select(x => x * 10);
varの利点は、何といっても書いていて楽なことです。はい、圧倒的に楽です。そして、型宣言の長さが一致するので「実は見やすい」というのもポイント高し。たった3文字の短さと相まって、ソースコードが綺麗になります。また、必ず変数の初期化を伴う、というのも良いことです。
欠点は「メソッドの戻り値などは宣言を見ても型が分からない」「インターフェイスで宣言できない」の二つが代表的でしょうか。前者は、Visual Studioを使えばマウスオーバーで型が表示されるので、コーディング上では支障はない。メールやBlogやWikiなど、Visual Studioのサポートのない完全にコードのみの状態だとサッパリなのは確かに難点ではありますが、逆にその程度の部分的な範囲なら、括り出されている目的が明確なわけなので、適切な変数名がついているのなら、正確な型名とまではいかずとも何に使うもののか大体分かるのではないでしょうか?なので、大きな問題だとは私は思いません。もし変数名がテキトーで型名ぐらいしかヒントが得られないんだよ!ということならば、varよりも前にまともな変数名をつけるようにしたほうがいいです。
インターフェイスで宣言できないことは、私は何の問題もないと思っています。具象型やメソッドの返す型でそのまま受けることに何の不都合が?むしろインターフェイスで宣言すると、アップキャスト可能という怪しい状態を作り出しているだけです。
ちなみにintやstring、配列などの基本的なものぐらいは型を書くという流儀もなくはないようですが、それは意味無いのでやめたほうがいいでしょう。
var num = 100;
var text = "hogehoge";
var array = new[] { 1, 2, 3, 4, 5 };
だって、こういうのこそ、見れば一発で分かるほど自明なので。
- オプション引数を使う(使いすぎない)
害悪もあるわけですが、割と積極的に使ってもいいような気がします。実際Roslyn CTPなどでは結構派手に使われていますし、オーバーロード地獄よりはIntelliSense的にも分かりやすいかな、って。思います。enumなど使うと、明確に何が使われるか見えるんですね、これはとても嬉しくて。やっぱC#としてはIntelliSenseで分かりやすい、というのはとても大事かと。
さて、分かりやすく使いすぎに注意な点としては、引数なしコンストラクタが消滅してしまう可能性があげられます。引数なしコンストラクタがないと、色々なところで弊害が起こります。
// こんなオプション引数なコンストラクタしかないクラスがあるとして
public class ToaruClass
{
public ToaruClass(int defaultValue = -1)
{
}
}
class Program
{
static T New<T>() where T : new()
{
return new T();
}
static void Main(string[] args)
{
// 使うときは引数なしでnewできるけど
var _ = new ToaruClass();
// 実態は違うので、ジェネリックのnew制約が不可能になる
New<ToaruClass>(); // コンパイル通らない
// 引数なしコンストラクタを要求するシリアライザの利用も不可能に
new XmlSerializer(typeof(ToaruClass)).Serialize();
}
}
シリアライズできなかったりジェネリックのnew制約がきかなくなってしまったり。ご利用は計画的に。シリアライズに関しては、DataContractSerializerならばコンストラクタを無視するので使えはしますが……。その辺の話はneue cc - .NETの標準シリアライザ(XML/JSON)の使い分けまとめで。
Roslyn CTPのAPIはオプション引数が激しく使われているのですが、中でもこれは面白いと思いました。Mindscape Blog » Blog Archive » In bed with Roslynから引用します。
PropertyDeclarationSyntax newProperty = Syntax.PropertyDeclaration(
modifiers: Syntax.TokenList(Syntax.Token(SyntaxKind.PublicKeyword)),
type: node.Type,
identifier: node.Identifier,
accessorList: Syntax.AccessorList(
accessors: Syntax.List(
Syntax.AccessorDeclaration(
kind: SyntaxKind.GetAccessorDeclaration,
bodyOpt: Syntax.Block(
statements: Syntax.List(
getter
)
)
),
Syntax.AccessorDeclaration(
kind: SyntaxKind.SetAccessorDeclaration,
bodyOpt: Syntax.Block(
statements: Syntax.List(
setter
)
)
)
)
)
);
そう、名前付き引数でツリー作ってるんですね。LINQ to XMLの関数型構築も面白いやり方だと思いましたが、この名前付き引数を使った構築も、かなり素敵です。流行るかも!
- ジェネリックを使う
もしお持ちの本がArrayListやHashTableを使っているコードが例示されていたら、窓から投げ捨てましょう。Silverlightでは廃止されていますし、WinRT(Windows 8)でも、勿論そんな産廃はありません。もはやどこにもそんなものを使う理由はありません。どうしても何でも入れられるListが欲しければ、List<object>を使えばいいぢゃない。
- ジェネリックデリゲート(Func, Action)を使う
これも賛否両論ではあるのですが、私は断然ジェネリックデリゲート派です。ちなみにその反対は野良デリゲート量産派でしょうか(悪意のある言い方!)。ジェネリックデリゲートを使うと良い点は、デリゲートの型違い(同じ引数・戻り値のデリゲートでも型が違うとキャストが必要)に悩まされなくてすむ、定義しなくていいので楽、そして、なにより分かりやすい。例えば、「MatchEvaluator」というだけじゃ、何なのかさっぱり分かりません。正規表現のReplaceで使われるデリゲートなのですけどね。Func<Match, string>のほうが、ずっと分かりやすい。
では良くない点は、というと、引数に変数名で意味をつけられない。例えばLINQのSelectメソッドのFunc<TSource, int, TResult>。このintはインデックスですが、そのことはドキュメントコメントからしか分かりません。その点、野良デリゲートを作れば
public delegate TR ConverterWithIndex<T, TR>(T value, int index);
という形で、明示できます。なんて素晴らしい?そう?実のところそんなでもなくて、これ、IntelliSenseからじゃあ分からないんですよね。
F12なりなんなりで型定義まで飛ばないと見えないのです、デリゲートの変数名は。その点Func<Match, string>なら引数の型、戻り値の型がIntelliSenseで見えるわけでして。C#的にはIntelliSenseで見えないと価値は9割減です。というわけで、天秤にかければ、圧倒的にFuncの大勝利。引数ずらずらでイミフになるならドキュメントコメントに書くことで補う、でもいいぢゃない。
ちなみにoutやrefのジェネリックデリゲートは存在しないので、その場合のみ自作デリゲートを立てる必要があります。それ以外、つまるところ99%ぐらいはFunc, Action, EventHandler<T>でいいと思います。LINQだってPredicateじゃなくてFunc<T, bool>だしね。
- ラムダ式を使う
ラムダ式(C# 3.0)を使わなければ何を使うのって話ですが、ラムダ式の登場により割を食った匿名メソッド(C# 2.0)は産廃です。唯一の利点は、匿名メソッドは引数を使わない場合は省略して書けます。
// 引数省略して書けるぞ!
button.Click += delegate { MessageBox.Show("hoge"); };
// ラムダ式の場合は省略できないんだ(棒)
button.Click += (_, __) => MessageBox.Show("hoge");
こんなことは実にどうでもいいので、匿名メソッドを使うのはやめましょう。もしラムダ式が先にあれば、匿名メソッドはなかったと思います。ジェネリックが最初からあれば非ジェネリックコレクションクラスがなかっただろう、ということな程度には。あとジェネリックが先にあれば野良デリゲートもなかった気がする。なので、多少どうでもいい利点があったとしても、素直に使わないのが一番。
ところでラムダ式の引数の名前ですが、どうしていますか?私は、昔は型名から取っていました、例えばintだったらi、stringだったらs。でも最近は全てxにしています。理由は、面倒くさいし適切な名前が出てこない場合もあるし修正漏れが起こったりする(ハンガリアンみたいなもんですしねえ)などなどで、メリットを感じなかったので。
ちなみに、ラムダ式で長い名前を使うのは反対です。「名前はしっかりつけなきゃダメ!」が原則論のはずなのにxってなんだよそれって感じですが、逆に、小さい範囲のものは小さいほうがいいのです。名前をつけないことで、他の名前のついているものを強調します。なんでもかんでも名前をつけていると五月蝿くて、木を森に隠す、のようになってしまいます。LINQやRxでラムダ式だらけになると、なおそうです。勿論、ラムダ式だからって全てxにするわけではありません。中でネストしてネスト内でも使われたり、式ではなく文になってスコープが長くなっている場合などは、ちゃんと名前をつけます。また、(分かりやすさのため)強く意味を持たせたい場合も名前をつけます。型名以上の意味を持たせられないのなら、あえて名前をつける必要性を感じないのでxです。
そういうわけで、多少崩すこともありますが、原則的に私の命名規則は「ただの変数 = x, 配列などコレクション = xs, 引数を使わない = アンダースコア」としています。xのかわりにアンダースコアを使う流儀もあるようですが、私は嫌いですね……。Scalaのアンダースコアとは意味が違う感じもあるし、同じ.NETファミリーならばF#が引数を使わないという意味でアンダースコアを使っているので、それに合わせたほうがいいと思っています。xだと座標のxと被る、という場合は座標のxにつける変数名をpointXだかpxだかに変えます。
Exceptionはexにしたり、イベントの場合は(sender, e)にしたりはしますけれど、このへんは慣習ですし、わざわざ崩すほうが変かな。あとLINQでのGroupingはgを使ったりしますね。
- LINQを使う(主にメソッド構文を使う、クエリ構文もたまには使う)
LINQはデータベースのためだけじゃなく、むしろ通常のコレクションへの適用(LINQ to Objects)のほうが多い。そんなにコレクション操作することなんてない、わけがない、はず。
// 配列の中のYから始まるものの名前(スペースできった最後のもの)を取り出す
new[] { "Yamada Tarou", "Yamamoto Jirou", "Suzuki Saburou" }
.Where(x => x.StartsWith("Y"))
.Select(x => x.Split(' ').Last());
上の例のような、Where(フィルタリング)+ Select(射影)は特に良く使うパターンです。Pythonなどでもリスト内包表記としてパッケージされるぐらいには。やはり、この手の処理を持っていないと、重苦しい。しかし、C# 3.0はLINQを手にしたので、お陰で軽快に飛び回れるようになりました。しかもただのフィルタ+射影だけではなく、ありとあらゆる汎用コレクション処理を、チェーンで組み合わせることで、無限のパターンを手にしました。
LINQにはメソッド構文とクエリ構文があり、どちらも同じですがメソッド構文のほうが機能豊富だし、分かりやすいです。なのでメソッド構文でメソッドチェーンハァハァしましょう。linq.js - LINQ for JavaScriptで同様の記法でJavaScriptでも使えますし!
じゃあクエリ構文に利点はないのかというと当然そんなことはなく、多重from(SelectManyに変換される)が多く出現する場合はクエリ構文のほうがいいですね。また、Joinなどもクエリ構文のほうが書きやすいし、GroupJoinと合わせた左外部結合を記述したりなど複雑化する場合はクエリ構文じゃないと手に負えません(書けなくはないんですけどねえ)
それと、LINQ to SQLなどExpression Treeをそれぞれの独自プロパイダが解釈するタイプのものは、メソッド構文の豊富な記述可能性が逆に、プロパイダの解釈不能外に飛び出しがちなので、適度に制約の効いたクエリ構文だけで書いたほうがスムーズにいく可能性があります。
また、XMLはC#ではXmlReader/Writer, XmlDocument(DOM), XDocument(LINQ to XML)がありますが、そのうちDOMのXmlDocumentは産廃です。DOMって使いづらいのよね、それにSilverlightにはないし。メモリ内にツリーを持つタイプではXDocument(XElement)でLINQでハァハァするのが主流です。ちなみにXmlReader/Writerはストリーミング型なので別枠、ただ、生で使うことはあまりないと思います。特にWriterは、XStreamingElementを使えば省メモリなストリーミングで、Writeできる、しかもずっと簡単に。なので、使うことはないかと思います。
- Taskを使う(生スレッドを使わない)
マルチスレッドプログラミングしなきゃ!Threadを使おう?デリゲートのBeginInvokeがある?それともThreadPool?BackgroundWorkerもあるぞ!古い記事はこれらの使用方法が解説されてきました。そうです、今まではそれらしかなかったので。けれど、全部ゴミ箱に投げ捨てましょう。.NET 4.0からは基本的原則的にTaskを使うべきです。豊富な待ち合わせ処理・継続・例外処理・キャンセルなどをサポートしつつ、同じスレッドを使いまわそうとするなど実行効率も配慮されています。もはや生スレッドを使う理由はないし、デリゲートのBeginInvokeなどともさよなら。BackgroundWorkerは、もう少しは出番あるかも(UIへの通知周りが今のTaskだけだと少し面倒、RxやC# 5.0のAsyncなら簡単にこなせるのですが)。
CPUを使う処理を並列に実行をしたいのなら、PLINQやParallel.ForEachなどが手軽かつ超強力です。
また、C# 5.0からはTaskの言語サポートが入り、awaitキーワードによりコード上では待機したように見せかけ同期的のように書けつつ中身は非同期で動く、といったことが可能になります。
async Task<string> GetBingHtml()
{
var wc = new WebClient();
var html = await wc.DownloadStringTaskAsync(new Uri("http://bing.com/"));
return html;
}
awaitするだけで同期的のように非同期が書けるなんて魔法のよう!
また、非同期といっても二つあります。CPUを沢山使って重たい処理と、I/O待ち(ネットワークやファイルアクセス)が重たい処理。これらへの対処は、別です。I/O待ちにスレッドを立てて対処することも可能ではありますが、あまり褒められた話ではありません。と、C#たんが非同期I/O待ちで言ってました。非同期I/Oは優れているのは分かったとしても、記述が面倒なのがネックだったのですね。しかし、C# 5.0からならばawaitが入るのでかなりサクッと書ける。非同期だって、node.jsにばかりは負けてられない!
なお、現在SilverlightやWindows Phone 7にはTaskがない(Silverlight 5にはTask入りました)ですが、将来的には間違いなく入るので、期待して待ちましょう。そして、分かりやすく書けるC# 5.0もwktkして待ちましょう。待ちきればければAsync CTPとして公開されているので、試すことが可能です。
- Rxを使う
C# 5.0はCTPだし、現実問題として非同期に困ってるんだよ!という場合は、変化球としてReactive Extensionsが使えます。詳しくはReactive Extensions(Rx)入門 - @ITで連載しているので読んでね!第二回がいつまでたっても始まらないのは何故なのでしょう、はい、私が原稿を送っていないからです、ごめんなさい……。これ書いてないで原稿書けやゴルァという感じですはい。いえ、もうすぐもう少しなので、ちょっと待ってください。
RxはTaskとは全く別の次元からやってきつつ、機能的にはある程度代替可能です。C# 5.0が来た時に共存できるのか、というと、非同期面ではTaskに譲るでしょう。けれど、非同期の生成をTaskで行なって、コントロールをawaitも使いつつRxでメインに使うとかも可能です。基本的に、コントロール周りはawaitサポートを除けばRxのほうが強力で柔軟です(代償として効率を若干犠牲にしているけれど)。ただまあ、基本的には非同期処理はTaskに絞られていくだろうと考えています。少し寂しいけど、全体としてより美しく書けるなら全然いいです、むしろ大歓迎なので早くC#5.0来ないかなあ。
ちなみに、Rxは別に非同期のためだけじゃなくて、イベントと、そしてあらゆるソースを合成するという点も見逃せないわけなので、決してAsync来たからRxさようなら、ではないです。その辺のことも連載であうあう。
- Expressionを使う(そしてEmitしない)
Expressionの簡単な基本ですが、Expressionとして宣言します。Funcとほとんど同じで、違うのは型宣言だけです。
// 同じラムダ式だけれど
Expression<Func<int, int>> expr = x => x * x;
Func<int, int> func = x => x * x;
// Expressionで宣言すると実態は以下のものになる(コンパイラが自動生成する)
var paramX = Expression.Parameter(typeof(int), "x");
var expr = Expression.Lambda<Func<int, int>>(
Expression.Multiply(paramX, paramX),
new[] { paramX });
Expressionで宣言するとコンパイラがコンパイル時に式木生成コードに変換してくれるのですね。自分で宣言しなくても、メソッドの引数の型がExpressionならば同じです。例えばQueryable.SelectのselectorはExpression型の引数なので、Queryableで連鎖を書いているということは同様に↑のようなコードが吐かれています。
Expressionの仕事は色々ありますが、概ね二つ。式がデータとして取り出せること。簡単な所ではINotifyPropertyChangedの実装なので話題沸騰したりしなかったりした、文字列ではなくプロパティを渡して、そこから引数名を取り出すことができること。
public class MyClass
{
public string MyProperty { get; set; }
}
// これ
public static string GetPropertyName<T>(Expression<Func<T>> propertyExpression)
{
return (propertyExpression.Body as MemberExpression).Member.Name;
}
static void Main(string[] args)
{
var mc = new MyClass();
var propName = GetPropertyName(() => mc.MyProperty);
}
こんな形で推し進めたのが、LINQ to SQLなど、式木の塊を解釈してSQLに変換するといった、QueryProviderですね。
そしてもう一つはILビルダー。式木はCompileすることでFuncに変換することが可能です。
// (object target, object value) => ((T)target).memberName = (U)value
static Action<object, object> CreateSetDelegate(Type type, string memberName)
{
var target = Expression.Parameter(typeof(object), "target");
var value = Expression.Parameter(typeof(object), "value");
var left =
Expression.PropertyOrField(
Expression.Convert(target, type), memberName);
var right = Expression.Convert(value, left.Type);
var lambda = Expression.Lambda<Action<object, object>>(
Expression.Assign(left, right),
target, value);
return lambda.Compile();
}
// Test
static void Main(string[] args)
{
var target = new MyClass { MyProperty = 200 };
var accessor = CreateSetDelegate(typeof(MyClass), "MyProperty");
accessor(target, 1000); // set
Console.WriteLine(target.MyProperty); // 1000
}
少なくとも、自前でILを書くよりは圧倒的に簡単に、動的コード生成を可能にしました。動的コード生成はCompileは重いものの、一度生成したデリゲートをキャッシュすることで二度目以降は超高速になります。単純なリフレクションよりはずっと速く。といったようなことをneue cc - Expression Treeのこね方・入門編 - 動的にデリゲートを生成してリフレクションを高速化で書いたので読んでね!
- dynamicを使わない(部分的に使う)
一時期、LLブームで動的言語が持て囃されましたが、今は(静的型付けの)関数型言語ブームで、静的型付けへ寄り戻しが来ています。なので、C#もdynamicあって動的だよねひゃっほーい、なんてことはなく、むしろvarで型推論です(キリッ のほうが正しくて。そんなわけで、dynamicはあまり使いません。ですが、使うとより素敵な場所も幾つかあります。それは、本質的に動的なところに対して。動的なのってどこ?というと、アプリケーションの管理範囲外。
例えばJSONはスキーマレス。DBも、自動生成しなければアプリケーションの外側で見えない。.NETはDLRがあるのでIronPythonなどスクリプト言語との連携などもそう。DynamicJsonを例にだすと、スキーマレスなJSONに対して、そのまま、JSONをJavaScriptで扱うのと同じように使えます。
var json = DynamicJson.Parse(@"{""foo"":""json"", ""bar"":100, ""nest"":{ ""foobar"":true } }");
var r1 = json.foo; // "json" - dynamic(string)
var r2 = json.bar; // 100 - dynamic(double)
var r3 = json.nest.foobar; // true - dynamic(bool)
また、動的な存在であるDBへのIDataRecordも、DbExecutorを例に出すと
var products = DbExecutor.ExecuteReaderDynamic(new SqlConnection(connStr), @"
select ProductName, QuantityPerUnit from Products
where SupplierID = @SupplierID and UnitPrice > @UnitPrice
", new { SupplierID = 1, UnitPrice = 10 })
.Select(d => new Product
{
ProductName = d.ProductName,
QuantityPerUnit = d.QuantityPerUnit
})
.ToArray();
ドットで自然に参照可能なことと、また、dynamicが自動でキャストしてくれるので明示的なキャストが不要なため、取り回しの面倒な手動DBアクセスが随分と簡単になります。(が、DBのアクセス結果を決まったクラスにマッピングするのなら、9で紹介している動的コード生成でアクセサを作ったほうが更に楽々になるのでそこまで出番はないかも。DbExecutorは両方を搭載しているので、必要に応じて選ぶことが可能です)
- 自動プロパティを使う
自動プロパティ vs パブリックフィールド。同じです。はい、同じです。じゃあパブリックフィールドでいいぢゃん?という話が度々ありますが、いえ、そんなことはありません。プロパティにするとカプセル化が云々、変更した場合に云々、などは割とどうでもいいのですが、重要な違いはちゃんとあります。
WPFとかASP.NETとかのバインディングがプロパティ大前提だったりしてパブリックフィールドだと動かないことがある。
なので、黙って自動プロパティにしておきましょう。それに関連してですが、リフレクションでの扱い易さがプロパティとフィールドでは全然違って、プロパティだと断然楽だったりします。そういう面でサードパーティのライブラリでも、プロパティだけをサポート、なものも割とあるのではかと思います。具体例はあげられませんが私は自分で作るちょろっとリフレクションもにょもにょ系の小粒なライブラリは面倒なのでプロパティだけサポート、にすることが結構あります。
あと自動プロパティの定義はコードスニペットを使って一行でやりましょう。prop->Tab->Tabです。
public int MyProperty { get; set; }
get, setで改行して5行使ったりするのはコードの可読性が落ちるので、好きじゃありません。コードスニペットで一行のものが生成されるわけなので、それに従うという意味でも、一行で書くのがベストと思います。
まとめ
C#は言語がちゃんと進化を続けてきた。進化って無用な複雑化!ではなくて、基本的には今までよくなかった、やりづらいことを改善するために進化するんですよね。だから、素直にその良さを甘受したい。そしてまた、進化するということは、歴史の都合上で廃棄物が出てきてしまうというのもまた隣合わせ。C#は巧妙に、多量の廃棄物が出現するのを避けてきていると思います。ヘジたんの手腕が光ります。しかし、やはりどうしても幾つかの廃棄物に関しては致し方ないところです。それに関しては、ノウハウとして、自分で避けるしかなくて。
この手の話だったら、.NETのクラスライブラリ設計が良いです。もしお持ちでなければ、今すぐ買いましょう!いますぐ!超おすすめです。
また、この手の本だったらEffective C# 4.0もかしら。第一版は(翻訳が出たのが既にC# 4.0の頃で1.0の内容)古くてう~ん、といった感だったのですが、第二版(C# 4.0対応)はかなり良かったです。More Effective C#のほうはLINQ前夜といった感じの内容で若干微妙なのですが、LINQ的な考えが必要な理由を抑える、という点では悪くないかもしれません。また、決定版的な内容を求めるならば、読み通す必要はなく気になるところつまみ読みで良いので、プログラミング.NET Frameworkがお薦めです。
これらに加えて、自分のやりたいことの対象フレームワークの本(ASP.NET/ASP.NET MVC/Win Forms/WPF/WCF/Silverlight/Windows Phone 7/Unity)を一冊用意すれば、導入としては準備万端で素敵ではないかしらん。まあ、今時フレームワークとかの先端の部分だと、フレームワークの進化の速度が速すぎて本だと情報の鮮度が落ちる(特に日本だと)ので、基本は本でサッと抑えて、深い部分はネットの記事を見たほうが良いのではかと思います。ソースコードが公開されていたりフレームワークの制作陣や第一人者が情報出していたりしますしね。本こそが情報の基本にして全て、という時代でもないのだなぁ、と。学習の仕方というのも、時代で変わっていくものだと思います。