Micro-ORMとテーブルのクラス定義自動生成について
- 2013-06-30
謎社のデータアクセスはMicro-ORMでやっています。生SQL書いて、シンプルなPOCOにマッピングするだけの。ですが、そこで困るのはPOCOの作成。データベースの写しなだけのクラスですが、手で作るには、ひじょーに面倒。Entity Frameworkならドラッグアンドドロップで!DataSetですらホイホイと作れるのに、100%手作業とか嫌だよー、200テーブルを延々とクラス作るだけの刺身たんぽぽなんてしてたら死んじゃうよー。
というわけで、Micro-ORM使うなら避けては通れない定義。EFのクラス定義だけ流用しちゃうとか色々と逃げ道も考えられなくもないですが、もしくは数によっては手動で頑張ってしまうのも手ですが、ここは自動生成しましょうの会。
GetSchema
普通にSQLのクエリを書いてデータベースの情報を取ってくることも可能ですが、各データベースでそれぞれバラバラだったりするので、ここはADO.NETで用意されているGetSchemaを使いましょう。情報取得の部分が抽象化されていて、型無しDataTableとして受け取ることが可能です。
using (var conn = new MySqlConnection("接続文字列。MySQLでもなんでもいいよ。"))
{
conn.Open();
var schema = conn.GetSchema();
}
さて、schemaとやらをデバッガで見てみるとですね……
うおおお、これがDataTableか!な、なんだ、このデバッガビリティの低さは……。これはヤヴァい。マジキチ。RowsのKeyを辿るのも苦労するうえに、Valueが一覧で見れない。頑張ってもKeyだけ。なんだこりゃ。データの取得もLINQ to DataSet(笑)によって、普通に実に扱いづらい。話にならない。 クソが。というわけで、今時ならば型無しDataTableはdynamicで扱ったほうが楽です。ExpandoObjectに変換しましょう。
public static class DataTableExtensions
{
/// <summary>DataTableの各RowをExpandoObjectに変換します。</summary>
public static IEnumerable<dynamic> AsDynamic(this DataTable table)
{
return table.AsEnumerable().Select(x =>
{
IDictionary<string, object> dict = new ExpandoObject();
foreach (DataColumn column in x.Table.Columns)
{
var value = x[column];
if (value is System.DBNull) value = null;
dict.Add(column.ColumnName, value);
}
return (dynamic)dict;
});
}
}
こんなものを用意すると、 var schema = conn.GetSchema().AsDynamic() とするだけで
うおおおおおおお、超捗る!ちゃんと動的ビューでKeyとValueが見える!ExpandoObjectありがとう。DataTableは死ね。また、DBNullをフツーのnullに変換したりなどもしているので、データを触るのもかなり捗るといったところもあります。item.Field<string>("CollectionName")と書くよりも、item.CollectionNameって書きたいですから。
では、気を取り直してこれで解析していきましょう。まずは、件のCollectionNameを見てみますか。
// MetaDataCollections
// DataSourceInformation
// DataTypes
// Restrictions
// ReservedWords
// Databases
// Tables
// Columns
// Users
// Foreign Keys
// IndexColumns
// Indexes
// Foreign Key Columns
// UDF
// Views
// ViewColumns
// Procedure Parameters
// Procedures
// Triggers
foreach (var item in conn.GetSchema().AsDynamic())
{
Console.WriteLine(item.CollectionName);
}
MySQLでは以上のデータが取れるようです。この辺は使ってるデータベースによってかなり変わるので、適宜調べながら合わせてみてくださいな。というわけで、それっぽそうなTablesを見てみます。var tables = conn.GetSchema("Tables").AsDynamic(); とすれば
テーブル一覧が取れるようです。で、しかし、今回必要なのはTablesではありません。TablesはほんとーにTableのデータだけなので。今回必要なのは、Columnsです。
var columns = conn.GetSchema("Columns").AsDynamic()
.GroupBy(x => x.TABLE_NAME) // 全てのカラムが平らに列挙されてくるのでテーブル名でグルーピング
.Select(g => new
{
ClassName = g.Key, // クラス名はテーブル名(= グルーピングのキー)
Properties = g
.OrderBy(x => x.ORDINAL_POSITION) // どんな順序で来るか不明なので、カラム定義順にきちんと並び替え
.Select(x => new
{
Name = x.COLUMN_NAME,
Type = x.DATA_TYPE
})
.ToArray()
});
良い感じに作れてきました。さて、クラスを自動生成するのに必要なのは「クラス名」「プロパティ名」「プロパティの型」です。DATA_TYPEだとDBの生の型名、bigintとかvarcharとかC#のデータ型じゃないよー。なので、このまんまじゃダメです。
というわけで、マッピングを用意してあげます。といっても手動でやる必要はなくて、これはGetSchema("DataTypes")で取れます。
// 型名を決めるのに必要なのは
// TypeName(MySQLの型名), DataType(.NETの型名), IsUnsigned
var typeDictionary = conn.GetSchema("DataTypes").AsDynamic()
.ToDictionary(x =>
Tuple.Create((string)x.TypeName.ToLower(), (bool?)x.IsUnsigned ?? false),
x => (Type)Type.GetType(x.DataType));
// MySQLのtinyintはboolとして使われることが多いので、そちらにマッピングしちゃう(不要ならしなくていいです)
typeDictionary[Tuple.Create("tinyint", true)] = typeof(bool);
typeDictionary[Tuple.Create("tinyint", false)] = typeof(bool);
// nullableの場合を考慮する必要があるのでTypeDictionaryは生では使わない
Func<string, bool, bool, string> getTypeName = (dataType, isUnsigned, isNullable) =>
{
var type = typeDictionary[Tuple.Create(dataType.ToLower(), isUnsigned)];
return (isNullable && type.IsValueType)
? type.Name + "?" // 値型かつnull許可の時
: type.Name;
};
TypeName(MySQLの型名), DataType(.NETの型名), IsUnsigned。それにNullableへの対応を組み合わせれば、マッピングできると考えられます。GetSchemaからの情報だけだとNullable対応が苦しくなるので、外にメソッド立てています。
さて、この型定義辞書を使って変換すると、以下のようになります。
var columns = conn.GetSchema("Columns").AsDynamic()
.GroupBy(x => x.TABLE_NAME) // 全てのカラムが平らに列挙されてくるのでテーブル名でグルーピング
.Select(g => new
{
ClassName = g.Key, // クラス名はテーブル名(= グルーピングのキー)
Properties = g
.OrderBy(x => x.ORDINAL_POSITION) // どんな順序で来るか不明なので、カラム定義順にきちんと並び替え
.Select(x => new
{
Name = x.COLUMN_NAME,
// unsignedの判定はCOLUMN_TYPEから、nullableの判定はYES/NOで行われる
Type = getTypeName(x.DATA_TYPE, x.COLUMN_TYPE.Contains("unsigned"), x.IS_NULLABLE == "YES")
})
.ToArray()
});
これで完璧!さて、定義の抽出はできたので、次はテンプレート作りにいきましょうか。
T4
(テキストとしての)C#コード生成は、C#コード上で文字列を切った貼ったする、わけは勿論ありません。この手の作業するときはテンプレートエンジンを使うのが良いでしょう。最近だとRazorを使ったRazorEngineなどもあるのですが、RazorはあくまでHTML/XMLを出力するのに向いている構文で、C#コードを出力するような用途で使うのは、あまり向いていません。ここは素直にVisual Studio標準のT4 Templateを使うのが良いでしょう。あえてStringTemplate.NETとか、他のを選ぶ理由は、ないかなぁ。T4でいいですよ。
T4にはVisual Studioと連携して保存時にテンプレートが当てはまったテキストを出力するタイプと、ふつーのクラスとして、実行時に任意の変数をあてて、テキストを生成するもののニタイプが選べます。このブログでも何度か紹介してきたのは、全て前者でしたが、今回は後者のパターンを使います。
昔の名前は「前処理されたテキストテンプレート」でした。VS2012から名前変わって「ランタイムテキストテンプレート」になったようです。見つからなかったら検索ウィンドウにT4と入れると良いですよ。では、まず、TableGeneratorTemplate.ttとしてテンプレートを定義します。
<#@ template language="C#" #>
<#@ assembly name="System.Core" #>
<#@ import namespace="System.Linq" #>
<#@ import namespace="System.Text" #>
<#@ import namespace="System.Collections.Generic" #>
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
public class <#= ClassName #>
{
<# foreach(var x in Properties) {#>
public <#= x.Type #> <#= x.Name #> { get; set; }
<# } #>
public override string ToString()
{
return ""
<# foreach(var x in Properties) {#>
+ "<#= x.Name #> : " + <#= x.Name #> + "|"
<# } #>
;
}
}
テンプレートの記法としては、#=で囲むだけなので、まぁそう難しいものでもないです、読みづらさはかなりありますが。さて、このままだとClassNameとかPropertiesとかいうのは未定義でコンパイルもできないので、パーシャルクラスを作ります。クラス名はテンプレートと同名で。
public partial class TableGeneratorTemplate
{
public string ClassName { get; set; }
public IEnumerable<dynamic> Properties { get; set; }
public TableGeneratorTemplate(string className, IEnumerable<dynamic> properties)
{
this.ClassName = className;
this.Properties = properties;
}
}
これで、パラメータを渡せるようになりました。コンパイルエラーも出ません。というわけで実際に出力しましょう。テンプレートをnewして、TransformTextを呼ぶだけです。
// あ、ちなみにここまでのはConsoleApplicationでの話でした、はい。
// テーブル名.csにテンプレートを当てて全部出力
foreach (var item in columns)
{
var tt = new TableGeneratorTemplate(item.ClassName, item.Properties);
var text = tt.TransformText();
File.WriteAllText(item.ClassName + ".cs", text, Encoding.UTF8);
}
これで、以下の様なファイルがテーブル数だけ出力されます。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
public class aiueo_test_table
{
public Int64 id { get; set; }
public String name { get; set; }
public Int32 age { get; set; }
public DateTime? created { get; set; }
public DateTime? modified { get; set; }
public override string ToString()
{
return ""
+ "id : " + id + "|"
+ "name : " + name + "|"
+ "age : " + age + "|"
+ "created : " + created + "|"
+ "modified : " + modified + "|"
;
}
}
やったね!刺身たんぽぽさようなら!ちなみにただのプロパティの塊というだけじゃなく、ToStringも生成しておいてやると、実アプリでのデバッグの時に割と便利ですねー。
応用
GetSchemaで得られる情報は他にも沢山ありますので、Diffを取るプログラムを書けたり、あと、Index, IndexColumnsでインデックスの情報が取れます。インデックスで貼られているものはselectクエリーをほぼほぼ発行するはず、とみなせるので、selectクエリを発行するメソッドを自動生成しちゃったりとかは、実際に謎社ではしています。
つまり作業手順としては、どちらかというデータベースファーストになります。結局、コードとデータベースは違うので、データベース優先の定義・作業のほうが、どこまでいっても自然かな、と。(EF)コードファーストは私は幻想だと思っています。別に、SQL Server Management StudioなりHeidiSQLなどのツールでぽちぽち定義作るのは、そう面倒なわけでもない。そこからC#側のクラス定義も自動で生成できるのなら、むしろ、無理のあるコードでのデータベース表現をして回るよりも、結局、楽じゃない?DB定義をC#側から発行されて、どーのこーとか、なんてのに気を使わなくてもいいので、ずっと楽ちんだと思うんだ。原始的で全然スマートじゃないようで、実利はこっちにある。
まとめ
コードの詳細はMySQLなので、SQL Serverとかじゃ100%そのままは動かないかもですが、その辺は適宜調整してください、きっと似たようなのはあるはずなので。
あと、GetSchemaのDataTableで何が取れるのか、どんなフィールドがあるのかって、特にMySQLだとドキュメントゼロなのですが、そこでAsDynamicは本当に死ぬほど役に立ちました。Visual Studio上でのデバッガビリティを高めるの超大事。その辺がクソなのがDataTableの嫌なところですねえ。今時DataSetを使いまくってるレガシー会社とかあると悲しいですねえ。
ともあれ、dynamicはかなりデバッガビリティ高いので、活用してあげると良いです。dynamic、最近だと忘れ去られているC#の機能らしいので(笑)まあ、メインには使いませんけれど、あるとやっぱ便利なので、あって良かったなって、思いますよん。
AsyncOAuth ver.0.7.0 - HttpClient正式版対応とバグ修正
- 2013-06-24
HttpClientがRTMを迎えた、と思ったら、次バージョンのベータが出た、と、展開早くて追いつけないよ~な感じですが、とりあえず正式版のほうを要求する形で、AsyncOAuthも今回からBetaじゃなくなりました。
- AsyncOAuth - GitHub
- PM> Install-Package AsyncOAuth
AsyncOAuthについてはAsyncOAuth - C#用の全プラットフォーム対応の非同期OAuthライブラリを、HttpClientについてはHttpClient詳解、或いはAsyncOAuthのアップデートについてを参照ください。
今回のアップデートなのですが、PCL版HttpClientのRTM対応という他に、バグ修正が二点ほどあります。バグ的には、結構痛いところですね……。
OrderByと文字列について
バグ修正その一として、OAuthの認証シグネチャを作るのにパラメータを並び替える必用があるのですが、その並び順が特定条件の時に狂っていました。狂っている結果、正しく認証できないので、実行が必ず失敗します。特定条件というのは、パラメータ名が大文字始まりと小文字始まりが混在するときです。例えばhogeとHugaとか。本当はHuga-hogeにならなければならないのに、hoge-Hugaの順序になってしまっていたのでした。
なぜそうなったか、というと、OrderByをデフォルトで使っていたからです。
// charの配列をOrderByで並び替えると、ASCIIコード順 = 65:'A', 97:'a'
foreach (var item in new[] { 'a', 'A' }.OrderBy(x => x))
{
Console.WriteLine((int)item + ":" + (char)item);
}
// stringの配列をOrderByで並び替えると、良い感じ順 = "a", "A"
foreach (var item in new[] { "a", "A" }.OrderBy(x => x))
{
Console.WriteLine(item);
}
良い感じ順!というのはなにかというと、StringComparison/Comparer.CurrentCultureの順番です。ほぅ……。ありがたいような迷惑のような。で、今回はASCIIコードにきっちり従って欲しいので、
// 実際のコード。
// ところでrealmのところは!x.Key.Equals("realm", StringComparison.OrdinalIgnoreCase) って書くべき、次のバージョンで直します
var stringParameter = parameters
.Where(x => x.Key.ToLower() != "realm")
.Concat(queryParams)
.OrderBy(p => p.Key, StringComparer.Ordinal)
.ThenBy(p => p.Value, StringComparer.Ordinal)
.Select(p => p.Key.UrlEncode() + "=" + p.Value.UrlEncode())
.ToString("&");
といったように、StringComparer.OrdinalをOrderBy/ThenByに渡してあげることで解決しました。メデタシメデタシ。文字列の比較とか、言われてみれば基本中の基本っっっ!なのですけれど、完全に失念してました。テストでも、パラメータ小文字ばっかだったりして問題が中々表面化しないんですね、言い訳ですけれど……。しかし、幾つかのライブラリ見てみると、ほとんどがこれの対応できてなかったので私だけじゃないもん!みたいな、うう、情けないのでやめておきましょう。OAuthBase.csもissueには上がってましたがマージされてないんで、同様の問題抱えてるのですねえ。さすがにDotNetOpenAuthはきっちりできていました。
UriクラスとQueryとエスケープについて
バグ修正その2。GETにパラメータをくっつける、つまりクエリストリングとして並べた時に、日本語などURLエンコードが必要な物が混ざっている時に必ず認証に失敗しました。エンコード周りということで、お察しの通り二重エンコードが原因です。
これに関しては、私のUriクラスへの認識が甘々だったのがマズかったです。ぶっちけ、ただのstringを包んだだけの面倒臭い代物とか思ってたりとかしたりとか……。いや、さすがにそこまでではないんですが、まぁしかし甘々でした。こっちは↑のに比べても本当に初歩ミスすぎて穴掘って埋まりたいですぅー。相当恥ずかしい。
// クエリストリングつけたものを投げるとして
var uri = new Uri("http://google.co.jp/serach?q=つくば");
// QueryはURLエンコードされてる => ?q=%E3%81%A4%E3%81%8F%E3%81%B0
Console.WriteLine(uri.Query);
// URLエンコードされてるのを渡したとして
uri = new Uri("http://google.co.jp/serach?q=%E3%81%A4%E3%81%8F%E3%81%B0");
// ToString結果はURLデコードされたものがでてくる => http://google.co.jp/serach?q=つくば
Console.WriteLine(uri.ToString());
というわけで、Queryは常にURLエンコードされるし、ToStringは常にURLデコードされてる。この辺の認識がフワフワッとしてると、エンコードやデコード結果が非常にアレになっちゃうんですね……。
// 元データが欲しい時はOriginalString
Console.WriteLine(uri.OriginalString);
// エンコードされないで取るにはGetComponentsで指定してあげる
// ちなみにQueryは (UriComponents.Query | UriComponents.KeepDelimiter, UriFormat.Escaped) と等しい
var unescaped = uri.GetComponents(UriComponents.Query, UriFormat.Unescaped);
Console.WriteLine(unescaped); // q=つくば
GetComponents大事大事。
Next
今回からBetaを取ったということで、1.0に上げようかなあ、とか思ったんですが、↑のように手痛いバグが残っていたことが発覚したりなので、まだもう少し様子見といった感。さすがにもう大丈夫なはず!といくら思っても見つかっていくわけで、枯れてるって本当に大事ですねえ。AsyncOAuthも枯れたライブラリになるよう、頑張ります。今回のバグとかもユーザーが伝えてくれるお陰なので足を向けて寝られません。
私自身が使ってるのか?というと、答えは、使っています!こないだに.NET最先端技術によるハイパフォーマンスウェブアプリケーションというセッションを行ない、そこで言及したように、現在、某ソーシャルゲームをC#で再構築中です。で、GREEにせよMobageにせよ、ソーシャルゲームってOpenSocialの仕組みに乗っかっているのですが、その認証がOAuthなのです。というわけで、めっちゃくちゃヘヴィーに使っています。(認証の形態がちょっと違うので幾つかメソッドが足されているのと、Shift-JIS対応とかのためのカスタムバージョンだったりはしますが…… その辺はAsyncOAuth本体にも載せるか検討中)。実戦投下まであとちょっとってところですが、それできっちり動ききれば、十分に枯れた、といえるのではないかと思っています。その時までは0.xで。でも現状でもかなり大丈夫だとは思いますので、是非是非使ってください。
The History of LINQ
- 2013-06-23
という内容で、つくばC#勉強会で話してきました。
初心者向け勉強会(?)ということで、歴史ですかにぇー。詳細よりは、全体的にあっさり、みたいな。多分ね!Cωとかもう10年前ですが、これだけ時間のたった今だからこそ、あらためてちょっと見てみると面白いね、みたいな。
第一回つくばC#勉強会 #tkbcsmt 当日の様子ということで実にカオス、よかったね!面白かったです~。
TypeScript 0.9のジェネリクス対応でlinq.jsの型定義作って苦労した話
- 2013-06-20
久しぶりのTypeScript。Announcing TypeScript 0.9というわけで、ついに待望のジェネリクスが搭載されました。やったね!というわけで、ジェネリクス対応のlinq.jsの型定義を早速作りました、と。ver 3.0.4-Beta5です。まだまだBeta、すみませんすみません、色々忙しくて……。NuGetからもしくはサイトからのダウンロードで公開してます。
とりあえず例として使ってみた感じ。
// booleanしか受け入れないwhereにnumberを突っ込んだらちゃんと怒ってくれるよ!
// Call signatures of types '(x: any) => number' and '(element: number, index: number) => boolean' are incompatible.
var seq = Enumerable.from([1, 10, 100, 100]).where(x => x * 2);
やったー。これだよこれ!LINQはジェネリクスがあって本当の本当の真価を発揮するんです!!!
面白げな型定義
C#とちょっと違うところとしては、ジェネリクスの型変数のところにもオブジェクトを突っ込めるんですね。だから
// from(obj)の型定義はこんな感じ。
from(obj: any): IEnumerable<{ key: string; value: any }>;
// ちなみにfromのオーバーロードはいっぱいある
// linq.js自体はJScriptのIEnumerableとWinMDのIIterable<T>にも対応してるのですがTSで表現できないので、そこは未定義……。。。
from(): IEnumerable<any>; // empty
from<T>(obj: IEnumerable<T>): IEnumerable<T>;
from(obj: number): IEnumerable<number>;
from(obj: boolean): IEnumerable<boolean>;
from(obj: string): IEnumerable<string>;
from<T>(obj: T[]): IEnumerable<T>;
from<T>(obj: { length: number;[x: number]: T; }): IEnumerable<T>;
このkey,valueはどこから出てきたんだよって感じでわかりづらかったので、こうして見えてくれると嬉しい度高い。
型消去
場合によってはIEnumerable<any>となるため、Tの型をつけてあげたかったり、もしくは強引に変換したかったり(例えば↑のIIterable<T>は{key,value}に解釈されてしまうので、明示的に変換してあげる必要がある)する場合のために、castメソッドを用意しました。
// any[]型で渡ってきたりする場合に
var seq: any[] = [1, 2, 3, 4, 5];
var result = Enumerable.from(seq)
.cast<number>() // numberに変換
.select(x => x * x) // x:number
.toArray(); // result:number[]
さて、ところで、TypeScriptのジェネリクスは型消去(Type Erasure)式です。コンパイル結果のJSは以下のような感じで
var result = Enumerable.from(seq)
.cast()
.select(function (x) { return x * x; })
.toArray();
一切の型は消えています。なので、TypeScriptでTの型を取って実行時に扱ったりはできません。型が欲しければ型を渡せ方式。
linq.jsにはofTypeという、型でフィルタリングするメソッドがあるのですが
// 混在した配列から、型でフィルタして取り出す
var mixed: any[] = [1, "hoge", 100, true, "nano"];
// このままじゃ型が分からないのでresultはany[]
var result1 = Enumerable.from(mixed)
.ofType(Number) // 数値型のみでフィルタ、つまり[1, 100]
.toArray();
// ofTypeの後にcastするか、もしくはofTypeで型指定するか
var result2 = Enumerable.from(mixed)
.ofType<number>(Number)
.toArray();
というように、<number>(Number)と連続するのが非常に不恰好……。まあ、この辺はそういうものなのでしょうがないと諦めましょう。
地雷ふんだり
さて、そんな素敵なTypeScript 0.9なのですが、残念なお知らせ。現在の、というか、この0.9ですが、完成度はものすごーーーーーーーーく、低いです。はい、超低いです。Visual Studioと組み合わせて使う場合、半端無く動作も補完も遅いです。正直、ベータどころかアルファぐらいのクオリティで、何故に堂々と出してきたのか理解に苦しむ。遅いだけならまだしも、ジェネリクスの解釈が非常に怪しく、地雷を踏むと補完やエラー通知が消え去ります。いや、消え去るだけならまだよくて、Visual Studioと裏で動くTypeScript Compilerが大暴走をはじめてCPUが100%に張り付いたりします。もうヤヴァい。タスクマネージャー開きっぱにして警戒しながらじゃないと書けないです。linq.jsの型定義書くの超絶苦労した……。
interface IEnumerable<T> {
// 戻り値の型をIEnumerable<IGrouping<TKey, T>>にしたいのですが、
// interface側で定義しているTをネストした型変数に使うとVSが大暴走して死ぬ
// ので、今回のバージョンではanyにしてます
groupBy<TKey>(keySelector: (element: T) => TKey): IEnumerable<IGrouping<TKey, any>>;
}
具体的に踏んだのは↑ですねえ。最初原因が分かってなくてかなり時間食われちゃいました。んもー。最小セットで試してる限りでは、コンパイルエラーにはなるんですが(A generic type may not reference itself with a wrapped form of its own type parameters.)暴走はしない、んですがlinq.d.tsで弄ってると死ぬ。おうふ。もう定義の仕方が悪いのかコンパイラがアレなのか判断つかないのでしんどい。
そんなわけで、0.8の完成度からだいぶ退化して、実用性はゼロになってしまいました。私の環境だけ、じゃあないよねえ…‥?とりあえず0.9.1を待ちましょう。どうやらコンパイラをまるっと書き換えたそうですしねー、初物だからshoganaiと思うことにして。しかし泣きたい。
linq.d.ts
とりあえず、こんな感じになってます。
// Type Definition for linq.js, ver 3.0.4-Beta5
declare module linqjs {
interface IEnumerator<T> {
current(): T;
moveNext(): boolean;
dispose(): void;
}
interface Enumerable {
Utils: {
createLambda(expression: any): (...params: any[]) => any;
createEnumerable<T>(getEnumerator: () => IEnumerator<T>): IEnumerable<T>;
createEnumerator<T>(initialize: () => void , tryGetNext: () => boolean, dispose: () => void ): IEnumerator<T>;
extendTo(type: any): void;
};
choice<T>(...params: T[]): IEnumerable<T>;
cycle<T>(...params: T[]): IEnumerable<T>;
empty<T>(): IEnumerable<T>;
// from<T>, obj as JScript's IEnumerable or WinMD IIterable<T> is IEnumerable<T> but it can't define.
from(): IEnumerable<any>; // empty
from<T>(obj: IEnumerable<T>): IEnumerable<T>;
from(obj: number): IEnumerable<number>;
from(obj: boolean): IEnumerable<boolean>;
from(obj: string): IEnumerable<string>;
from<T>(obj: T[]): IEnumerable<T>;
from<T>(obj: { length: number;[x: number]: T; }): IEnumerable<T>;
from(obj: any): IEnumerable<{ key: string; value: any }>;
make<T>(element: T): IEnumerable<T>;
matches<T>(input: string, pattern: RegExp): IEnumerable<T>;
matches<T>(input: string, pattern: string, flags?: string): IEnumerable<T>;
range(start: number, count: number, step?: number): IEnumerable<number>;
rangeDown(start: number, count: number, step?: number): IEnumerable<number>;
rangeTo(start: number, to: number, step?: number): IEnumerable<number>;
repeat<T>(element: T, count?: number): IEnumerable<T>;
repeatWithFinalize<T>(initializer: () => T, finalizer: (element) => void ): IEnumerable<T>;
generate<T>(func: () => T, count?: number): IEnumerable<T>;
toInfinity(start?: number, step?: number): IEnumerable<number>;
toNegativeInfinity(start?: number, step?: number): IEnumerable<number>;
unfold<T>(seed: T, func: (value: T) => T): IEnumerable<T>;
defer<T>(enumerableFactory: () => IEnumerable<T>): IEnumerable<T>;
}
interface IEnumerable<T> {
constructor(getEnumerator: () => IEnumerator<T>);
getEnumerator(): IEnumerator<T>;
// Extension Methods
traverseBreadthFirst(func: (element: T) => IEnumerable<T>): IEnumerable<T>;
traverseBreadthFirst<TResult>(func: (element: T) => IEnumerable<T>, resultSelector: (element: T, nestLevel: number) => TResult): IEnumerable<TResult>;
traverseDepthFirst<TResult>(func: (element: T) => Enumerable): IEnumerable<T>;
traverseDepthFirst<TResult>(func: (element: T) => Enumerable, resultSelector?: (element: T, nestLevel: number) => TResult): IEnumerable<TResult>;
flatten(): IEnumerable<any>;
pairwise<TResult>(selector: (prev: T, current: T) => TResult): IEnumerable<TResult>;
scan(func: (prev: T, current: T) => T): IEnumerable<T>;
scan<TAccumulate>(seed: TAccumulate, func: (prev: TAccumulate, current: T) => TAccumulate): IEnumerable<TAccumulate>;
select<TResult>(selector: (element: T, index: number) => TResult): IEnumerable<TResult>;
selectMany<TOther>(collectionSelector: (element: T, index: number) => IEnumerable<TOther>): IEnumerable<TOther>;
selectMany<TCollection, TResult>(collectionSelector: (element: T, index: number) => IEnumerable<TCollection>, resultSelector: (outer: T, inner: TCollection) => TResult): IEnumerable<TResult>;
selectMany<TOther>(collectionSelector: (element: T, index: number) => TOther[]): IEnumerable<TOther>;
selectMany<TCollection, TResult>(collectionSelector: (element: T, index: number) => TCollection[], resultSelector: (outer: T, inner: TCollection) => TResult): IEnumerable<TResult>;
selectMany<TOther>(collectionSelector: (element: T, index: number) => { length: number;[x: number]: TOther; }): IEnumerable<TOther>;
selectMany<TCollection, TResult>(collectionSelector: (element: T, index: number) => { length: number;[x: number]: TCollection; }, resultSelector: (outer: T, inner: TCollection) => TResult): IEnumerable<TResult>;
where(predicate: (element: T, index: number) => boolean): IEnumerable<T>;
choose(selector: (element: T, index: number) => T): IEnumerable<T>;
ofType<TResult>(type: any): IEnumerable<TResult>;
zip<TResult>(second: IEnumerable<T>, resultSelector: (first: T, second: T, index: number) => TResult): IEnumerable<TResult>;
zip<TResult>(second: { length: number;[x: number]: T; }, resultSelector: (first: T, second: T, index: number) => TResult): IEnumerable<TResult>;
zip<TResult>(second: T[], resultSelector: (first: T, second: T, index: number) => TResult): IEnumerable<TResult>;
zip<TResult>(...params: any[]): IEnumerable<TResult>; // last one is selector
merge<TResult>(...params: IEnumerable<T>[]): IEnumerable<T>;
merge<TResult>(...params: { length: number;[x: number]: T; }[]): IEnumerable<T>;
merge<TResult>(...params: T[][]): IEnumerable<T>;
join<TInner, TKey, TResult>(inner: IEnumerable<TInner>, outerKeySelector: (outer: T) => TKey, innerKeySelector: (inner: TInner) => TKey, resultSelector: (outer: T, inner: TKey) => TResult, compareSelector?: (obj: T) => TKey): IEnumerable<TResult>;
join<TInner, TKey, TResult>(inner: { length: number;[x: number]: TInner; }, outerKeySelector: (outer: T) => TKey, innerKeySelector: (inner: TInner) => TKey, resultSelector: (outer: T, inner: TKey) => TResult, compareSelector?: (obj: T) => TKey): IEnumerable<TResult>;
join<TInner, TKey, TResult>(inner: TInner[], outerKeySelector: (outer: T) => TKey, innerKeySelector: (inner: TInner) => TKey, resultSelector: (outer: T, inner: TKey) => TResult, compareSelector?: (obj: T) => TKey): IEnumerable<TResult>;
groupJoin<TInner, TKey, TResult>(inner: IEnumerable<TInner>, outerKeySelector: (outer: T) => TKey, innerKeySelector: (inner: TInner) => TKey, resultSelector: (outer: T, inner: TKey) => TResult, compareSelector?: (obj: T) => TKey): IEnumerable<TResult>;
groupJoin<TInner, TKey, TResult>(inner: { length: number;[x: number]: TInner; }, outerKeySelector: (outer: T) => TKey, innerKeySelector: (inner: TInner) => TKey, resultSelector: (outer: T, inner: TKey) => TResult, compareSelector?: (obj: T) => TKey): IEnumerable<TResult>;
groupJoin<TInner, TKey, TResult>(inner: TInner[], outerKeySelector: (outer: T) => TKey, innerKeySelector: (inner: TInner) => TKey, resultSelector: (outer: T, inner: TKey) => TResult, compareSelector?: (obj: T) => TKey): IEnumerable<TResult>;
all(predicate: (element: T) => boolean): boolean;
any(predicate?: (element: T) => boolean): boolean;
isEmpty(): boolean;
concat(...sequences: IEnumerable<T>[]): IEnumerable<T>;
concat(...sequences: { length: number;[x: number]: T; }[]): IEnumerable<T>;
concat(...sequences: T[]): IEnumerable<T>;
insert(index: number, second: IEnumerable<T>): IEnumerable<T>;
insert(index: number, second: { length: number;[x: number]: T; }): IEnumerable<T>;
alternate(alternateValue: T): IEnumerable<T>;
alternate(alternateSequence: { length: number;[x: number]: T; }): IEnumerable<T>;
alternate(alternateSequence: IEnumerable<T>): IEnumerable<T>;
alternate(alternateSequence: T[]): IEnumerable<T>;
contains(value: T): boolean;
contains<TCompare>(value: T, compareSelector?: (element: T) => TCompare): boolean;
defaultIfEmpty(defaultValue?: T): IEnumerable<T>;
distinct(): IEnumerable<T>;
distinct<TCompare>(compareSelector: (element: T) => TCompare): IEnumerable<T>;
distinctUntilChanged(): IEnumerable<T>;
distinctUntilChanged<TCompare>(compareSelector: (element: T) => TCompare): IEnumerable<T>;
except(second: { length: number;[x: number]: T; }): IEnumerable<T>;
except<TCompare>(second: { length: number;[x: number]: T; }, compareSelector: (element: T) => TCompare): IEnumerable<T>;
except(second: IEnumerable<T>): IEnumerable<T>;
except<TCompare>(second: IEnumerable<T>, compareSelector: (element: T) => TCompare): IEnumerable<T>;
except(second: T[]): IEnumerable<T>;
except<TCompare>(second: T[], compareSelector: (element: T) => TCompare): IEnumerable<T>;
intersect(second: { length: number;[x: number]: T; }): IEnumerable<T>;
intersect<TCompare>(second: { length: number;[x: number]: T; }, compareSelector: (element: T) => TCompare): IEnumerable<T>;
intersect(second: IEnumerable<T>): IEnumerable<T>;
intersect<TCompare>(second: IEnumerable<T>, compareSelector: (element: T) => TCompare): IEnumerable<T>;
intersect(second: T[]): IEnumerable<T>;
intersect<TCompare>(second: T[], compareSelector: (element: T) => TCompare): IEnumerable<T>;
union(second: { length: number;[x: number]: T; }): IEnumerable<T>;
union<TCompare>(second: { length: number;[x: number]: T; }, compareSelector: (element: T) => TCompare): IEnumerable<T>;
union(second: IEnumerable<T>): IEnumerable<T>;
union<TCompare>(second: IEnumerable<T>, compareSelector: (element: T) => TCompare): IEnumerable<T>;
union(second: T[]): IEnumerable<T>;
union<TCompare>(second: T[], compareSelector: (element: T) => TCompare): IEnumerable<T>;
sequenceEqual(second: { length: number;[x: number]: T; }): boolean;
sequenceEqual<TCompare>(second: { length: number;[x: number]: T; }, compareSelector: (element: T) => TCompare): boolean;
sequenceEqual(second: IEnumerable<T>): boolean;
sequenceEqual<TCompare>(second: IEnumerable<T>, compareSelector: (element: T) => TCompare): boolean;
sequenceEqual(second: T[]): boolean;
sequenceEqual<TCompare>(second: T[], compareSelector: (element: T) => TCompare): boolean;
orderBy<TKey>(keySelector: (element: T) => TKey): IOrderedEnumerable<T>;
orderByDescending<TKey>(keySelector: (element: T) => TKey): IOrderedEnumerable<T>;
reverse(): IEnumerable<T>;
shuffle(): IEnumerable<T>;
weightedSample(weightSelector: (element: T) => number): IEnumerable<T>;
// truly, return type is IEnumerable<IGrouping<TKey, T>> but Visual Studio + TypeScript Compiler can't compile.
groupBy<TKey>(keySelector: (element: T) => TKey): IEnumerable<IGrouping<TKey, T>>;
groupBy<TKey, TElement>(keySelector: (element: T) => TKey, elementSelector: (element: T) => TElement): IEnumerable<IGrouping<TKey, TElement>>;
groupBy<TKey, TElement, TResult>(keySelector: (element: T) => TKey, elementSelector: (element: T) => TElement, resultSelector: (key: TKey, element: IEnumerable<TElement>) => TResult): IEnumerable<TResult>;
groupBy<TKey, TElement, TResult, TCompare>(keySelector: (element: T) => TKey, elementSelector: (element: T) => TElement, resultSelector: (key: TKey, element: IEnumerable<TElement>) => TResult, compareSelector: (element: T) => TCompare): IEnumerable<TResult>;
// :IEnumerable<IGrouping<TKey, T>>
partitionBy<TKey>(keySelector: (element: T) => TKey): IEnumerable<IGrouping<TKey, any>>;
// :IEnumerable<IGrouping<TKey, TElement>>
partitionBy<TKey, TElement>(keySelector: (element: T) => TKey, elementSelector: (element: T) => TElement): IEnumerable<IGrouping<TKey, TElement>>;
partitionBy<TKey, TElement, TResult>(keySelector: (element: T) => TKey, elementSelector: (element: T) => TElement, resultSelector: (key: TKey, element: IEnumerable<TElement>) => TResult): IEnumerable<TResult>;
partitionBy<TKey, TElement, TResult, TCompare>(keySelector: (element: T) => TKey, elementSelector: (element: T) => TElement, resultSelector: (key: TKey, element: IEnumerable<TElement>) => TResult, compareSelector: (element: T) => TCompare): IEnumerable<TResult>;
buffer(count: number): IEnumerable<T>;
aggregate(func: (prev: T, current: T) => T): T;
aggregate<TAccumulate>(seed: TAccumulate, func: (prev: TAccumulate, current: T) => TAccumulate): TAccumulate;
aggregate<TAccumulate, TResult>(seed: TAccumulate, func: (prev: TAccumulate, current: T) => TAccumulate, resultSelector: (last: TAccumulate) => TResult): TResult;
average(selector?: (element: T) => number): number;
count(predicate?: (element: T, index: number) => boolean): number;
max(selector?: (element: T) => number): number;
min(selector?: (element: T) => number): number;
maxBy<TKey>(keySelector: (element: T) => TKey): T;
minBy<TKey>(keySelector: (element: T) => TKey): T;
sum(selector?: (element: T) => number): number;
elementAt(index: number): T;
elementAtOrDefault(index: number, defaultValue?: T): T;
first(predicate?: (element: T, index: number) => boolean): T;
firstOrDefault(predicate?: (element: T, index: number) => boolean, defaultValue?: T): T;
last(predicate?: (element: T, index: number) => boolean): T;
lastOrDefault(predicate?: (element: T, index: number) => boolean, defaultValue?: T): T;
single(predicate?: (element: T, index: number) => boolean): T;
singleOrDefault(predicate?: (element: T, index: number) => boolean, defaultValue?: T): T;
skip(count: number): IEnumerable<T>;
skipWhile(predicate: (element: T, index: number) => boolean): IEnumerable<T>;
take(count: number): IEnumerable<T>;
takeWhile(predicate: (element: T, index: number) => boolean): IEnumerable<T>;
takeExceptLast(count?: number): IEnumerable<T>;
takeFromLast(count: number): IEnumerable<T>;
indexOf(item: T): number;
indexOf(predicate: (element: T, index: number) => boolean): number;
lastIndexOf(item: T): number;
lastIndexOf(predicate: (element: T, index: number) => boolean): number;
asEnumerable(): IEnumerable<T>;
cast<TResult>(): IEnumerable<TResult>;
toArray(): T[];
// truly, return type is ILookup<TKey, T> but Visual Studio + TypeScript Compiler can't compile.
toLookup<TKey>(keySelector: (element: T) => TKey): ILookup<TKey, any>;
toLookup<TKey, TElement>(keySelector: (element: T) => TKey, elementSelector: (element: T) => TElement): ILookup<TKey, TElement>;
toLookup<TKey, TElement, TCompare>(keySelector: (element: T) => TKey, elementSelector: (element: T) => TElement, compareSelector: (key: TKey) => TCompare): ILookup<TKey, TElement>;
toObject(keySelector: (element: T) => any, elementSelector?: (element: T) => any): Object;
// :IDictionary<TKey, T>
toDictionary<TKey>(keySelector: (element: T) => TKey): IDictionary<TKey, any>;
toDictionary<TKey, TValue>(keySelector: (element: T) => TKey, elementSelector: (element: T) => TValue): IDictionary<TKey, TValue>;
toDictionary<TKey, TValue, TCompare>(keySelector: (element: T) => TKey, elementSelector: (element: T) => TValue, compareSelector: (key: TKey) => TCompare): IDictionary<TKey, TValue>;
toJSONString(replacer: (key: string, value: any) => any): string;
toJSONString(replacer: any[]): string;
toJSONString(replacer: (key: string, value: any) => any, space: any): string;
toJSONString(replacer: any[], space: any): string;
toJoinedString(separator?: string): string;
toJoinedString<TResult>(separator: string, selector: (element: T, index: number) => TResult): string;
doAction(action: (element: T, index: number) => void ): IEnumerable<T>;
doAction(action: (element: T, index: number) => boolean): IEnumerable<T>;
forEach(action: (element: T, index: number) => void ): void;
forEach(action: (element: T, index: number) => boolean): void;
write(separator?: string): void;
write<TResult>(separator: string, selector: (element: T) => TResult): void;
writeLine(): void;
writeLine<TResult>(selector: (element: T) => TResult): void;
force(): void;
letBind<TResult>(func: (source: IEnumerable<T>) => { length: number;[x: number]: TResult; }): IEnumerable<TResult>;
letBind<TResult>(func: (source: IEnumerable<T>) => TResult[]): IEnumerable<TResult>;
letBind<TResult>(func: (source: IEnumerable<T>) => IEnumerable<TResult>): IEnumerable<TResult>;
share(): IDisposableEnumerable<T>;
memoize(): IDisposableEnumerable<T>;
catchError(handler: (exception: any) => void ): IEnumerable<T>;
finallyAction(finallyAction: () => void ): IEnumerable<T>;
log(): IEnumerable<T>;
log<TValue>(selector: (element: T) => TValue ): IEnumerable<T>;
trace(message?: string): IEnumerable<T>;
trace<TValue>(message: string, selector: (element: T) => TValue ): IEnumerable<T>;
}
interface IOrderedEnumerable<T> extends IEnumerable<T> {
createOrderedEnumerable<TKey>(keySelector: (element: T) => TKey, descending: boolean): IOrderedEnumerable<T>;
thenBy<TKey>(keySelector: (element: T) => TKey): IOrderedEnumerable<T>;
thenByDescending<TKey>(keySelector: (element: T) => TKey): IOrderedEnumerable<T>;
}
interface IDisposableEnumerable<T> extends IEnumerable<T> {
dispose(): void;
}
interface IDictionary<TKey, TValue> {
add(key: TKey, value: TValue): void;
get(key: TKey): TValue;
set(key: TKey, value: TValue): boolean;
contains(key: TKey): boolean;
clear(): void;
remove(key: TKey): void;
count(): number;
toEnumerable(): IEnumerable<{ key: TKey; value: TValue }>;
}
interface ILookup<TKey, TElement> {
count(): number;
get(key: TKey): IEnumerable<TElement>;
contains(key: TKey): boolean;
toEnumerable(): IEnumerable<IGrouping<TKey, TElement>>;
}
interface IGrouping<TKey, TElement> extends IEnumerable<TElement> {
key(): TKey;
}
}
// export definition
declare var Enumerable: linqjs.Enumerable;
アップデートを全然追ってないので、初期に作った定義の仕方のまんまなので、大丈夫かな、まあ、大丈夫じゃないかな、きっと。
とりあえずとにかく面倒くさかった。しかし定義する人が苦労すれば、利用者はハッピーになれるから!!!なのではやくVS対応がまともになってください。ぶんぶんぶん回したいのだけれどねえ←その前にlinq.js ver.3がいつまでもBetaなのをなんとかしろ
C#の強み、或いは何故PHPから乗り換えるのか
- 2013-06-14
という内容で、C#ユーザー会で話してきました。
特にPHPディスりたいわけでは、あるのかないのかはともかく、やっぱり実際に使ってきて良いところというのも分からなくもない感じです。会場でも話したのは、短期的な開発速度には有利なのは間違いないのかな、と。デプロイとかも、とりあえずポン置きでいいし、開発も、なんかもう複雑なことやると面倒だし、どうせ文字列だらけになるしで、開き直ってハードコーディングでバカバカ作っていくから速い、とか。ただし勿論あとで苦労するわけですがそれはそれとして。けれどやっぱC#良いよね、って。
言語も色々なトレードオフで成り立つわけですが、その中でもC#は、バランス良くて好きだなーというのが私の個人的なところです。Visual Studio良いよねー、でもいいですしLINQ良いよねー、もいいですし、IntelliSenseがないと生きていけないですし。うん、そう、IntelliSense指向言語が好きなわけです。
ほとんどVisual Studioの話じゃねーか、というのは、まぁそうなのですけれど、大事なのはVisual Studioを前提においた言語構造になってるってとこです。強力すぎる型推論は、100%の入力補完を実現できなかったりする。強力すぎる動的さは100%の入力補完を実現できなかったりする。C#がVisual Studioとともに使って快適なのは、そういう言語設計になっているからです。コンパイルの速さも重要で。C#は他のコンパイル型言語に比べて速い部類に入ります。だから快適だし、エラー通知とかもリアルタイム。目に見えないところ、使ってみないと評価しにくい、ただの○×表だけの性能比較にはない部分、結構多いものです。
.NET最先端技術によるハイパフォーマンスウェブアプリケーション FAQ
- 2013-06-10
Build Insider Offlineにて、「.NET最先端技術によるハイパフォーマンスウェブアプリケーション」と題して、グラニのC#によるウェブアプリケーション作成の仕組みについて話してきました。
一日でViewsが1万、はてブが250と、ドトネト系にしては珍しく多くの人に見てもらえたようでなにより。
今までのC#関連って、MS純正ライブラリを使ったどうのこうの、というのはありましたが、外部ライブラリを組み合わせて、実践的にどうしているかっていうような話ってほとんどなかったんですよね。やってるところがない、ことはないのですが、しかし表にない、見えないものはないに等しいです。
別にC#だって自分達の手でライブラリを選び、作り、組み上げていく。それがこれからの時代のスタンダードです。遅れていたのかもしれません。しかし、遅すぎるなんてことはない。素材は良いし、.NETは死んだのでなく、むしろ風が吹いてきている。リアルなモデルケースとして、引っ張っていけたらと思っています。
FAQ
- 今時Windowsサーバー?
AWSやAzureなどで簡単にWindowsインスタンスの立ち上げが可能なので、今時というか普通に選択肢に入ってくれるといいですねえ、これからは。ウェブ=LAMPとか誰が決めたの?という話で。コスト面ではそんなに高くなるわけでもないし、それで開発効率が上がったり、サーバー台数が削減されるなら、むしろプラスです。開発効率に関しては人員次第ですが、うちのメンバーの習熟度で言ったら間違いなく上がります、圧倒的に。
- サーバーサイドでトラフィック以外がくってるのおかしい
そうそう、一般的にウェブではネックなのは通信部分だけ、と思っていたことも有りました。でもまぁ、クックパッドのRails アプリケーションのパフォーマンスについて RubyKaigi 2013 で発表しましたのスライドのResponse time breakdownのところ。SQLが20%がRubyが80%とあるんですね。うちのグラフと一緒?CakePHPはとにかくアレなわけですが、重量級フレームワークは割とそうなるもんなのかしらねえ、と少しだけホッとしたような絶望したような。
先入観じゃなくちゃんとモニタリングしてくのが大事、と当たり前の話ですけれど。スライドでも推しましたがNew Relicは本当に最高なので、よほどの事情がない限りは入れるべきかなぁ、と。PHP, Ruby, Java, .NET, Python, Node.js、多くの言語に対応しています。確実にパワーは喰いますが、それでも全然お釣りくる。
- アプリケーションサーバー:DBマスター:DBスレーブの比率がおかしい
ええと、これ、実質的にはSlaveは0です。色々な経緯があって、アプリ本体からは参照も全てMasterにし か振ってません。Slaveの用途は管理画面からの参照用とか、その程度です(スタンバイという点でも、RDSのMulti-AZを利用しているので意味は無い)。となると比率はますますオカシクなるわけですが、うーん、まあ、今時のDBってなんだかんだで頑丈ですからねえ、そう増えないかなあ。しかしそれにしてもアプリケーションサーバーの台数が多すぎなのは、まぁもう本当にすみませんすみませんって感じなわけなのですが。みんなCakePHP触ってみるといいですよ、XHProfにかけると絶望しますから。
勿論チューニングの余地はあります。ありますし、最後にはCakePHPを捨てるというところになることも見えるし、PHPにこだわる理由がどこにもありません。それならC#移行に、といったところですね。とはいえ実際のとこ増え続けることによる歪みが色々発生してるので、ヒィヒィ言いながら適宜対処してます。あと、最低限というか割とそれなりにはCakePHP本体に手を入れたりもしてはいるんですけど、それでも中々どうにも。
- PHPのLINQあるよ
ないよ。幾つかのPHP-LINQライブラリを、コードも読んで評価しましたが、ゴミという結論に達しました。私はJavaScriptにLINQ移植したりとかしているので、LINQにはこだわりがありひじょーにうるさいのです。あとCakePHPに含まれてるコレクション系のメソッドもゴミですね。かわりに自作したLINQっぽいコレクション処理用の何かを使っています。PHP 5.4を使っているので、ラムダとかもがしがし使えはするので、メソッドチェーンでフィルターやグルーピング、複数キーのソートとか一通りできるように作りました。これ作ってなかったら死んでたわ……。ただ、簡易的な実装なので遅延実行ではありません。最近発表されたGinqは非常に良いですね!もっと前にあったら、採用していたかもしれません。
- 「何でもハッシュに詰めるしかない」
ここ説明が足りなくてアレでしたね、申し訳ないです。文脈としてはIntelliSenseが効くか効かないか、の話しかするつもりなかったのと、ハッシュだろうとオブジェクトだろうとDBからの戻り値の場合はどうせ効かないのでどうでもいい、といった感だったので不正確でした、すみません。
あ、DBから以外の部分で、Modelとして作りこむところでは普通にclass立てたり、TypeHinting使ったりPhpDoc書いたりして、補完がなるべく効きやすいように作ってはいますよ。IDE信奉者なので、PhpStormを会社で購入して使っていますので。とはいえ、タイプヒンティングやPHPDocでも、PHP自体がゆるふわなので効きめはイマイチなんですよ。じゃあ、PHP自体がのゆるふわさを捨てて完全にガチガチに書くか?といったら、それはそれでイマイチになるので、まぁ、半分諦めるのがいいかな、とは。
一応誤解なきように弁解すると、無知のC#モノがイヤイヤPHP使ってるだけでPHPについて何も分かっちゃいない、というほどに分かってないわけではないです。一応はPHPの最新言語仕様についてはちゃんと追っかけて差分取っているぐらいには使っています。さすがに仕事の商売道具ですから、嫌いだから何も勉強しない!わけでもないです。traitなども効果的だと思ったところには使ってますし、まぁtrait使うと更にPhpStormの補完が死ぬわけですが。
- なんで水平分割いやがるの
一応スライド中にも書きましたが、メンドーごとが増えるので。特に嫌なのは、HeidiSQLとか、GUIツール郡の使い勝手が低下して、かわりに自社製のツールセット(コマンドか、しょぼいWebUI)になるか、とかですよねえ、それが一番避けたくて。他社の話を聞いていて驚くのは、phpMyAdminとかコマンド叩いてるとかいうんですよ、DB見たりするのに。私的には、ありえない。みんなもっとちゃんとGUIツールも使いましょう。必要とあらばウェブツールだけじゃなくてGUIツールも自作しましょう(C#なら簡単です!)。
まあ、どうしてもダメになったら水平にします。でもFusion-IOはもとより、AWSでも10万IOPSのインスタンスが出たりとか、ハードウェア性能はどんどん進化しているので、何とかなってくれるんじゃないかなあ、と楽観視はしています。良い時代になったな、と思います。
なぜ移行するの?C#のどこがいいの?
ということを、6/11 第90回codeseek&第30回日本C#ユーザー会 勉強会 「やっぱり.NETだよね」でお話しますので、明日ですが、当日まで申し込みは受け付けてる的なノリがいつもの感じだとあるので、時間あるかたは是非お越しください。そんなにDisってよりは割とSoftな感じです、私の分はいちおー。
珍しく既にスライドは完成していまして、「Static vs Dynamic」「Type for IntelliSense」「Type for Refactoring」「Debugger is Power」「LINQ vs array_xxx」「Razor:Template Engine Revolution」といったようなタイトルが並んでますので、気になった方はどーぞ。
ああ、そう、あとA vs Bにおいて、全てにおけてAのほうがいい、なんてことは絶対にないんですね。どこかしらかはBのほうがよかったり、BならXxxなのに、とかいうようなことも出てくるでしょう。何がどれだけ自分に、自分達に良いのかの選択をしてくのが肝要なので、その選択を考えるための道具になればいいかと思ってます。
HttpClient詳解、或いはAsyncOAuthのアップデートについて
- 2013-05-27
すっかり忘れていたわけではないですが、ちょっとかなり前、3/30のRoom metro #15にて、HttpClient詳解という、HttpClientについてのセッションを行いました。
HttpClientは、使えば使うほど、もうWebRequestやWebClientに戻りたくないわー、という非常に秀逸な、完全にこれからのスタンダードになる代物なので、きっちり習得しましょう。
或いは非同期の落とし穴について、ということで、async/awaitでも顕在の、いや、async/awaitだからこそ現れるデッドロックの問題と回避方法についても紹介しています。はまる時ははまっちゃうんですよねー、これ、何気に地味に実は。それなりに痛い目みました、私も。
PCL版のRC
HttpClientは現在.NET 4.5とWindows Store Appsのほうに標準搭載されていますが、それ以外でも使うために、Portable Class Libraryとしての提供がされています。そして、5/22にPortable HttpClient is now available as RCとしてRC版がリリースされました!
こないだまでのBeta版だと、AsyncOAuthを使ってTiwtterのストリーミングAPIを読む時に、awaitすると全く戻ってこなくなるという現象がありました。これはAsyncOAuthが悪いのかHttpClientが悪いのか調べたんですが、結果としてHttpClientのバグでした。HttpClientは内部で通信にWebRequestを使っているのですが、それのAllowReadStreamBufferingとAllowWriteStreamBufferingをfalseにセットしなければならないのに、何もセットしない(ことによって結果的にtrueになっている)状態でした。すると、ストリーミングAPIを読むのにバッファを取ろうとして、当然ストリーミングなのでオワリがないので永遠に帰ってこないという……。
ちゃんとバグ報告したら(偉い!←自分で言う)、今回のRC版で直してくれたようです、多分。とりあえずWP8のEmulatorで試した限りでは、ちゃんとストリーミングAPI動きました。よかったよかった。というわけで、AsyncOAuthもver.0.6.4として、新しいHttpClientに依存するようにアップデートしておきました。なお、AsyncOAuthについてはAsyncOAuth - C#用の全プラットフォーム対応の非同期OAuthライブラリを読んでくださいな。
そういえば同時に、というか4/17にですが、.NET4などでもasync/awaitを使えるようにするMicrosoft.Bcl.AsyncはStableになってました。これで気兼ねなくasync使える!
6月の予定
6月は何故かいっぱいイベントに出ることになっています。6/8のBuild Insider OFFLINE、そこで「.NET最先端技術によるハイパフォーマンスウェブアプリケーション」についてお話します。もう席は満席となってしまいましたが、Ustreamでの中継も行われるようですので、よろしければそちらで見ていただければと思います。
また、6/11~14あたりに、C#ユーザー会で何か話すそうです。何か。何でしょうね。一節によるとPHP被害者友の会(?)だとか・
そして6/22につくばC#勉強会でThe History of LINQと題して、何か話すそうです。はい。つくばいいですね!素晴らしいですぅー。つくば勉強会はまだまだ残席あるようなので、みんな参加しよう!登壇者も募集しているようですので、登壇もしよう!
RxとRedisを用いたリモートPub/Sub通信
- 2013-04-24
今日から始まったBuild Insiderで、RedisとBookSleeveの記事を書きました - C#のRedisライブラリ「BookSleeve」の利用法。Redis、面白いし、Windowsで試すのも想像以上に簡単なので、是非是非試してみて欲しいです。そして何よりもBookSleeve!使うと、強制的に全てがasyncになるので、C# 5.0でのコーディングの仕方のトレーニングになる(笑)。にちじょー的にasync/awaitを使い倒すと、そこから色々アイディアが沸き上がっきます。ただたんにsyncがasyncになった、などというだけじゃなく、アプリケーションの造りが変わります。そういう意味では、Taskは結構過小評価されてるのかもしれないな、なんて最近は思っています。
さて、RedisにはPub/Sub機能がついているわけですが、Pub/Sub→オブザーバーパターン→Rx!これはティンと来た!と、いうわけで、これ、Rxに乗せられました、とても自然に。というわけで、□□を○○と見做すシリーズ、なCloudStructuresにRx対応を載せました。
追加したクラスはRedisSubject<T>。これはSubjectやAsyncSubjectなどと同じく、ISubject<T>になっています。IObservableであり、IObserverでもある代物。とりあえず、コード例を見てください。
var settings = new RedisSettings("127.0.0.1");
var subject = new RedisSubject<string>(settings, "PubSubTest");
// SubscribeはIObservable<T>なのでRxなLINQで書ける
var a = subject
.Select(x => DateTime.Now.Ticks + " " + x)
.Subscribe(x => Console.WriteLine(x));
var b = subject
.Where(x => !x.StartsWith("A"))
.Subscribe(x => Console.WriteLine(x), () => Console.WriteLine("completed!"));
// IObserverなのでOnNext/OnError/OnCompletedでメッセージ配信
subject.OnNext("ABCDEFGHIJKLM");
subject.OnNext("あいうえお");
subject.OnNext("なにぬねの");
Thread.Sleep(200); // 結果表示を待つ...
a.Dispose(); // UnsubscribeはDisposeで
subject.OnCompleted(); // OnCompletedを受信したSubscriberもUnsubscribeされる
はい、別になんてこともない極々フツーのRxのSubjectです。が、しかし、これはネットワークを通って、Redisを通して、全てのSubscriberへとメッセージを配信しています。おお~。いやまあ、コードの見た目からじゃあそういうの分からないので何も感動するところもないのですが、とにかく、本当に極々自然に普通に、しかし、ネットワークを超えます。この、見た目何も変わらずに、というところがいいところなわけです。
Subscribeする側はIObservableなので別にフツーに合成していけますし、Publishする側もIObserverなので、Subscribeにしれっと潜り込ませたりしても構わない。もう、全然、普通にインメモリなRxでやる時と同じことが、できます。
オワリ。地味だ。
う、うーん。ほ、ほらほら!!
// ネットワーク経由
var settings = new RedisSettings("xx.xxx.xxx.xxx");
var subject = new RedisSubject<DateTime>(settings, "PubSubTest");
// publisherはこちらのコード
while (true)
{
Console.ReadLine();
var now = DateTime.Now;
Console.WriteLine(now.Ticks);
subject.OnNext(now);
}
// subscriberはこちらのコードを動かす
subject.Subscribe(x =>
{
var now = DateTime.Now;
Console.Write(x.Ticks + " => " + now.Ticks);
Console.WriteLine(" | " + (now - x));
});
というわけで、実際にネットワーク経由(AWS上に立てたRedisサーバーを通してる)で動かしてみた結果がこんな感じです。ネットワークを超えたことで用法は幾らでもある!夢膨らみまくり!
で、↑のコードは遅延時間のチェックを兼ねてるのですが、概ね、0.03秒ぐらい。たまにひっかかって0.5秒超えてるのがあって、ぐぬぬですが。実際のとこRedis/Linuxの設定で結構変わってくるところがあるので、その辺は煮詰めてくださいといったところでしょうか。
ともあれチャットとかなら全然問題なし、ゲームでもアクションとかタイミングにシビアじゃないものなら余裕ですね、ボードゲームぐらいなら全く問題ない。ちょっとしたMMOぐらいならいけるかも。これからはネットワーク対戦はRedisで、Rxで!!!
余談
CloudStructuresですが、.configからの設定読み込み機能も地味につけました。
<configSections>
<section name="cloudStructures" type="CloudStructures.Redis.CloudStructuresConfigurationSection, CloudStructures" />
</configSections>
<cloudStructures>
<redis>
<group name="cache">
<add host="127.0.0.1" />
<add host="127.0.0.2" port="1000" />
</group>
<group name="session">
<add host="127.0.0.1" db="2" valueConverter="CloudStructures.Redis.ProtoBufRedisValueConverter, CloudStructures" />
</group>
</redis>
</cloudStructures>
// これで設定を読み込める
var groups = CloudStructuresConfigurationSection.GetSection().ToRedisGroups();
色々使いやすくなってきて良い感じじゃあないでしょーか。
コールバック撲滅
そうそう、最後に実装の話を。元々BookSleeveのPub/Sub購読はコールバック形式です。「public Task Subscribe(string key, Action<string, byte[]> handler)」handlerはstringがキー, byte[]が送られてくるオブジェクトを指します。コールバック is ダサい。コールバック is 扱いにくい。ので、コールバックを見かけたらObservableかTaskに変換することを考えましょう!それがC# 5.0世代の常識です!
というわけで、以下のようにして変換しました。
public IDisposable Subscribe(IObserver<T> observer)
{
var channel = Connection.GetOpenSubscriberChannel();
var disposable = System.Reactive.Disposables.Disposable.Create(() =>
{
channel.Unsubscribe(Key).Wait();
});
// ここが元からあるコールバック
channel.Subscribe(Key, (_, xs) =>
{
using (var ms = new MemoryStream(xs))
{
var value = RemotableNotification<T>.ReadFrom(ms, valueConverter);
value.Accept(observer); // この中でobserverのOnNext/OnError/OnCompletedが叩かれる
if (value.Kind == NotificationKind.OnError || value.Kind == NotificationKind.OnCompleted)
{
disposable.Dispose(); // ErrorかCompletedでもUnsubscribeしますん
}
}
}).Wait();
return disposable; // もしDisposableが呼ばれたらUnsubscribeしますん
}
こんなふぅーにRxで包むことで、相当使いやすさがアップします。感動した。
CloudStructures - ローカルとクラウドのデータ構造を透過的に表現するC# + Redisライブラリ
- 2013-04-05
というものを作りました。インストールはNuGetから。
何を言ってるのかヨクワカラナイので、まずはコード例を。
// こんなクラスがあるとして
public class Person
{
public string Name { get; private set; }
public List<Person> Friends { get; private set; }
public Person(string name)
{
Name = name;
Friends = new List<Person>();
}
}
// こんなのがいるとして
var sato = new Person("さとう");
// 人を足す
sato.Friends.Add(new Person("やまだ"));
sato.Friends.Add(new Person("いとう"));
// 件数数える
var friendCount = sato.Friends.Count;
これは普通にローカルで表現する場合です。実に普通です。では、次。
// RedisServerの設定の表現
public static class RedisServer
{
public static readonly RedisSettings Default = new RedisSettings("127.0.0.1");
}
// こんなクラスがあるとして
public class Person
{
public string Name { get; private set; }
public RedisList<Person> Friends { get; private set; }
public Person(string name)
{
Name = name;
Friends = new RedisList<Person>(RedisServer.Default, "Person-" + Name);
}
}
// こんなのがいるとして
var sato = new Person("さとう");
// 人を足す
await sato.Friends.AddLast(new Person("やまだ"));
await sato.Friends.AddLast(new Person("いとう"));
// 件数数える
var friendCount = await sato.Friends.GetLength();
この場合、Redisを通してサーバー上にデータは保存されています。ですが、操作感覚はローカルにあるものとほぼほぼ同じです。違いは全ての操作が非同期なので、awaitするぐらい。
IAsyncList
これは、Actor Framework for Windows AzureのDistributed Collectionsに影響を受けています。ActorFxのそれは、SOURCE CODEを落としてdocsフォルダの Distributed Collections using the ActorFx.docx に色々書いてあって面白いので必読です。
そして、ActorFxではSystem.Cloud.Collectionsとして(System名前空間!)、現状、以下のようなインターフェイスが定義されています(まだ変更の可能性大いにあり)。
namespace System.Cloud.Collections
{
public interface IAsyncCollection<T> : IObservable<T>
{
Task<int> CountAsync { get; }
Task<bool> IsReadOnlyAsync { get; }
Task AddAsync(T item);
Task ClearAsync();
Task<bool> ContainsAsync(T item);
Task CopyToAsync(T[] array, int arrayIndex);
Task<bool> RemoveAsync(T item);
}
public interface IAsyncList<T> : IAsyncCollection<T>
{
Task<T> GetItemAsync(int index);
Task SetItemAsync(int index, T value);
Task<int> IndexOfAsync(T item);
Task InsertAsync(int index, T item);
Task RemoveAtAsync(int index);
// Less chatty versions
Task AddAsync(IEnumerable<T> items);
Task RemoveRangeAsync(int index, int count);
}
public interface IAsyncDictionary<TKey, TValue> : IAsyncCollection<KeyValuePair<TKey, TValue>>
{
Task<TValue> GetValueAsync(TKey key);
Task SetValueAsync(TKey key, TValue value);
Task<Tuple<bool, TValue>> TryGetValueAsync(TKey key);
// No AddAsync - use SetValueAsync instead. We have no atomic operation to add iff a value is not in the dictionary.
Task<bool> ContainsKeyAsync(TKey key);
Task<bool> RemoveAsync(TKey key);
// Bulk operations
Task<ICollection<TValue>> GetValuesAsync(IEnumerable<TKey> keys);
Task SetValuesAsync(IEnumerable<TKey> keys, IEnumerable<TValue> values);
Task RemoveAsync(IEnumerable<TKey> keys);
ICollection<TKey> Keys { get; }
ICollection<TValue> Values { get; }
}
}
わくわくしてきません?私はこの定義を見た瞬間に衝撃を受けました。RxのIObservable<T>を見た時と同程度の衝撃かもわからない。Ax(ActorFx)の実装としてはCloudList, CloudDictionary, CloudStringDictionaryがありますが(基盤としてAzure Table)、見てすぐにRedisと結びついた。Redisの持つデータ構造、List, Hash, Set, SortedSetってこれじゃないか!って。こういう風に表現されたらどれだけ素敵な見た目になるか……!
Strings, Set, SortedSet, List, Hash, その他
というわけで、最初の例ではRedisListだけ出しましたが、StringsもSetもSortedSetもHashもあります。また、HashClassやMemoizedRedisStringといった特殊なものも幾つか用意してあります。
// フィールドに持たなくても、ふつーにRedisClient的に使ってもいいよ
var client = new RedisString<string>(RedisServer.Default, "toaru-key");
await client.Set("あいうえお!", expirySeconds: TimeSpan.FromMinutes(60).TotalSeconds);
// RedisClassはRedisのHash構造をクラスにマッピングするもの
var hito = new RedisClass<Hito>(RedisServer.Default, "hito-1");
await hito.SetField("Name", "やまもと");
await hito.Increment("Money", 100);
var localHito = await hito.GetValue(); // Cloud -> Localに落とす、的ないめーぢ
実際色々あるので見て回ってください!
ConnectionManagement
基盤的な機能として、BookSleeveの接続管理を兼ねています。
// Redisの設定を表す
var settings = new RedisSettings(host: "127.0.0.1", port: 6379, db: 0);
// BookSleeveはスレッドセーフで単一のコネクションを扱う
// コネクションを一つに保ったり切断されていた場合の再接続などをしてくれる
var conn = settings.GetConnection();
// 複数接続はRedisGroupで管理できる
var group = new RedisGroup(groupName: "Cache", settings: new[]
{
new RedisSettings(host: "100.0.0.1", port: 6379, db: 0),
new RedisSettings(host: "105.0.0.1", port: 6379, db: 0),
});
// keyを元に分散先のサーバーを決める(デフォルトはMD5をサーバー台数で割って決めるだけの単純な分散)
var conn = group.GetSettings("hogehoge-100").GetConnection();
// シリアライザはデフォルトではJSONとProtoBufを用意(未指定の場合はJSON)
new RedisSettings("127.0.0.1", converter: new JsonRedisValueConverter());
new RedisSettings("127.0.0.1", converter: new ProtoBufRedisValueConverter());
って、ここまでBookSleeveの説明がなかった!BookSleeveはRedisのライブラリで、非同期の操作のみを提供しています。CloudStructuresのRedis操作はBookSleeveに全部委ねてます。というかぶっちゃけ、かなり単純なラップがほとんどだったりします(!)。見せ方を変えただけ、です、よーするところ。
んで、BookSleeveは斬新で非常に良いライブラリなのですけれど、操作が本当にプリミティブなものしかないので(全てのGetとSetがstringとbyte[]しかない、とかね)、ある程度、自分で作りこんでやらないと全く使えません。なので、この部分だけでも、結構使えるかなって思います。
Next
個人的にはすっごく面白いと思ってます。見せ方の違いでしかないわけですが、しかし、その見せ方の違いというのが非常に大事なのです。直感的、ですが、ある種奇抜なデザインなので、戸惑うとは思います。異色度合いで言ったら、以前に私の作ったReactivePropertyと同程度に異色かな、と。だからこそ、凄く大きな可能性を感じませんか?
ちなみに、これは(いつものように)コンセプト止まりじゃなくて、実際に使う予定アリなので、しっかり育ててく気満々です。是非、試してみてもらえると嬉しいですね。
Microsoft MVP for Visual C#を再再受賞しました
- 2013-04-02
今年も再受賞することができました。去年は某g社のフロントに立って、というのもあって、オフライン活動が割と非常に活発でした。ブログは量は減りましたし、中身も職業柄ウェブ系の要素が濃くなった感。とはいえ、内容自体は結構面白げなC#の内容になってたのではかしらん。基本的には、他で得られない情報や、ディープに踏み込んだ先の話、新しい考え。そういったのを提供し続けたいです。
今年はもっともっと面白くしたい。去年に、私は
C#を世間(といっても、技術系の人に、ですね)に強くアピールするにはどうすればいいのか、といったら、一番大事なのはそれで書かれたメジャーなアプリケーションなのです。PerlではてなやMixi、livedoorなどを思い浮かべる、RubyでCookpadを、ScalaでFoursquareやTwitterを、そういった憧れにも似た気持ちを浮かべさせるようなアプリケーションがなければいけなくて。
Stack OverflowはC#, ASP.NET MVCだし、TIOBEのプログラミング言語ランキングでは三位など、海外でのC#の地位は十分に高い。のですが、国内ではそれと比べれば全くもってない。日本で誰もが知る会社の誰もが知るアプリケーション、それがC#で書かれている。そういう状態にならなければ、日本で強く普及は無理だな、と。
と言いました。今年はそれを実現するため、謎社(もう謎ではない)のCTOとして、人の憧れるような、日本を代表するC#の企業にします。C#による圧倒的な成果、C#だからこその強さ、というのを現実に示していく。幸い、私は今、それができる場所にいると思っています。
そんなわけで、今年もよろしくお願いします。
C#のラムダ式でyieldっぽい何かをawaitで代用する方法
- 2013-03-27
C#がインラインでyield書けないならawait使えばいいじゃない。と、偉い人は言いました。というわけで、こそこそっと開発がされているIxに、面白い機能が入りました(開発リポジトリ上だけなのでNuGetからダウンロードしても、まだ入ってません)。こんなのです。
var hoge = "あいうえお";
var seq = EnumerableEx.Create<int>(async Yield =>
{
await Yield.Return(10);
await Yield.Return(100);
hoge = "ふがふが"; // インラインで書けるのでお外への副作用が可能
await Yield.Return(1000);
});
foreach (var item in seq)
{
Console.WriteLine(item); // 10, 100, 1000
}
Console.WriteLine(hoge); // ふがふが
そう、yield return(っぽい何か)がラムダ式で、メソッド外部に出すことなく書けてしまうのです!これは素敵ですね?い、いや、なんか何やってるのか分からなすぎて黒魔術怖いって雰囲気も漂ってますね!しかし面白いものは面白いので、実装見ましょう。
add types iyielder, iawaitable, and iawait; add support for creating ienumerable from action
public static class EnumerableEx
{
public static IEnumerable<T> Create<T>(Action<IYielder<T>> create)
{
if (create == null) throw new ArgumentNullException("create");
foreach (var x in new Yielder<T>(create))
{
yield return x;
}
}
}
public interface IYielder<in T>
{
IAwaitable Return(T value);
IAwaitable Break();
}
public interface IAwaitable
{
IAwaiter GetAwaiter();
}
public interface IAwaiter : ICriticalNotifyCompletion
{
bool IsCompleted { get; }
void GetResult();
}
public class Yielder<T> : IYielder<T>, IAwaitable, IAwaiter, ICriticalNotifyCompletion
{
private readonly Action<Yielder<T>> _create;
private bool _running;
private bool _hasValue;
private T _value;
private bool _stopped;
private Action _continuation;
public Yielder(Action<Yielder<T>> create)
{
_create = create;
}
public IAwaitable Return(T value)
{
_hasValue = true;
_value = value;
return this;
}
public IAwaitable Break()
{
_stopped = true;
return this;
}
public Yielder<T> GetEnumerator()
{
return this;
}
public bool MoveNext()
{
if (!_running)
{
_running = true;
_create(this);
}
else
{
_hasValue = false;
_continuation();
}
return !_stopped && _hasValue;
}
public T Current
{
get
{
return _value;
}
}
public IAwaiter GetAwaiter()
{
return this;
}
public bool IsCompleted
{
get { return false; }
}
public void GetResult() { }
public void OnCompleted(Action continuation)
{
_continuation = continuation;
}
public void UnsafeOnCompleted(Action continuation)
{
_continuation = continuation;
}
}
ほぅ、わけわからん?若干トリッキーなので、順を追っていきますか。asyncについて考える前に、まず、基本的なforeachのルール。実はIEnumerableを実装している必要はなくて、GetEnumeratorという名前のメソッドがあればいい。同様にMoveNextとCurrentというメソッドがあればIEnumerator扱いされる。なので、foreach (var x in new Yielder
あと、インターフェイスが、IAwaitableとかいっぱい再定義されてて、ワケワカランのですけれど、そこまで意味あるわけじゃないです。これはラムダ式にYielderを渡すわけですが、そこで内部の諸々が呼べちゃうのはイクナイので隠ぺいする、程度の意味合いでしかないので、これを実装するのにインターフェイスの再定義が必要!というわけは全然ないです。
で、コアになるのはMoveNext。
public bool MoveNext()
{
if (!_running)
{
_running = true;
_create(this);
}
else
{
_hasValue = false;
_continuation();
}
return !_stopped && _hasValue;
}
そもそもyield returnで生成されたメソッドが最初に実行されるのは、GetEnumeratorのタイミングではなく、GetEnumeratorされて最初のMoveNextが走った時、なので、ここが本体になっているのはセマンティクス的に問題なし。
!_runnningは初回実行時の意味で、ここで_create(this)、によってラムダ式で書いた本体が走ります。
var seq = EnumerableEx.Create<int>(async Yield =>
{
await Yield.Return(10);
// ↑のとこがまず実行され始める
await Yield.Return(100);
await Yield.Return(1000);
});
public IAwaitable Return(T value)
{
_hasValue = true;
_value = value;
return this;
}
まずはメソッド実行なのでReturn。これは値をセットして回っているだけ。そしてIAwaitableを返し、await。ここで流れは別のところに行きます。
public bool IsCompleted
{
get { return false; }
}
public void GetResult() { }
public void OnCompleted(Action continuation)
{
_continuation = continuation;
}
public void UnsafeOnCompleted(Action continuation)
{
_continuation = continuation;
}
まず完了しているかどうかの確認(IsCompleted)が走りますが、この場合は常にfalseで(そうしないと終了ということになってラムダ式のほうに戻ってこなくなっちゃう)。これによってUnsafeOnCompleted(ICriticalNotifyCompletionが実装されている場合はこっちが走る)でcontinuation(メソッド本体)が走る。で、「次回用」に変数保存して、MoveNext(create(this)したとこの位置)に戻ってくる。あとはMoveNextがtrueを返すのでCurrentで値取得して、それがyield returnされる。
二度目のMoveNextでは
public bool MoveNext()
{
if (!_running)
{
_running = true;
_create(this);
}
else
{
_hasValue = false;
_continuation(); // ここが呼び出されて
}
return !_stopped && _hasValue;
}
var seq = EnumerableEx.Create<int>(async Yield =>
{
await Yield.Return(10);
// ここから再度走り出す
await Yield.Return(100);
await Yield.Return(1000);
});
といった感じになって、以下繰り返し。良く出来てますね!ていうか、asyncなのに非同期全く関係ないのが素敵。そう、asyncは別に非同期関係なく使えちゃうわけです。ここ大事なので繰り返しましょう。asyncは別に非同期関係なく使うことができます。
まとめ
async、フツーに使うのもそろそろ飽きてき頃だと思うので、弄って遊ぶのは大正義。実際に投下しだすかどうかは判断次第。あと、↑のはまだ大事な要素ができていないので絶対使いませんけれど。大事な要素はIDisposableであること。foreachで大事だと思ってるのはDisposeしてくれるとこ!だとも思っているので、それが実現できてないのはナイナー、と。
そういえばAsyncについてですが、3/30の土曜にRoom metro #15でHttpClient(非同期の塊!)について話すので、まだ残席ありますので良ければお越しくだしあー。
並列実行とSqlConnection
- 2013-03-09
どうも、ParallelやThreadな処理が苦痛度100なペチパーです。嘘です。空前のThreadLocalブームが来てたり来てなかったりする昨今です。あ、謎社の宣伝しますとグリーとグラニ、「GREE」におけるソーシャルゲームの提供などについて戦略的業務提携に合意というわけで、ぐりとぐら、としかいいようがない昨今でもあります。その日に開催されていたGREEプラットフォームカンファレンスでは、謎社はC#企業になる!と大宣言したので、ちゃんと実現させていきたいところです、いや、むしろそのためにフル回転しています。
そんな宣伝はおいておいて本題なのですけれど、SQL。データベース。大量にクエリ発行したい時など、パラレル実行したいの!インサートだったら当然BulkInsertが一番早いんですが、Updateとかね。シンドイんだよね。あとUpsert(Merge/ON DUPLICATE KEY UPDATE)とかも使っちゃったりしてね。そんなわけで、お手軽お気楽な手法としてはParallelがありますねー、.NETはこの辺本当に楽なんだよねー、ぴーHPはシラネ。
で、実際パラレールにこんな感じに書くと……
using (var connection = new SqlConnection("接続文字列"))
{
connection.Open();
Parallel.For(1, 1000, x =>
{
var _ = connection.Query<DateTime>("select current_timestamp").First(); // Dapper
});
}
落ちます。理由は単純明快でSqlConnectionはスレッドセーフじゃないから。というわけで、やるなら
Parallel.For(1, 1000, x =>
{
using (var connection = new SqlConnection("接続文字列"))
{
connection.Open();
var _ = connection.Query<DateTime>("select current_timestamp").First(); // Dapper
}
});
となります、これなら絶対安全。でも、スレッドって基本的にコアの数とちょびっとしか立てられないわけだし、連続的に実行しているのだから、たとえコネクションプール行きだとかなんだりであっても、一々コネクションを開いて閉じてをするよりも、開きっぱで行きたいよね。
ようするにSqlConnectionがスレッドセーフじゃないからいけない。これはどこかで聞いたような話です。先日C#とランダムで出したThreadLocalの出番ではないでしょうか!
ThreadLocal
というわけでスレッドセーフなSqlConnectionを作りましょう、ThreadLocalを使って。
using (var connection = new ThreadLocal<SqlConnection>(() => { var conn = new SqlConnection("接続文字列"); conn.Open(); return conn; }))
{
Parallel.For(1, 1000, x =>
{
var _ = connection.Value.Query<DateTime>("select current_timestamp").First(); // Dapper
});
}
new SqlConnectionがThreadLocalに変わっただけのお手軽さ。これで、安全なんですって!本当に?本当に。で、実際こうして速度はどうなのかというと、私の環境で実行したところ、シングルスレッドで16秒、毎回new SqlConnectionするParallelで5秒、ThreadLocalなParallelで2秒でした。これは圧勝。幸せになれそう。
Disposeを忘れない、或いは忘れた
でも↑のコードはダメです。ダメな理由は、コネクションをDisposeしてないからです。ThreadLocalのDisposeは、あくまでThreadLocalのDisposeなのであって、中身のDisposeはしてくれてないのです。ここ忘れると悲劇が待ってます。でもFactoryで作ってる上にThreadで一意なValue、どうやってまとめてDisposeすればいいの!というと、trackAllValuesというオプションを有効にすると簡単に実現できます。
using (var connection = new ThreadLocal<SqlConnection>(() => { var conn = new SqlConnection("接続文字列"); conn.Open(); return conn; }
, trackAllValues: true)) // ThreadLocalの.Valuesプロパティの参照を有効化する
{
Parallel.For(1, 1000, x =>
{
var _ = connection.Value.Query<DateTime>("select current_timestamp").First(); // Dapper
});
// 生成された全てのConnectionを一括Dispose
foreach (var item in connection.Values.OfType<IDisposable>()) item.Dispose();
}
このtrackAllValuesが可能なThreadLocalは.NET 4.5からです。それ以前の人は、残念でした……。謎社は遠慮なく.NET 4.5を使いますので全然問題ありません(
もう一つまとめて
とはいえ、なんか面倒くさいので、ちょっとラップしませう、以下のようなクラスを用意します。
public static class DisposableThreadLocal
{
public static DisposableThreadLocal<T> Create<T>(Func<T> valueFactory)
where T : IDisposable
{
return new DisposableThreadLocal<T>(valueFactory);
}
}
public class DisposableThreadLocal<T> : ThreadLocal<T>
where T : IDisposable
{
public DisposableThreadLocal(Func<T> valueFactory)
: base(valueFactory, trackAllValues: true)
{
}
protected override void Dispose(bool disposing)
{
var exceptions = new List<Exception>();
foreach (var item in this.Values.OfType<IDisposable>())
{
try
{
item.Dispose();
}
catch (Exception e)
{
exceptions.Add(e);
}
}
base.Dispose(disposing);
if (exceptions.Any()) throw new AggregateException(exceptions);
}
}
これを使うと
using (var connection = DisposableThreadLocal.Create(() => { var conn = new SqlConnection("接続文字列"); conn.Open(); return conn; }))
{
Parallel.For(1, 1000, x =>
{
var _ = connection.Value.Query<DateTime>("select current_timestamp").First(); // Dapper
});
}
といったように、超シンプルに書けます。うん。いいね。
それAsync?
Asyncでドバッと発行してTask.WhenAll的なやり方も、接続が非スレッドセーフなのは変わらなくて、結構やりづらいんですよ……。それで、なんか色々細かくawaitしまくりで逆に遅くなったら意味ないし。それならドストレートに行ったほうがいいのでは感が若干ある。どうせThreadなんてそこそこ余ってるんだから(←そうか?)局所的にParallelってもいいぢゃないと思いたい、とかなんとかかんとか。
非.NET 4.5の場合
Parallel.For, ForEachに関しては、localInit, localFinallyというタスク内で一意になる変数を利用したオーバーロードを利用して、似たような雰囲気で書けます。正確には同じ挙動ではないですが、まぁまぁ悪くない結果が得られます。
Parallel.For(1, 1000,
() =>
{
// local init
var conn = new SqlConnection("接続文字列");
conn.Open();
return conn;
},
(x, state, connection) =>
{
var _ = connection.Query<DateTime>("select current_timestamp").First(); // Dapper
return connection;
},
(connection) =>
{
// local finally
connection.Dispose();
});
オーバーロードが結構地獄でシンドイですね!ここも簡単にラップしたものを作りましょう。
public static class ParallelEx
{
public static ParallelLoopResult DisposableFor<TDisposable>(long fromInclusive, long toExclusive, Func<TDisposable> resourceFactory, Action<long, ParallelLoopState, TDisposable> body)
where TDisposable : IDisposable
{
return Parallel.For(fromInclusive, toExclusive, resourceFactory, (item, state, resource) => { body(item, state, resource); return resource; }, disp => disp.Dispose());
}
public static ParallelLoopResult DisposableForEach<T, TDisposable>(IEnumerable<T> source, Func<TDisposable> resourceFactory, Action<T, ParallelLoopState, TDisposable> body)
where TDisposable : IDisposable
{
return Parallel.ForEach(source, resourceFactory, (item, state, resource) => { body(item, state, resource); return resource; }, disp => disp.Dispose());
}
}
こうしたものを作れば、
ParallelEx.DisposableFor(1, 1000,
() =>
{
var conn = new var conn = new SqlConnection("接続文字列");
conn.Open();
return conn;
},
(x, state, connection) =>
{
var _ = connection.Query<DateTime>("select current_timestamp").First(); // Dapper
});
まぁまぁ許せる、かな?
C#とランダム
- 2013-03-06
古くて新しいわけはない昔ながらのSystem.Randomのお話。Randomのコンストラクタは二種類あって、seed引数アリの場合は必ず同じ順序で数値を返すようになります。
// 何度実行しても同じ結果
var rand = new Random(0);
Console.WriteLine(rand.Next()); // 1559595546
Console.WriteLine(rand.Next()); // 1755192844
Console.WriteLine(rand.Next()); // 1649316166
例えばゲームのリプレイなどは、ランダムだけど同一の結果が得られることを期待したいわけなので、大事大事ですね。(とはいえ、Windows-CLIとLinux-monoでは結果が違ったりするので、マルチプラットフォームでの共有などという場合は、別策を取ったほうがよさそうです)。何も渡さない場合はseedとしてEnvironment.TickCountが渡されます。精度はミリ秒。ということは、ですね、例えばループの中でRandomをnewするとですよ、
for (int i = 0; i < 100; i++)
{
var rand = new Random();
Console.WriteLine(rand.Next());
}
マシンスペックにもよりますが、私の環境では30個ぐらい同じ数値が出た後に、別の、また30個ぐらい同じ数値が続き……となりました。何故か、というと、seedがEnvironment.TickCountだからで、ループ内といったようなミリ秒を超える超高速の状態で生成されている時は、seed値が同じとなってしまうから。なので、正しくは
var rand = new Random();
for (int i = 0; i < 100; i++)
{
Console.WriteLine(rand.Next());
}
といったように、ループの外に出す必要性があります。
ランダムなランダム
では、ランダムなランダムが欲しい場合は。例えばマルチスレッド。そうでなくても、例えばループの外に出す(直接的でなくてもメソッドの中身がそうなっていて、意図せず使われてしまう可能性がある)のを忘れてしまうのを強制的に避ける場合。もしくは、別にマルチスレッドは気を付けるよー、といっても、ASP.NETとか複数リクエストが同時に走るわけで、同タイミングでのRandom生成になってしまう可能性は十分にある。そういう時は、RandomNumberGeneratorを使います。
using (var rng = new RNGCryptoServiceProvider())
{
// 厳密にランダムなInt32を作る
var buffer = new byte[sizeof(int)];
rng.GetBytes(buffer);
var seed = BitConverter.ToInt32(buffer, 0);
// そのseedを基にRandomを作る
var rand = new Random(seed);
}
これでマルチスレッドでも安全安心だ!勿論、RNGCryptoServiceProviderはちょっとコスト高。でも、全然我慢できる範囲ではある。お終い。
ThreadLocal
でも、これって別にスレッドセーフなランダムが欲しいってだけなわけだよね、それなのにちょっとした、とはいえ、コスト高を背負うのって馬鹿げてない?そこで出てくるのがThreadLocal<T>、.NET 4.0以降ですが、スレッド単位で一意な変数を宣言できます。それを使った、Jon Skeet氏(ゆーめーじん)の実装は
public static class RandomProvider
{
private static int seed = Environment.TickCount;
private static ThreadLocal<Random> randomWrapper = new ThreadLocal<Random>(() =>
new Random(Interlocked.Increment(ref seed))
);
public static Random GetThreadRandom()
{
return randomWrapper.Value;
}
}
なるほどねー!これなら軽量だし、とってもセーフで安心できるしイイね!もし複数スレッドで同時タイミングで初期化が走った時のために、Interlocked.Incrementで、必ず違う値がseedになるようになってるので、これなら色々大丈夫。
マルチスレッド→マルチサーバー
けれど、大丈夫なのは、一台のコンピューターで完結する時だけの時の話。クラウドでしょ!サーバー山盛りでしょ!な時代では、サーバーをまたいで同時タイミングなEnvironment.TickCountで初期化されてしまう可能性が微レ存。というわけで、Environment.TickCountに頼るのは完全に安全ではない。じゃあ、そう、合わせ技で行けばいいじゃない、seedは完全ランダムで行きましょう。
public static class RandomProvider
{
private static ThreadLocal<Random> randomWrapper = new ThreadLocal<Random>(() =>
{
using (var rng = new RNGCryptoServiceProvider())
{
var buffer = new byte[sizeof(int)];
rng.GetBytes(buffer);
var seed = BitConverter.ToInt32(buffer, 0);
return new Random(seed);
}
});
public static Random GetThreadRandom()
{
return randomWrapper.Value;
}
}
これで、軽量かつ安全安泰なRandomが手に入りました。めでたしめでたし。
AsyncOAuth - C#用の全プラットフォーム対応の非同期OAuthライブラリ
- 2013-02-27
待ち望まれていたHttpClientがPortable Class Library化しました、まだBetaだけどね!というわけで、早速PCL版のHttpClientをベースにしたOAuthライブラリを仕上げてみました。ポータブルクラスライブラリなので、.NET 4.5は勿論、Windows Phone 7.5, 8, Windows Store Apps, Silverlight, それと.NET 4.0にも対応です。
前身のReactiveOAuthがTwitterでしかロクにテストしてなくてHatenaでズタボロだったことを反省し、今回はSampleにTwitterとHatenaを入れておきました&どっちでもちゃんと正常に動きます。なお、完全に上位互換なので、ReactiveOAuthはObsoleteです。それと、ライブラリのインストールはNuGet経由でのみの提供です。
PM> Install-Package AsyncOAuth -Pre
もしくはPreReleaseを表示に含めてGUIから検索してください。
AsyncOAuth is not a new library
AsyncOAuthの実態はOAuthMessageHandlerというDelegatingHandlerです。
var client = new HttpClient(new OAuthMessageHandler("consumerKey", "consumerSecret", new AccessToken("accessToken", "accessTokenSecret")));
// 上のだとnewの入れ子が面倒なので短縮形、戻り値は上のと同じ
var client = OAuthUtility.CreateOAuthClient("consumerKey", "consumerSecret", new AccessToken("accessToken", "accessTokenSecret"));
こうなっていると何がいいか、というと、全ての操作がHttpClient標準通りなのです。
// Get
var json = await client.GetStringAsync("http://api.twitter.com/1.1/statuses/home_timeline.json?count=" + count + "&page=" + page);
// Post
var content = new FormUrlEncodedContent(new[] { new KeyValuePair<string, string>("status", status) });
var response = await client.PostAsync("http://api.twitter.com/1.1/statuses/update.json", content);
var json = await response.Content.ReadAsStringAsync();
// Multi Post
var content = new MultipartFormDataContent();
content.Add(new StringContent(status), "\"status\"");
content.Add(new ByteArrayContent(media), "media[]", "\"" + fileName + "\"");
var response = await client.PostAsync("https://upload.twitter.com/1/statuses/update_with_media.json", content);
var json = await response.Content.ReadAsStringAsync();
もうおれおれクライアントのAPIを覚える必要はありません。これからの標準クライアントであるHttpClientの操作だけを覚えればいいのです。
コンセプトはHttpClientチームから掲示されているサンプルコードExtending HttpClient with OAuth to Access Twitterどおりですが、このサンプルコードは本当にただのコンセプトレベルなサンプルで、そのまんまじゃ使えないので、ちゃんと実用的なOAuthライブラリとして叩き直したのがAsyncOAuthになります。DelegatingHandlerというのは、リクエストを投げる直前をフックするものなので、そこでOAuth用の認証を作っているわけです。
イニシャライズ
使う場合は、必ず最初にHMAC-SHA1の計算関数をセットしなければなりません。何故か、というと、ポータブルクラスライブラリには現状、暗号系のライブラリが含まれていなくて、その部分は含むことができないからです。外部から差し込んでもらうことでしか対処できない、という。ご不便おかけしますが、的な何か。そのうち含まれてくれるといいなあ、って感じですねえ。それまでは、以下のコードをApp.xaml.csとかApplication_Startとか、初回の本当に最初の最初に呼ばれるところに、コピペってください。
// WinRT以外(Silverlight, Windows Phone, Consoleなどなど)
OAuthUtility.ComputeHash = (key, buffer) => { using (var hmac = new HMACSHA1(key)) { return hmac.ComputeHash(buffer); } };
// Windows Store App(めんどうくせえええええ)
AsyncOAuth.OAuthUtility.ComputeHash = (key, buffer) =>
{
var crypt = Windows.Security.Cryptography.Core.MacAlgorithmProvider.OpenAlgorithm("HMAC_SHA1");
var keyBuffer = Windows.Security.Cryptography.CryptographicBuffer.CreateFromByteArray(key);
var cryptKey = crypt.CreateKey(keyBuffer);
var dataBuffer = Windows.Security.Cryptography.CryptographicBuffer.CreateFromByteArray(buffer);
var signBuffer = Windows.Security.Cryptography.Core.CryptographicEngine.Sign(cryptKey, dataBuffer);
byte[] value;
Windows.Security.Cryptography.CryptographicBuffer.CopyToByteArray(signBuffer, out value);
return value;
};
また、使いかたの詳しいサンプルは、GitHub上のソースコードからAsyncOAuth.ConsoleAppの中にTwitter.csとHatena.csがあるので、それを見てもらえればと思います。AccessToken取得までの、認証系の説明はここには書きませんが(OAuthAuthorizerという特別に用意してあるものを使う)、その具体的な書き方が乗っています。特にHatenaの認証はTwitterに比べるとかなりメンドーくさいので、メンドーくさい系のOAuthが対象の場合は参考になるかと思います。
ストリーミング、Single vs Multiple、或いはRxの再来
勿論、TwitterのストリーミングAPIにも対応できます。以下のようなコードを書けばOK。
public async Task GetStream(Action<string> fetchAction)
{
var client = OAuthUtility.CreateOAuthClient(consumerKey, consumerSecret, accessToken);
client.Timeout = System.Threading.Timeout.InfiniteTimeSpan; // ストリーミングなのでTimeoutで切られないよう設定しておくこと
using (var stream = await client.GetStreamAsync("https://userstream.twitter.com/1.1/user.json"))
using (var sr = new StreamReader(stream))
{
while (!sr.EndOfStream)
{
var s = await sr.ReadLineAsync();
fetchAction(s);
}
}
}
ほぅ、Actionですか、コールバックですか……。ダサい。使い勝手悪い。最悪。しかし、じゃあ何返せばいいんだよ!ということになる。Taskは一つしか返せない、でもストリーミングは複数。うーん、うーん、と、そこでIObservable<T>の出番です。Reactive Extensionsを参照して、以下のように書き換えましょう。
public IObservable<string> GetStream()
{
return Observable.Create<string>(async (observer, ct) =>
{
try
{
var client = OAuthUtility.CreateOAuthClient(consumerKey, consumerSecret, accessToken);
client.Timeout = System.Threading.Timeout.InfiniteTimeSpan; // ストリーミングなのでTimeoutで切られないよう設定しておくこと
using (var stream = await client.GetStreamAsync("https://userstream.twitter.com/1.1/user.json"))
using (var sr = new StreamReader(stream))
{
while (!sr.EndOfStream && !ct.IsCancellationRequested)
{
var s = await sr.ReadLineAsync();
observer.OnNext(s);
}
}
}
catch (Exception ex)
{
observer.OnError(ex);
return;
}
if (!ct.IsCancellationRequested)
{
observer.OnCompleted();
}
});
}
var client = new TwitterClient(consumerKey, consumerSecret, new AccessToken(accessTokenKey, accessTokenSecret));
// subscribe async stream
var cancel = client.GetStream()
.Skip(1)
.Subscribe(x => Console.WriteLine(x));
Console.ReadLine();
cancel.Dispose(); // キャンセルはDisposeで行う
といったように、自然にRxと繋げられます。コールバックのObservable化はObservable.Createで、そんなに難しくはない(ただしOnNext以外にちゃんとOnError, OnCompletedも記述してあげること)です。キャンセル対応に関しては、ちゃんとCancelleationToken付きのオーバーロードで行いましょう。そうしないと、Subscribeの解除はされていても、内部ではループが延々と動いている、といったような状態になってしまいますので。
ともあれ、asyncやCancellationTokenとRxがスムースに結合されていることは良くわかるかと思います。完璧!
こういった、単発の非同期はTaskで、複数の非同期はIObservable<T>で行う、というガイドはTPLチームからも示されています。先日のpfxteamからのスライドから引用すると(ちなみにこのスライドはTask系の落とし穴などが超丁寧に書かれているので必読!)
といった感じです。んねー。
まとめ
ReactiveOAuthはオワコン。HttpClient始まってる。Reactive Extensions自体は終わってない、むしろ始まってる。というわけで、色々と使いこなしていきましょう。
追記:リリースから一晩開けて、POST周りを中心にバグが発見されていてお恥ずかしい限りです。あらかた修正したとは思うのですが(NuGetのバージョンは随時上げています)、怪しい挙動見つけたら報告下さると嬉しいです。勿論、GitHubなのでPull Requestでも!
C#でぬるぽを回避するどうでもいい方法
- 2013-02-19
どうもペチパーです。嘘です逃げないで。まあ、どうでもいいPHPの例をまずは出しませう。あ、逃げないで、PHPの話はすぐやめるんで。
// ネストしてる配列
$hoge["huga"]["hage"]["tako"] = "なのなの";
// なのなの
$v = isset($hoge["huga"]["hage"]["tako"])
? $hoge["huga"]["hage"]["tako"]
: "ない";
// 途中で欠けてる配列
$hoge["huga"] = "なのなの";
// ない
$v = isset($hoge["huga"]["hage"]["tako"])
? $hoge["huga"]["hage"]["tako"]
: "ない";
全体的にキモいんですが、まあ無視してもらって、何が言いたいか、と言うとisset。これはネストしてる部分も一気に評価してくれるのです。フツーの関数だと常識的に考えて評価は先に内側で行うので配列の境界外で死ぬんですが、issetは関数みたいな見た目だけど実は言語構文なのだ!キモチワルイ。ともあれ、そんなわけでネストしてるものの有無を一気にチェックできるのです。
で、PHPのこと書いてるとサイト違うので、C#の話をしませう。
C#でネストネスト
PHPは何でも連想配列なのですが、C#だったらクラスのプロパティでしょうか。以下のようなシチュエーション。
// こういうドカドカした構造があるとして
class Hoge
{
public Huga Prop1 { get; set; }
}
class Huga
{
public Hage Prop2 { get; set; }
}
class Hage
{
public string Prop3 { get; set; }
}
// こっちプログラム本体
var hoge = new Hoge();
// とちゅーでヌルぽが発生すると死んじゃうんの回避が醜悪!
var prop3 = (hoge != null && hoge.Prop1 != null && hoge.Prop1.Prop2 != null && hoge.Prop1.Prop2.Prop3 != null)
? hoge.Prop1.Prop2.Prop3
: null;
!=nullの連鎖が面倒くさいですぅー。なんとかしてくださいぃー。ぴーHPに負けてるんじゃないですかぁー?とか言われてないですが言われてるってことにするので、しょうがないからエレガントな解決策を探してあげました、誰にも頼まれてませんが!
Love ExpressionTree
こーいう風に書ければいいんでしょ!下のhoge.GetValueOrDefaultってとこです。
// こんなHogeがあるとして
var hoge = new Hoge();
// すっきり!
var value = hoge.GetValueOrDefault(x => x.Prop1.Prop2.Prop3);
Console.WriteLine(value == null); // true
// 中身が詰まってたら
hoge = new Hoge { Prop1 = new Huga { Prop2 = new Hage { Prop3 = "ほげ!" } } };
var value2 = hoge.GetValueOrDefault(x => x.Prop1.Prop2.Prop3);
Console.WriteLine(value2); // ほげ!
すっごくスッキリしますね!イイね!
で、どーやってるかというと、ExpressionTreeでグルグルですよ。
public static class MonyaMonyaExtensions
{
public static TR GetValueOrDefault<T, TR>(this T value, Expression<Func<T, TR>> memberSelector)
where T : class
{
var expression = memberSelector.Body;
var memberNames = new List<string>();
while (!(expression is ParameterExpression))
{
if ((expression is UnaryExpression) && (expression.NodeType == ExpressionType.Convert))
{
expression = ((UnaryExpression)expression).Operand;
continue;
}
var memberExpression = (MemberExpression)expression;
memberNames.Add(memberExpression.Member.Name);
expression = memberExpression.Expression;
}
object value2 = value;
for (int i = memberNames.Count - 1; i >= 0; i--)
{
if (value2 == null) return default(TR);
var memberName = memberNames[i];
dynamic info = value2.GetType().GetMember(memberName)[0];
value2 = info.GetValue(value2);
}
return (TR)value2;
}
}
はい。というわけで、一つ言えるのは、これ、あんま速くないんで実用には使わないでくださいね、あくまでネタです、ネタ。
もにゃど
それもにゃど、という人はLINQでMaybeモナドでも検索しませう。既出なので私は書きません。