Micro-ORMとテーブルのクラス定義自動生成について

謎社のデータアクセスは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正式版対応とバグ修正

HttpClientがRTMを迎えた、と思ったら、次バージョンのベータが出た、と、展開早くて追いつけないよ~な感じですが、とりあえず正式版のほうを要求する形で、AsyncOAuthも今回からBetaじゃなくなりました。

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

という内容で、つくばC#勉強会で話してきました。

The History of LINQ from Yoshifumi Kawai

初心者向け勉強会(?)ということで、歴史ですかにぇー。詳細よりは、全体的にあっさり、みたいな。多分ね!とかもう10年前ですが、これだけ時間のたった今だからこそ、あらためてちょっと見てみると面白いね、みたいな。

第一回つくばC#勉強会 #tkbcsmt 当日の様子ということで実にカオス、よかったね!面白かったです~。

TypeScript 0.9のジェネリクス対応でlinq.jsの型定義作って苦労した話

久しぶりのTypeScriptAnnouncing 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から乗り換えるのか

という内容で、C#ユーザー会で話してきました。

C#の強み、或いは何故PHPから乗り換えるのか from Yoshifumi Kawai

特にPHPディスりたいわけでは、あるのかないのかはともかく、やっぱり実際に使ってきて良いところというのも分からなくもない感じです。会場でも話したのは、短期的な開発速度には有利なのは間違いないのかな、と。デプロイとかも、とりあえずポン置きでいいし、開発も、なんかもう複雑なことやると面倒だし、どうせ文字列だらけになるしで、開き直ってハードコーディングでバカバカ作っていくから速い、とか。ただし勿論あとで苦労するわけですがそれはそれとして。けれどやっぱC#良いよね、って。

言語も色々なトレードオフで成り立つわけですが、その中でもC#は、バランス良くて好きだなーというのが私の個人的なところです。Visual Studio良いよねー、でもいいですしLINQ良いよねー、もいいですし、IntelliSenseがないと生きていけないですし。うん、そう、IntelliSense指向言語が好きなわけです。

ほとんどVisual Studioの話じゃねーか、というのは、まぁそうなのですけれど、大事なのはVisual Studioを前提においた言語構造になってるってとこです。強力すぎる型推論は、100%の入力補完を実現できなかったりする。強力すぎる動的さは100%の入力補完を実現できなかったりする。C#がVisual Studioとともに使って快適なのは、そういう言語設計になっているからです。コンパイルの速さも重要で。C#は他のコンパイル型言語に比べて速い部類に入ります。だから快適だし、エラー通知とかもリアルタイム。目に見えないところ、使ってみないと評価しにくい、ただの○×表だけの性能比較にはない部分、結構多いものです。

.NET最先端技術によるハイパフォーマンスウェブアプリケーション FAQ

Build Insider Offlineにて、「.NET最先端技術によるハイパフォーマンスウェブアプリケーション」と題して、グラニのC#によるウェブアプリケーション作成の仕組みについて話してきました。

.NET最先端技術によるハイパフォーマンスウェブアプリケーション from Yoshifumi Kawai

一日で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なのに、とかいうようなことも出てくるでしょう。何がどれだけ自分に、自分達に良いのかの選択をしてくのが肝要なので、その選択を考えるための道具になればいいかと思ってます。

Profile

Yoshifumi Kawai

Cysharp, Inc
CEO/CTO

Microsoft MVP for Developer Technologies(.NET)
April 2011
|
July 2025

X:@neuecc GitHub:neuecc

Archive