2016-07-08 7 views
0

Я родом из мира Python и пытаюсь создать «генераторный» метод в C#. Я разбираю файл в кусках определенного размера буфера и хочу только читать и хранить следующий фрагмент за один раз и давать его в цикле . Вот то, что я до сих пор (упрощенное доказательство концепции):C# «Генератор» Метод

class Page 
{ 
    public uint StartOffset { get; set; } 
    private uint currentOffset = 0; 

    public Page(MyClass c, uint pageNumber) 
    { 
     uint StartOffset = pageNumber * c.myPageSize; 

     if (StartOffset < c.myLength) 
      currentOffset = StartOffset; 
     else 
      throw new ArgumentOutOfRangeException("Page offset exceeds end of file"); 

     while (currentOffset < c.myLength && currentOffset < (StartOffset + c.myPageSize)) 
      // read data from page and populate members (not shown for MWE purposes) 
      . . . 
    } 
} 

class MyClass 
{ 
    public uint myLength { get; set; } 
    public uint myPageSize { get; set; } 

    public IEnumerator<Page> GetEnumerator() 
    { 
     for (uint i = 1; i < this.myLength; i++) 
     { 
      // start count at 1 to skip first page 
      Page p = new Page(this, i); 
      try 
      { 
       yield return p; 
      } 
      catch (ArgumentOutOfRangeException) 
      { 
       // end of available pages, how to signal calling foreach loop? 
      } 
     } 
    } 
} 

Я знаю, что это не является совершенным, так как это рабочий пример минимального (не позволяют многим из этих свойств необходимо установить публично, но для сохраняя это просто, я не хочу вводить частные члены и свойства).

Однако, мой главный вопрос: как я могу позволить вызывающему абоненту обходить MyClass с помощью инструкции foreach, знать, что больше нет элементов, оставшихся до цикла? Есть ли исключение, которое я указываю, что элементов нет?

+0

Вы просто прекратите сдавать предметы, как в Python.При этом вы должны сделать метод, который возвращает 'IEnumerable '; перечисления легче потреблять. – poke

+1

'IEnumerator .MoveNext' - это то, что говорит вызывающему абоненту прекратить итерацию. Это выполняется для вас, когда вы используете 'yield return'. Если вы хотите явно остановиться, вы можете использовать 'yield break'. –

+0

@poke несоответствие - это моя ошибка в примере. Страница - это сделанная вещь для этого поста, BTreePage - это действительно то, что я возвращаю в своем реальном коде. Исправлена. – Dan

ответ

1

Как уже упоминалось в комментариях, вы должны использовать IEnumerable<T> вместо IEnumerator<T>. Перечислитель - это технический объект, который используется для перечисления чего-либо. Это что-то - во многих случаях - перечислимо.

У C# есть особые возможности для обработки перечислений. Наиболее заметно, вы можете использовать цикл foreach с перечислимым (но не перечислителем, хотя цикл фактически использует перечислитель перечислимого). Кроме того, перечисления позволяют использовать LINQ, что делает его еще более удобным для использования.

Таким образом, вы должны изменить свой класс, как это:

class MyClass 
{ 
    public uint myLength { get; set; } 
    public uint myPageSize { get; set; } 

    # note the modified signature 
    public IEnumerable<Page> GetPages() 
    { 
     for (uint i = 1; i < this.myLength; i++) 
     { 
      Page p; 
      try 
      { 
       p = new Page(this, i); 
      } 
      catch (ArgumentOutOfRangeException) 
      { 
       yield break; 
      } 
      yield return p; 
     } 
    } 
} 

В конце концов, это позволяет использовать его как это:

var obj = new MyClass(); 

foreach (var page in obj.GetPages()) 
{ 
    // do whatever 
} 

// or even using LINQ 
var pageOffsets = obj.GetPages().Select(p => p.currentOffset).ToList(); 

Конечно, вы должны также изменить имя метода для чего-то значимого. Если вы возвращаете страницы, GetPages может быть хорошим первым шагом в правильном направлении. Имя GetEnumerator является видом зарезервированных для типов, реализующих IEnumerable, где предполагается, что метод GetEnumerator возвращает перечислитель коллекции, представляемой объектом.

+0

Делает гораздо больше смысла. Спасибо! – Dan

0

Используйте инструкцию yield break;, чтобы закончить последовательность, которую генерирует ваш итератор.

1

два способа сделать это позволить выполнение кода достичь конца функции GetEnumerator или положить в yield break; в коде, это будет вести себя так же, как return; в функции, возвращаемой void.

Из прозвища звонящего перечислитель, возвращаемый с GetEnumerator(), начнет возвращать false за MoveNext(), вот как они говорят, что перечислитель выполнен.


Чтобы исправить ваши «не может дать значение внутри тела Ьгу блока с пунктом поймать» вы поместили TRY/поймать вокруг неправильной части кода, то execption будет выброшено на new не yield return. Ваш код должен выглядеть

public IEnumerator<Page> GetEnumerator() 
{ 
    for (uint i = 1; i < this.myLength; i++) 
    { 
     // start count at 1 to skip first page 
     Page p; 
     try 
     { 
      p = new Page(this, i); 
     } 
     catch (ArgumentOutOfRangeException) 
     { 
      yield break; 
     } 
     yield return p; 
    } 
} 
+0

это правильный ответ, но теперь у меня есть две проблемы;) - по-видимому, я не могу использовать выход внутри инструкции try. Arg – Dan

+0

Нечетный. Я получаю «Не могу дать значение внутри тела блока try с предложением catch» – Dan

+0

Да, [вы не можете этого сделать] (http://stackoverflow.com/q/346365/216074). Просто запишите значение в переменной и выпустите его после try/catch. – poke