2014-04-01 12 views
7

Załóżmy, że mam IEnumerable<T> i chcę wziąć pierwszy element i przekazać pozostałe elementy do innego kodu. Mogę uzyskać pierwsze elementy z wykorzystaniem , używając Take(n), ale jak mogę uzyskać dostęp do pozostałych elementów bez powodowania ponownego uruchomienia wyliczenia?Czy mogę wziąć pierwsze n elementów z wyliczenia, a następnie użyć reszty wyliczenia?

Załóżmy na przykład, że mam metodę ReadRecords, która akceptuje rekordy w pliku CSV jako IEnumerable<string>. Załóżmy teraz, że w tej metodzie chcę odczytać pierwszy rekord (nagłówki), a następnie przekazać pozostałe rekordy do metody ReadDataRecords, która również zajmuje IEnumerable<string>. Tak:

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

    ReadDataRecords(dataRecords); 
} 

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

Gdybym był gotów ponownie rozpocząć wyliczanie, wtedy będę mógł używać dataRecords = records.Skip(1). Nie chcę jednak ponownie go uruchamiać - w rzeczywistości może on nie zostać ponownie uruchomiony.

Czy istnieje sposób na pobranie pierwszych rekordów, a następnie pozostałych rekordów (innych niż poprzez odczyt wszystkich wartości do nowej kolekcji i ponowne ich wyliczenie)?

+2

Jeśli chcesz tylko pominąć jedną, to jaka jest wielka sprawa? Po prostu wyliczając ponownie i pomijając jeden? – Paparazzi

+0

Jest fajne rozwiązanie w MoreLinq, jeśli potrzebujesz [wsparcia batching] (http://stackoverflow.com/questions/13731796/create-batches-in-linq). Pojawiło się również kilka pytań dotyczących specjalnego pierwszego/ostatniego elementu obudowy. –

+0

@Blam: Jeśli wyliczenie zostało utworzone przez wyliczenie czegoś w pamięci, takiego jak wyliczenie pozycji na liście, to tak. Ale co, jeśli wyliczenia nie można ponownie uruchomić lub jest bardzo kosztowne w wykonaniu? Nie rozumiem, dlaczego konieczne jest posiadanie dwóch wyliczeń, gdy chcę pobrać tylko jeden przedmiot dokładnie jeden raz. –

Odpowiedz

13

To jest ciekawe pytanie, myślę, że można użyć obejście takiego, zamiast korzystania LINQ uzyskać wyliczający i używać go:

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; 
    } 
} 

Aktualizacja: Zgodnie z Twojego komentarza tutaj jest bardziej rozszerzona sposób to zrobić:

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; 
     } 
    } 
} 

Zastosowanie:

private static void ReadCsv(IEnumerable<string> records) 
{ 
    var headerRecord = records.GetRecords(); 
    var dataRecords = records.GetRecords(); 
} 
+0

@TyCobb dzięki za edycję. –

+0

Brak prob. Dzięki za przypomnienie mi, że Enumeratory wciąż istnieją na szczycie pięknej składni, która wciąż dodaje się do frameworka i wciąż może być dobra do zaprzęgania od czasu do czasu. +1 – TyCobb

+0

Dzięki. Przypuszczam, że mógłbym utworzyć klasę ogólnego przeznaczenia, aby uzyskać resztę dla IEnumerable . BTW, dlaczego test zerowy w pętli? Wydaje się zbędne ... –

3

Tak, użyj IEnumerator z IEnumerable i możesz zachować pozycję poprzez wywołania metod;

Prosty przykład;

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); 
    } 
} 
+0

Dzięki, ale naprawdę chciałem móc wywołać metodę, która pobiera IEnumerable, aby zachować ładniejszy interfejs API. Przepraszam, że nie wyjaśniłem tego bardzo wyraźnie. –