Linq雑話

ここ数日Twitterで見た/出したLinqネタまとめ。私の広くない観測範囲(@neuecc)での話ですが。

SelectManyとクエリ構文でUsing

ネタ元、コード元はCode, code and more code.: SelectMany; combining IDisposable and LINQから。

static void Main(string[] args)
{
    var firstLines =
        from path in new[] { "foo.txt", "bar.txt" }
        from stream in File.OpenRead(path)
        from reader in new StreamReader(stream)
        select path + "\t" + reader.ReadLine();
}

public static IEnumerable<TResult> SelectMany<TSource, TDisposable, TResult>(
    this IEnumerable<TSource> source,
    Func<TSource, TDisposable> disposableSelector,
    Func<TSource, TDisposable, TResult> resultSelector) where TDisposable : IDisposable
{
    foreach (var item in source)
    {
        using (var disposableItem = disposableSelector(item))
            yield return resultSelector(item, disposableItem);
    }
}

自前定義の拡張メソッドはメソッド構文だけのものと思っていませんでしたか?私はそう思っていました。でも、クエリ構文でも同名のものがあれば拡張メソッドが使用されるんです、というお話。それを利用してusingのネストをクエリ構文で華麗に表現してやったぜー、というサンプルで、確かにこれはクール!素晴らしすぎる。

でも、クエリ構文使いたいかというと、そんなことはなく変わらずメソッド構文派です、私は。クエリ構文自体は悪いとは思わないし、良さがあるのも分かるんですが、他の拡張メソッドに繋げる時に前後にカッコで括ると途端に書き/読みにくくなることと、拡張性の乏しさが如何ともし難い。クエリ構文とメソッド構文のちゃんぽんになるぐらいなら、メソッド構文だけで書いたほうが美しいよね、と思ってしまう。あと、クエリ構文の存在が「LINQ = SQLみたいなの」という図式を産んでしまっているくさいのも、憎んでしまいますね……。

ラムダ式の引数の名前とシャッフルについて

お馴染み感溢れるOrderByでのシャッフル。

var rand = new Random();
var shuffle = Enumerable.Range(1, 10).OrderBy(_ => rand.Next());

それはそれとして、ラムダ式の引数の名前どうする?というお話が。私は、引数を使わない場合は _ を、使う場合は型の1~2文字(i(Int32)とかs(String)とかa(AnonymousType)とか、考えるの面倒なときはx、配列系はarかxs)という自分ルールを敷いています。以前にラムダ式の引数の名前という記事を書いたのですが、その時から変わっていません。ですが、最近ネットで見かけるコードでは全部_でまかなう例もよく見るね、と。Scalaでは匿名関数の引数として_が使える(プレースホルダ構文って言うんですね、名前知らなかった)ようなので、_をダメとは言い辛いのですけど、私はちょち苦手(linq.jsで$をゴリゴリ使ってるくせに、って話ではあるけど) 。

C#にもプレースホルダ構文みたいなの欲しいね、というのは、若干ある。プロパティの「value」とか最初からそこにある良く分からない変数、みたいなのはあるし。ただ、IntelliSenseとの兼ね合いもあるし、そういうのが入れられるか、入って本当に幸せになれるのかどうかの判断は保留。短絡的に欲しい!って言うのは簡単だけど、それの及ぼす影響となると分からないものだ。

それともう一つ。OrderByの引数は比較関数ではなくキーセレクターにすぎないのでちゃんとシャッフルされる、とか言ったりなどした私ですが、そうじゃなくてシャッフルの精度はランダムの範囲に影響されるね(実際上は問題ないとしても)、という話が。完全に頭から抜け落ちていて、かつ、全くもってその通りで恥ずかしかったりしたのですが確認できてよかったです、感謝。

OrderByのComparison

全然使わないけどOrderByの第二引数。

class MyClass
{
    public int Hoge { get; set; }
    public int Fuga { get; set; }
}
static void Main(string[] args)
{
    var array = new[] { new MyClass(), new MyClass() };
    // コンパイルは通るけど例外出る
    var ordered = array.OrderBy(x => x).ToArray();
    // 上のはこれに等しい(当然、例外出る)
    array.OrderBy(x => x, Comparer<MyClass>.Default);
    // AnonymousComparerを使えばComparisonを使った比較が出来る
    array.OrderBy(x => x, (x, y) => x.Fuga - y.Hoge);
}

OrderByついでですが、キーセレクターは制約かかってないので別にIComparableじゃなくても動いたりします。そういう時はComparer<T>.Defaultが指定されることになって、例外出て死ぬだけです。意味ナイネ。

DescendingとThenByがあるので滅多に使わないであろう第二引数はIComparer。一々クラス作ってnewですってよ、C#らしくないですね。Comparisonじゃないなんて!大変ウザい。そんな人のためのAnonymousComparer。ラムダ式でIEqualityComparer/IComparerを作ることが出来ます。また、Linq標準演算子への拡張メソッドとしてOrderBy/ThenByのオーバーロードとしてComparisonが使えるようになります。便利ですね!是非使ってください、という宣伝。

Empty -> Sum

Empty.Sum()は0。言われてみれば当たり前といえば当たり前なのですが……。

// SumはAggregateで表現出来る
var sum = Enumerable.Range(1, 10).Aggregate((x, y) => x + y); // 55
// でもEmptyで例外出るから表現出来ない(キリッ
sum = Enumerable.Empty<int>().Sum(); // 0
sum = Enumerable.Empty<int>().Aggregate((x, y) => x + y); // 例外
// 実はseed与えればおk
sum = Enumerable.Empty<int>().Aggregate(0, (x, y) => x + y); // 0

SumやMax, Minなどは全てAggregateで表現出来ます。でも、Sumは空シーケンスの時はゼロ出すけど(MaxやAverageは例外)Aggregateを使うと例外が出てしまうので表現出来ない、とか言ったのですが0を最初に与えとけばいいよね、という話が。ぬお、そうでした!

発端はlinq.jsでこの問題(というかC#と互換が取れてないこと)に気づいたことで、linq.jsではAggregateでやってるため、AggregateはScan.LastだからScan.LastOrDefault(0)にするー、なんて考えてたんですが、初項0で済むというシンプルさを完全に失念。標準演算子外のメソッドを大量に用意してあるので、そっち側で解決しちゃおうとしてしまう姿勢は、ちょっと頭硬直化しちゃってる、全くもってよろしくない。

シャッフルの話といい、Aggregateの話といい、最近はLinqに慣れすぎて逆に見方が定型的になりすぎていると実感したので、少し気を引き締めないと。あ、で、そんなこんなでlinq.jsの空シーケンスでのSumの問題は次のリリースで直します。他にもバグがあったり(MemoizeAllが少しマズい)、加えたいことが数点あったりするので、もう少し先になりますが。

世の中の主流はまだVS2005ですか?

開発言語としてのJavaとC#を10の視点から比較
共通点が多いが、今後は違いが大きくなるかも
しかし近年のC#はLINQ(Language Integrated Query:言語統合クエリ)プロジェクトが重視されています。これはクエリ、集合操作、変換、および型推測などのデータ指向機能の多くを直接的にC#言語に統合しようとするものです。今後は違いがさらに大きくなっていくかもしれません。
プログラマが知っておきたいJavaと.NETの違い (3/4) - @IT

Linqは、VS2008出たのは3年前だよね(プレビュー版から言えばどれだけ前なのかしら)。今後は違いが大きくなるかも、じゃなくて既に違いは大きすぎるような。そして10の比較というけれど、最大の違いはデリゲートの有無では?特に、匿名メソッド/ラムダ式の有無。A.R.N [ Top > 書庫 > Microsoftの「Delegate」について ]にある、Javaには無名クラスがあるからdelegateは不要、とは10年以上前のSunの言で、さすがに10年以上も前のを持ち出してどうこう言ってもしょうがないのですが(比較対象に匿名メソッドないし)、価値観は移り変わっていくものなのだと思わずにはいられない。匿名クラスで代用出来るって、いやまあ出来なくもないのは分かりますがUglyすぎ。今、クロージャなんて不要、とか言ったらフルボッコなはず。

言語面で見ると、Java5から進化の足を止めている(そしてJava7延期しすぎ)ように見えるJavaと、ひたすら貪欲に(無節操に)取り込み続けるC#。スタート時には似たようなものだったとして、今はもうコードの見た目からして全然似てるようには見えない。Java畑の人は、今でもC#はJavaに似たようなもの、という認識なのかしら。 確かに、古典的に書けば似てますが……。そして、他の言語を考えれば、やっぱ似てるといえば似てるのですが。しかし……。ふむ。そろそろModern C# Designが出版されるべき。 Bart De Smetが書くC# 4.0 Unleashed には超期待。

Profile

Yoshifumi Kawai

Cysharp, Inc
CEO/CTO

Microsoft MVP for Developer Technologies(C#)
April 2011
|
July 2024

Twitter:@neuecc GitHub:neuecc

Archive