LINQ to random thoughts

長くて疲れる割にオチが微妙なネタが終わったので小ネタをいくつか.
菊池さんの「LINQでリバーシ」シリーズを読みながら.

それ Any で

ほむほむ、吸収中

hoge.Count()>0 は Any() でいいのか。

みんな一度は言われる「それ Any で」.

Concat どう書く?

var q1 = ...
var q2 = ...

とあったとき,

var qA = Enumerable.Concat(q1, q2);
var qB = q1.Concat(q2);

のどっちが好きかという話.
私は対称性が良い前者を多用しているんですが,LINQ がオーバーロードと Extension Method の合わせ技であることを思い出せば実は後者の方がユニバーサル.LINQ to Object では同じでも,LINQ to SQL では大違い.
いやまあ q1 ++ q2 とか中置演算子で書ければ楽でいいんですがね.

IEnumerable<T> の省略記法

やっぱり省略表記欲しいなぁ.

もし C# が 2.0 の段階で初めてリリースされていれば,IEnumerable<T> は何か省略記法があってもよかったんじゃないかと思います.Nullable<T> が T? と書けるように.配列が T [ ] なので,例えば T [..] みたいなのはどうでしょうかね.

今から入れたら混乱の元でしょうけど.
独自言語を作るときは考慮したいですな.

IDisposable.Dispose はもはやインフラ

public static void WriteSequence<T>(this TextWriter writer, IEnumerable<T> source, string separator)
{
    IEnumerator<T> sourceIterator = source.GetEnumerator();
    bool hasNext = sourceIterator.MoveNext();
    while (hasNext)
    {
        writer.Write(sourceIterator.Current.ToString());
        hasNext = sourceIterator.MoveNext();
        if (hasNext) writer.Write(separator);
    }
}

using が抜けてますな.もはや IDisposable.Dispose はインフラ.

冒頭にも書いたように、.NET 2.0 になって、IDisposable を取り巻く環境に変化があらわれている。VisualBasic に Using ステートメントが追加されたり、イテレータの実装オブジェクトに IDisposable を利用した状態処理が自動的に実装されたり、C++ へのファイナライザ構文の追加と共にデストラクタコードが IDisposable.Dispose() メソッドとして実装されるなど、アンマネージドリソースと無縁な場所で、多くの記事などで解説される「本質的な IDisposable の目的」としての利用がどんどんと増える傾向になっている。

ついでに先日紹介した (id:NyaRuRu:20080131:p1) IEnumerator<T> フックでも書けそうなので挑戦.

using Achiral;
using Achiral.Extension;

public static IEnumerable<T> TraceSequence<T>(this IEnumerable<T> source, TextWriter writer, string separator)
{
    var isFirst = true;
    return source.Hook(new EnumeratorHook<T>
    {
        OnAfterMoveNext = (enumerator, hasNext) =>
        {
            if (hasNext)
            {
                if (isFirst)
                {
                    isFirst = false;
                }
                else
                {
                    writer.Write(separator);
                }
                writer.Write(enumerator.Current.ToString());
            }
        }
    });
}

static void Main(string[] args)
{
    Enumerable.Range(0, 100)
              .TraceSequence(Console.Out, ", ")
              .ForEach(_ => { });
}

結果.

0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99続行するには何かキーを押してください . . .

Enumerable.Range

C# で,

var q = 1.UpTo(100);

みたいなのが書けるのは分かってても自分でやることは無いだろう……と思っていたけど最近 Enumerable.Range(1, 100) を書くのが面倒で……いつか誘惑に負ける日が来るかも.

for と while ってもう要らないよね

いやまあ全然要らないってわけじゃないですけど.
C# 3.0 になってライブラリコード以外では for も while も要らない子になった気がする今日この頃.yield もライブラリ以外ではもう要らないかも.

static IEnumerable<byte> Players()
{
    while (true)
    {
        yield return black;
        yield return white;
    }
}

無限リピートがあればあとは何とかなりそう.

using System;
using System.Linq;

using Achiral;
using Achiral.Extension;

static class Program
{
    static void Main(string[] args)
    {
        const byte black = 1;
        const byte white = 2;
        var players = Make.Repeat(new[] { black, white })
                          .SelectMany(pair => pair);

        players.Take(10).ConsoleWriteLine();
    }
}

何とかなった.
あるいは Cycle を作ってみる?

public static IEnumerable<T> Cycle<T>(params T[] items)
{
    while (true)
    {
        foreach (var item in items)
        {
            yield return item;
        }
    }
}
static void Main(string[] args)
{
    const byte black = 1;
    const byte white = 2;
    var players = Cycle(black, white);

    players.Take(10).ConsoleWriteLine();
}

あるいはシーケンスを交互に織り込むメソッドってのも.

public static IEnumerable<T> Alternate<T>(IEnumerable<T> source1, IEnumerable<T> source2)
{
    using (IEnumerator<T> enumerator1 = source1.GetEnumerator(),
                          enumerator2 = source2.GetEnumerator())
    {
        while (true)
        {
            if (!enumerator1.MoveNext()) yield break;
            yield return enumerator1.Current;
            if (!enumerator2.MoveNext()) yield break;
            yield return enumerator2.Current;
        }
    }
}

static void Main(string[] args)
{
    const byte black = 1;
    const byte white = 2;
    var players = Alternate(Make.Repeat(black), Make.Repeat(white));

    players.Take(10).ConsoleWriteLine();
}

アプリケーション固有のメソッドで yield を書く可能性はかなり低くなったんじゃないでしょうかね.