C#でローカル変数からDictionaryを生成する
- 2013-01-05
どうもPHPerです。あ、すぐC#のコード出しますので帰らないで!というわけで、PHPにはcompactというローカル変数からハッシュテーブルを作るという関数があります。割と多用します。その逆のextractという関数もありますが、そちらはカオスなのでスルー。
$name = "hogehoge";
$age = 35;
// {"name":"hogehoge", "age":35}
$dict = compact("name", "age");
へー。いいかもね。これをC#でやるには?もったいぶってもshoganaiので先に答えを出しますが、匿名型を使えばよいです。
var name = "hogehoge";
var age = 35;
// Dictionary<string, object> : {"name":"hogehoge", "age":35}
var dict = Compact(new { name, age });
はい。別にPHPと見比べても面倒くさいことは全然ないです。C#はLLですから(嘘)
匿名型は「メンバー名を指定しなかった場合、コンパイラによって、初期化に使用するプロパティと同じ名前が付けられます。」ので、それを利用すればローカル変数の名前をキャプチャできる、という至極単純な仕組み。
Reflection vs FastMember
Compactメソッドの中身ですが、リフレクションでプロパティなめてDictionaryに吐き出しているだけです。LINQを使えば瞬殺。
static Dictionary<string, object> Compact(object obj)
{
return obj.GetType().GetProperties()
.Where(x => x.CanRead)
.ToDictionary(pi => pi.Name, pi => pi.GetValue(obj));
}
CanReadは、一応、匿名型以外を流し込む時のことも考慮しましょうか、的に。
さて、リフレクションを使うと実行速度がー気になってーカジュアルにー使いたくないー、のが人情というものです。個人的にはそこまで遅くもないので、そう気にしなければカジュアルに使ってもいいと思ってたりしますが、まあ気になるならShoganaiし、気にするのはいいことです。
そこで取り出すはFastMember。超高速シリアライザで有名なprotobuf-netの作者が作った、シンプルなプロパティアクセス高速化ライブラリです。
これを使って書くと
static Dictionary<string, object> Compact(object obj)
{
var type = FastMember.TypeAccessor.Create(obj.GetType());
return type.GetMembers().ToDictionary(x => x.Name, x => type[obj, x.Name]);
}
というように、書き方的にはそんなに違いはないですが、生成速度は数倍上昇します。TypeAccessor.Createして、GetMembersでプロパティ情報の列挙(TypeとNameがあるだけ)、PropertyInfoのGetValue的なのはインデクサを使います。FastMemberにはTypeAccessorの他にObjectAccessorがありますが、使い方は似たような感じなので略(インデクサの第一引数に対象オブジェクトを渡す必要がなくなる)。
FastMemberの仕組みですが、初回実行時にはリフレクションでプロパティ舐めています。別に魔法が存在するわけではないので、プロパティ名を取りたければ、リフレクション以外の選択肢はありません。そして取得したデータを基にしてILの動的生成を行いキャッシュし、以降のアクセス時はキャッシュから取得したアクセサ経由となるため、素のリフレクションよりも高速となっています。
よって、初回実行時に限れば、実行時間はむしろかなり遅くなります(IL生成は軽い処理ではない)。単純な平均で考えれば、1万アクセスぐらいないとペイしません(要素数による、多ければ多いほどFastMemberのほうが有利です)。という程度には、リフレクションもそんなに遅くはないです。ただまあ、初回に目をつむって以降の実行速度重視のほうがユーザー体験での満足度は高いケースがほとんどとは思われますので、個人的にはFastMember使って済ませるほうがいいな、とは思います。気分的にもスッキリしますしね。
ちなみに.NETでリフレクションにはTypeDescriptorという手段も標準で用意されていますが、アレはクソがつくほど遅いので、アレだけはやめておきましょう。少なくとも素のリフレクションを避けてあっちを使う理由がない。
名前大事
Compactという名前はPHP臭が激しいしC#的にはイミフなので、ちゃんとした名前をつけたほうがいいでしょう、ToDictionaryとか、ね。
匿名型 as Dictionary
Compact、という例を出すから何だか新しい感じがしなくもない誤魔化しでして、実のところ、ようするに、ただの匿名型→Dictionaryです。ASP.NET MVCではそこら中に見かけるアレです。ソレです。コレです。
その辺のアレコレはややニッチな Anonymous Types の使い方をまとめてみる (C# 3.0) - NyaRuRuが地球にいたころにまとまっているので見ていただくとして、以上終了。
実際問題、Dictionary<string, object>を要求するシチュエーションというのは少なくありません。パラメータ渡すところなんて、そうですよね。一々Dictionaryを使うのは、カッタルイってものです。なので、別にASP.NET MVCに限らず、↑のようなメソッドを作って、objectも受け入れられるようにしてあげるってのは、現代のC#的にはアリだと私は考えています。
// Dictionaryの初期化は割と面倒くさい
var hoge = ToaruMethod(new Dictionary<string, object>
{
{"screen_name", "hogehoge"},
{"count", 10},
{"since_id", 12345}
});
// 書きやすい!素敵!抱いて!
var hoge = ToaruMethod(new
{
screen_name = "hogehoge",
count = 10,
since_id = 12345
});
んね。
そうなるとメソッドの引数にobjectというものが出てしまって、安全性がショボーンになってしまいますので、やたらめったら使うのもまたアレですけれど。
匿名型がIAnonymousTypeとか、何らかのマーカーついてたらなあ、なんて思わなくもなかったりもしなかったりしましたが、こういう用途で使う時って、普通のクラスからも変換したかったりするので、匿名型に限定したほうが不便なんですね。幾ばくかの安全性は増しますが。ともあれともあれ、普通のクラスと匿名型に違いなんてない、と考えると、区別できないことは自然だから別にいいかなあ、なんて、ね、思ってます。where T : classと引数に制限つけるぐらいが丁度良いんではないでしょうか。
まとめ
PHPの良いところってどこなのか非常に悩ましい。その辺のほげもげに関してはいつか特に言いたいことはなくもないけどとくにない(去年の年末に勉強会というか技術交流会というかで、PHPの会社に行ってPHP vs C#なプレゼンはしてきましたが)。
というわけで、C#はLightweightだという話でした。ん?