Out of Memory

本ブログは更新を停止しました。Aerieをよろしくお願いいたします。

目次

Blog 利用状況

ニュース

2009年3月31日
更新を停止しました。引き続きAerieを御愛顧くださいませ。
2009年2月3日
原則としてコメント受付を停止しました。コメントはAerieまでお願いいたします。
詳細は2月3日のエントリをご覧ください。
2008年7月1日
Microsoft MVP for Developer Tools - Visual C++ を再受賞しました。
2008年2月某日
MVPアワードがVisual C++に変更になりました。
2007年10月23日
blogタイトルを変更しました。
2007年7月1日
Microsoft MVP for Windows - SDKを受賞しました!
2007年6月20日
スキル「ニュース欄ハック」を覚えた!
2006年12月14日
記念すべき初エントリ
2006年12月3日
わんくま同盟に加盟しました。

カレンダー

中の人

αετο? / aetos / あえとす

シャノン? 誰それ。

顔写真

埼玉を馬鹿にする奴は俺が許さん。

基本的に知ったかぶり。興味を持った技術に手を出して、ちょっと齧りはするものの、それを応用して何か形にするまでは及ばずに飽きて放り出す人。

書庫

日記カテゴリ

列挙にまつわるエトセトラ

ネタ元→カブるべからず(2)

要素を列挙可能なコレクションに対し、要素の1個目と2個目以降で異なる処理を行いたいとする。
foreach では問答無用で全要素を列挙してしまうので適さない。
かといって、1個目か2個目以降かを判別するためだけにフラグを持たせたり、添字アクセスするのも美しくない。

そうだ、「2個目以降だけ列挙する列挙子」を開発したらどうだろう、というのが発端。
こんな感じのものを考えていたわけだ。

class RangeEnumerator<T> : IEnumerable<T>, IEnumerator<T>
{
    private readonly IEnumerator<T> _baseEnumerator;
    private readonly int _first;
    private readonly int _count;
    private int _currentIndex;
    // baseEnumerator の first 番目(ゼロベース)から、最大 count 個の要素を列挙する
    public RangeEnumerator( IEnumerable<T> baseCollection, int first, int count )
    {
        this._baseEnumerator = baseCollection.GetEnumerator();
        this._first = first;
        this._count = count;
        Reset();
    }
    public RangeEnumerator( IEnumerable<T> baseCollection, int first ) : this( baseCollection, first, -1 )
    {
    }
    public RangeEnumerator( IEnumerable<T> baseCollection ) : this( baseCollection, 0 )
    {
    }
    public T Current
    {
        get
        {
            return this._baseEnumerator.Current;
        }
    }
    public void Dispose()
    {
        this._baseEnumerator.Dispose();
    }
    object IEnumerator.Current
    {
        get
        {
            return ( this._baseEnumerator as IEnumerator ).Current;
        }
    }
    public bool MoveNext()
    {
        ++this._currentIndex;
        if( this._count != -1 && ( this._first + this._count ) <= this._currentIndex )
        {
            return false;
        }
        return this._baseEnumerator.MoveNext();
    }
    public void Reset()
    {
        this._baseEnumerator.Reset();
        this._currentIndex = -1;
        for( int i = 0; i < this._first; ++i )
        {
            if( ! MoveNext() )
            {
                throw new ArgumentOutOfRangeException();
            }
        }
    }
    public IEnumerator<T> GetEnumerator()
    {
        return this;
    }
    IEnumerator IEnumerable.GetEnumerator()
    {
        return ( this as IEnumerator );
    }
}

IEnumerable について調べているうちに、いくつかの興味深い事実を発見した。

まずは「Enumerable パターン」の存在である。
C# プログラミング ガイド:foreach を使用してコレクション クラスにアクセスするによれば、C# では、foreach と互換性を保つ目的で、コレクション クラスを IEnumerable と IEnumerator から継承することは厳密には必要ありません。要求された GetEnumerator、MoveNext、Reset、Current の各メンバがクラスに含まれている限り、コレクション クラスで foreach を使用できます。だそうだ。おそらく、内部ではリフレクションを使っているのであろう。
(IEnumrable / IEnumerator を実装する場合を含み、)これらのメソッドを実装することを「Enumerable パターン」と呼ぶ(ちなみに、GetEnumerator はインスタンスメソッドでなければならない。静的メソッドだとエラーになる)。

また、yield return の戻り値(というのも変だが)としては、IEnumerable<T> と IEnumerator<T> のどちらも書けるというのも初めて知った。

class Test
{
    public IEnumerator<int> Hoge()
    {
        yield return 1;
        yield return 2;
        yield return 3;
    }
    public IEnumerable<int> Moge()
    {
        yield return 1;
        yield return 2;
        yield return 3;
    }
}

これはどちらも合法的なコードである。
しかし、大きな違いが一つある。IEnumerator<T> を返す方は、foreach できないのだ。

Test t = new Test();
// これはOK
foreach( int i in t.Moge() )
{
    Console.WriteLine( i );
}
/* これはダメ
foreach( int i in t.Hoge() )
{
    Console.WriteLine( i );
}
*/
// これならOK
using( IEnumerator<int> e = t.Hoge() )
{
    while( e.MoveNext() )
    {
        Console.WriteLine( e.Current );
    }
}

最後に、yield return で得た IEnumerator を Reset するべからずという問題を取り上げよう。
上のコードの3つ目のケースを、こんな風に書き換えると、

// これはダメ
using( IEnumerator<int> e = t.Hoge() )
{
    e.Reset();
    while( e.MoveNext() )
    {
        Console.WriteLine( e.Current );
    }
}

e.Reset で NotSupportedException が発生してしまう。これはドキュメントのバグである。

IEnumerator<T> は Reset の定義を持たない。これは非ジェネリックな IEnumerator から継承されるメソッドである。
そして、IEnumerator のドキュメントにも、IEnumerator.Reset のドキュメントにも、このメソッドが NotSupportedException を発生し得るとは書かれていない。
IEnumerator.Reset が投げ得る例外は、唯一 InvalidOperationException であるはずだ。

しかし、最新のオフライン版のドキュメントには、しれっとこんなことが書かれている。

Reset メソッドは COM 相互運用性のために提供されています。これは必ずしも実装する必要はありません。代わりに、実装側で単に NotSupportedException をスローできます。

こういうことをされるとまったく困る。
実際、あるメソッドないしはプロパティが投げ得る例外について、信頼に足るドキュメントは存在しないのが現状である。

余談だが、IEnumerable.Reset を呼ぶ際、列挙子のもとになるコレクションが変更されていると InvalidOperationException が投げられることは明記されているが、COM 相互運用の IEnumXXX インターフェイスの中には、Reset メソッドを呼ぶことで列挙子を作り直し、母体となるコレクションの変更を反映して列挙子を有効化するものもある。

ついでに、ドキュメントのミスをもう一つ指摘しておこう。
IEnumerator<T> のドキュメントには、MoveNext がコレクションの末尾を過ぎると、列挙子はコレクションの最後の要素の後ろに配置され、MoveNext は false を返します。列挙子がこの位置にある場合、以降、MoveNext を呼び出しても false が返されます。MoveNext への最後の呼び出しで false が返された場合は、Current が未定義です。Current を、再度、コレクションの最初の要素に設定することはできません。列挙子の新しいインスタンスを作成する必要があります。とある。…あれ? Reset は?
IEnumerator のドキュメントには、しっかり、Current をコレクションの最初の要素に再び設定するには、Reset を呼び出してから、MoveNext を呼び出します。と書かれているが…?
確かに、IEnumerator<T> 自体は Reset を持っていないが、IEnumerator を継承しているのだがら、ここでは Reset に言及して然るべきである。というか、IEnumerable<T> 自体に Reset が無いのが言及しない理由なら、MoveNext については書かれているのはおかしいだろう。

なお、この記事を書くにあたっては、窓際プログラマーの独り言を参考にした。

投稿日時 : 2007年7月10日 15:31

Feedback

# re: 列挙にまつわるエトセトラ 2007/07/10 15:56 επιστημη

んー、yieldのサポートと共にドキュメントがぐだぐだになったって感じねー ^^;

yieldは巻き戻せないIEnumeratorを生成するっきゃないから。

# re: 列挙にまつわるエトセトラ 2007/07/11 20:04 NyaRuRu

>カブるべからず(2)
それ,LINQ の Distinct で (以下略)

>「2個目以降だけ列挙する列挙子」を開発
それ,LINQ の Skip で (以下略)

LINQ 関係は,中の人たちがとんでもなく先に進んでいるので,やたら先が長いですよ.
多分上で議論されている内容は 2005 年代ぐらいにホットだった部分です.
blog 界での話題の変遷は多分こんな感じですかねぇ.

1. yield を使った一発芸 (2005 年代?)
2. IEnumerable<T> -> IEnumerable<K> へのライブラリ一般化 (LINQ; 2005 年から 2006 年, 「C# に monad comprehensions 入れたよ」 by Erik Meijer)
3. Expression Tree を使ったメタプログラミングによる LINQ to Hogehoge ブーム (2006 年頃, Don Box の影)
4. C# で lambda calculus (2006 年末から 2007 年初頭.みんな Y Combinator 大好き)
5. (今) 謎.(DLR と AST?)

# re: 列挙にまつわるエトセトラ 2007/07/12 10:27 シャノン

LINQ 関係だけ突出してしまっているような…
.NET 3.5 の新機能は全部 LINQ 関係と言ってもいいと思うので突出するのはある意味当然ですが、なんかその突出ぶりが、他のところとの整合性を欠いて突っ走っているような印象を受ける。
まぁ、過去のものを切り捨てることなく発展し続けるならばバランスが崩れるのは必然かもしれないけれど、どこかでリセットしたくなる俺がいるww

# re: 列挙にまつわるエトセトラ 2007/07/12 12:42 NyaRuRu

まあ DLR とか AST は,LINQ 用に導入された Expression Tree からのスピンアウト作品みたいなものなので,その意味でもう中の人たちは Post-LINQ な感じですね.
一方で今頃好評の @IT の C# 2.0 入門記事とかを見ていると,世間での「LINQ 入門元年」は 2009 年ぐらいになりそうですけど.

# yield return ?????? Reset() ???????????? | ??????????????? C# ????????? 2011/09/03 23:05 Pingback/TrackBack

yield return ?????? Reset() ???????????? | ??????????????? C# ?????????

# FxJLAGhTvVvPuKPUOhR 2011/12/22 21:03 http://www.discreetpharmacist.com/

JSKlZw The material is on the five plus. But there is a minus! My internet speed 56kb/sek. The page was loading for about 40 seconds!...

# IZNFTychuuMLNXYWq 2011/12/27 19:28 www.buytelescopes.com/

I was looking for the report in Yandex and suddenly came across this page. I found a little information on my topic of my report. I would like more, and thanks for that..!

# uxnpBpAAmbdKUvtT 2011/12/29 20:50 http://www.healthinter.org/health/page/lamisil.php

It's straight to the point! You could not tell in other words! :D

# fpHXVrRoHNWy 2012/01/07 9:18 http://www.luckyvitamin.com/m-186-metagenics

I must admit, the webmaster is a cool guy..!

タイトル  
名前  
Url
コメント