Array.Findを2次元に拡張したかっただけなのに

添え字を表すタプルを(その型を予め宣言せずに)返そうとして大騒ぎになってしまった.
C#の匿名型(レコード)とジェネリクス型推論との関係はこなれてない... メソッドの返り値型に匿名型が来てはいけないと言うのでこんな苦労を.まぁオブジェクト指向のinclusion polymorphismが先にあったから,ゲイツ様のお馬をみんな集めても,ゲイツ様の家来をみんな集めても (All Bill's horses and all Bill's men*1 ) 色々難しいのだろう.
匿名型を持ってきたいが持ってこれないメソッドの返り値型はジェネリクスの型パラメタで置き換えてやると,型パラメタを決定できるようにするにはメソッド呼び出しの実引数にその匿名型を含む値が来なければならない.そんな訳で new { ... } による匿名型オブジェクトの生成と,それを受ける var (ローカル変数) = ... が同一メソッド内になければならないことが帰結し,以下のコードでは(普通なら関数内で行いたい)匿名型オブジェクトの生成部分をdelegate型引数として分離して呼び出し側に移している.非常に醜いけど.
しかしvarλ式を束縛できてもいいじゃないか...
参考:

どうでもいいがIdentityを使った例は極めてバッドノウハウ的で,匿名型の利点のうち「名前空間を消費しない」ことしか生きていない(タイプ量の減少に関しては,「同一のシグネチャを持つ自明なdelegateで予め初期化しておくことで型推論を助ける」というアプローチでは必然的に「同一のシグネチャを持つ関数」を書くために同じ匿名型を書かねばならず,専用のクラスを先に宣言するのとタイプ量は大差ない).

using System;

class Find2D {
    static void Main() {
        var table=new[,]{{"a","b","c"},{"d","e","f"}};

        var pos = Find(table, e=>e=="d", (i,j)=>new{i,j});
        Console.WriteLine("{0} {1}", pos.i, pos.j);
        var pos1 = Find(table, e=>e=="e", (i,j)=>new{i,j});
        Console.WriteLine("{0} {1}", pos1.i, pos1.j);
    }
    static T Find<S,T>(S[,] arr, Func<S,bool> p, Func<int,int,T>ret) {
        for (int i=0; i<arr.GetLength(0); ++i)
            for (int j=0; j<arr.GetLength(1); ++j)
                if (p(arr[i,j])) return ret(i,j);
        return default(T);
    }
}