views:

312

answers:

1

Can anyone tell me why the following code doesn't work the way I expect? I'm trying to write an IEnumberable wrapper around a StreamReader, but when I use ElementAt on it, it reads sequential characters from the stream regardless of the index I pass to ElementAt.

The file "test.txt" contains "abcdefghijklmnopqrstuvwxyz". I would expect the output to be:

aaaaaaaaaaaaaaaaaaaaaaaaaaa
bbbbbbbbbbbbbbbbbbbbbbbbbbb
...

Instead I get

abcdefghijklmnopqrstuvwxyz

and ArgumentOutOfRangeException gets called, even though the only index I've passed to ElementAt so far is 0.

MSDN says:

If the type of source implements IList<(Of <(T>)>), that implementation is used to obtain the element at the specified index. Otherwise, this method obtains the specified element.

Should that read "obtains the next element"? If so, that kinda defeats the purpose of lazy lists...

    static IEnumerable<char> StreamOfChars(StreamReader sr)
    {
        while (!sr.EndOfStream)
            yield return (char)sr.Read();
    }


    static void Main(string[] args)
    {
        using (StreamReader sr = new StreamReader("test.txt"))
        {
            IEnumerable<char> iec = StreamOfChars(sr);

            for (int i = 0; i < 26; ++i)
            {
                for (int j = 0; j < 27; ++j)
                {
                    char ch = iec.ElementAt(i);
                    Console.Write(ch);
                }
                Console.WriteLine();
            }
        }
    }
+1  A: 

Yes, the MSDN documentation should read "obtains the next element". Obviously your iec object does not implement IList and only implements IEnumerable, so it is impossible to do a random read (without of course an extension method or something which uses the enumerator to reach the random index). Forward-only read is the only available option.

Let's look at the disassembled code for the ElementAt method. Clearly, if the source collection does not implement IList, we retrieve the current item in the enumerator:

public static TSource ElementAt<TSource>(this IEnumerable<TSource> source, int index)
{
    TSource current;
    if (source == null)
    {
        throw Error.ArgumentNull("source");
    }
    IList<TSource> list = source as IList<TSource>;
    if (list != null)
    {
        return list[index];
    }
    if (index < 0)
    {
        throw Error.ArgumentOutOfRange("index");
    }
    using (IEnumerator<TSource> enumerator = source.GetEnumerator())
    {
    Label_0036:
        if (!enumerator.MoveNext())
        {
            throw Error.ArgumentOutOfRange("index");
        }
        if (index == 0)
        {
            current = enumerator.Current;
        }
        else
        {
            index--;
            goto Label_0036;
        }
    }
    return current;
}
Rex M