8

Suppose I have an IEnumerable<T>, and I want to take the first element and pass the remaining elements to some other code. I can get the first n elements using Take(n), but how can I then access the remaining elements without causing the enumeration to re-start?

For example, suppose I have a method ReadRecords that accepts the records in a CSV file as IEnumerable<string>. Now suppose that within that method, I want to read the first record (the headers), and then pass the remaining records to a ReadDataRecords method that also takes IEnumerable<string>. Like this:

void ReadCsv(IEnumerable<string> records)
{
    var headerRecord = records.Take(1);
    var dataRecords = ???

    ReadDataRecords(dataRecords);
}

void ReadDataRecords(IEnumerable<string> records)
{
    // ...
}

If I were prepared to re-start the enumeration, then I could use dataRecords = records.Skip(1). However, I don't want to re-start it - indeed, it might not be re-startable.

So, is there any way to take the first records, and then the remaining records (other than by reading all the values into a new collection and re-enumerating them)?

Gary McGill
  • 26,400
  • 25
  • 118
  • 202
  • 2
    If you only need to skip one then what is the big deal? Just enumerating again and skip one? – paparazzo Apr 01 '14 at 23:36
  • There is nice solution in MoreLinq if you need some [batching support](http://stackoverflow.com/questions/13731796/create-batches-in-linq). There are also several questions about special casing first/last element too. – Alexei Levenkov Apr 01 '14 at 23:49
  • 1
    @Blam: If the enumeration was created by enumerating something in-memory such as enumerating over the items in a list, then yes. But what if the enumeration can't be re-started, or is very expensive to execute? I don't see why it should be necessary to have two enumerations when I only want to fetch each item exactly once. – Gary McGill Apr 02 '14 at 20:15

2 Answers2

13

This is an interesting question, I think you can use a workaround like this, instead of using LINQ get the enumerator and use it:

private void ReadCsv(IEnumerable<string> records)
{
     var enumerator = records.GetEnumerator();
     enumerator.MoveNext();
     var headerRecord = enumerator.Current;
     var dataRecords = GetRemainingRecords(enumerator);
}

public IEnumerable<string> GetRemainingRecords(IEnumerator<string> enumerator)
{
    while (enumerator.MoveNext())
    {
       if (enumerator.Current != null)
            yield return enumerator.Current;
    }
}

Update: According to your comment here is more extended way of doing this:

public static class CustomEnumerator
{
    private static int _counter = 0;
    private static IEnumerator enumerator;
    public static IEnumerable<T> GetRecords<T>(this IEnumerable<T> source)
    {
        if (enumerator == null) enumerator = source.GetEnumerator();

        if (_counter == 0)
        {
            enumerator.MoveNext();
            _counter++;
            yield return (T)enumerator.Current;
        }
        else
        {
            while (enumerator.MoveNext())
            {
                yield return (T)enumerator.Current;
            }
            _counter = 0;
            enumerator = null;
        }
    } 
}

Usage:

private static void ReadCsv(IEnumerable<string> records)
{
     var headerRecord = records.GetRecords();
     var dataRecords = records.GetRecords();
}
Selman Genç
  • 100,147
  • 13
  • 119
  • 184
  • No prob. Thanks for reminding me that Enumerators still exist on top of all the lovely syntax that keeps getting added to the framework and can still be good to harness from time to time. +1 – TyCobb Apr 01 '14 at 23:43
  • Thanks. I suppose I could create an general purpose class to get the remainder for IEnumerable. BTW, why the null test in the loop? Seems redundant... – Gary McGill Apr 02 '14 at 07:06
  • If I had said I wanted to call a method ProcessHeaderRecords that takes IEnumerable and *then* ProcessDataRecords, again taking IEnumerable, what then? I can almost see how your answer could be extended, but there would have to be something in place to insist that exactly N records was read by ProcessHeaderRecords before anything was read by ProcessDataRecords, sine they would be sharing the same Enumerator... – Gary McGill Apr 02 '14 at 07:31
  • @GaryMcGill I understand what you mean, see my second solution.I think it is doing exactly what you want. – Selman Genç Apr 02 '14 at 11:59
  • 1
    It looks like `_counter` (should be a `bool`) is redundant with `enumerator` being `null`. – NetMage Nov 17 '17 at 20:48
3

Yes, use the IEnumerator from the IEnumerable, and you can maintain the position across method calls;

A simple example;

public class Program
{
    static void Main(string[] args)
    {
        int[] arr = new [] {1, 2, 3};
        IEnumerator enumerator = arr.GetEnumerator();
        enumerator.MoveNext();
        Console.WriteLine(enumerator.Current);
        DoRest(enumerator);
    }

    static void DoRest(IEnumerator enumerator)
    {
        while (enumerator.MoveNext())
            Console.WriteLine(enumerator.Current);
    }
}
RJ Lohan
  • 6,497
  • 3
  • 34
  • 54
  • Thanks, but I really wanted to be able to call a method that takes IEnumerable so as to preserve a nicer API. Sorry I didn't make that very clear. – Gary McGill Apr 02 '14 at 07:02