.NET Reactive Framework メソッド探訪第二回:AnonymousEnumerable

予定は常に変更されるもの、というわけで、今回はAnonymousEnumerableとAnonymousEnumeratorを見たいと思います。表に出てこない、internalのクラスな上に、内部でも全然使われていないので、見る必要はあんまりない。のですが、これと対になるAnonymousObservableとAnonymousObserverを見るにあたって、先に慣れ親しんだIEnumerable/Enumeratorで考えたほうが分かりやすかったので、これを先に考えます。

class AnonymousEnumerable<T> : IEnumerable<T>
{
    private Func<IEnumerator<T>> getEnumerator;
    public AnonymousEnumerable(Func<IEnumerator<T>> getEnumerator)
    {
        // 以下略
}

class AnonymousEnumerator<T> : IEnumerator<T>
{
    private Func<T> current;
    private Action dispose;
    private Func<bool> moveNext;

    public AnonymousEnumerator(Func<bool> moveNext, Func<T> current, Action dispose)
    {
        this.moveNext = moveNext;
        this.current = current;
        this.dispose = dispose;
    }

    public T Current
    {
        get { return this.current(); }
    }

    public bool MoveNext()
    {
        return this.moveNext();
    }
    
    // 以下略
}

Reflectorで見ちゃってるので、一部だけ(ライセンス!)。ただ、どれも一行なので見るまでもなく何をやってるのか想像付くと思います。 お馴染みのインターフェイスを実装しているだけですが、その実装を全てコンストラクタで受ける関数に任せています。ようするにこれはどういうことなのかというと、実例としてEnumerable.RepeatとSelectをAnonymousEnumerableで実装してみるとこうなります。

public static class Enumerable
{
    public static IEnumerable<T> Repeat<T>(T element, int count)
    {
        return new AnonymousEnumerable<T>(() =>
        {
            var index = 0;
            var current = default(T);
            return new AnonymousEnumerator<T>(
                () => // MoveNext
                {
                    if (index == 0) current = element;
                    return (index++ < count);
                },
                () => current, // Current
                () => { } // Dispose
            );
        });
    }
    
    // 実際のSelectは<TSource,TResult>ですが、都合により略
    public static IEnumerable<T> Select<T>(this IEnumerable<T> source, Func<T, T> selector)
    {
        return new AnonymousEnumerable<T>(() =>
        {
            var enumerator = source.GetEnumerator();
            return new AnonymousEnumerator<T>(
                () => enumerator.MoveNext(),
                () => selector(enumerator.Current),
                () => enumerator.Dispose()
            );
        });
    }
}

勿論、普通はyieldを使えばいいわけですが、もしyieldがなければ、こういう形で実装するのが簡潔でベスト、に見える。クロージャでコンパイラにクラス生成を任せているわけですねー。外部イテレータですが、外部のクラスに分割せず中に書けるため、すっきり分かりやすい。この発想はあったけどC#でやるという発想はなかったわ、というわけで結構感動しました。

そうそう、この仕組みはlinq.jsと丸っきり同じなのです。感動ってのは、私の基本方針は間違ってなかったんだ!の裏付けでの喜びなので不純です。そのJavaScriptでの実装はこんな感じ。

Linq.Object = function(getEnumerator)
{
    this.GetEnumerator = getEnumerator;
}

Linq.Enumerator = function(moveNext)
{
    this.Current = null;
    this.MoveNext = moveNext;
}

Repeat: function(element, count)
{
    return new Linq.Object(function()
    {
        var index = 0;
        return new Linq.Enumerator(function()
        {
            if (this.Current == null) this.Current = element;
            return (index++ < count);
        });
    });
}

Select: function(selector)
{
    var source = this;
    selector = Linq.Utils.CreateFunctor(selector);

    return new Linq.Object(function()
    {
        var enumerator = source.GetEnumerator();
        var index = 0;

        return new Linq.Enumerator(function()
        {
            if (enumerator.MoveNext())
            {
                this.Current = selector(enumerator.Current, index++);
                return true;
            }
            else
            {
                return false;
            }
        });
    });
}

スコープが若干違うかなって感じですが、大体同じです。(Linq.ObjectはLinq.Enumerableに名前変えよう……)。JavaScriptだからこそ、yieldがなくてもスッキリ定義出来る!と思っていただけに、そっかあ、そういう手を使えば良かったのかと少しショックだったりして。ほんとC#は柔軟な言語で、むしろもうJavaScriptよりもLightWeightだよ!

さて、次回はAnonymousObservableとAnonymousObserverを見てみることにします。勿論、予定は未定です。ていうかSubscribeはどうした、というと、Subscribeの中身はAnonymousObserverなので全然横道にそれてません、大丈夫です、まだ一直線です。

Profile

Yoshifumi Kawai

Cysharp, Inc
CEO/CTO

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

Twitter:@neuecc GitHub:neuecc

Archive