Я бы сделал шаг назад сюда. Вы концентрируетесь на придирчивых деталях кода, но упускаете общую картину. Давайте посмотрим на один из ваших примеров циклов:
int offset = 0;
while(true)
{
Record r = Read(offset);
if(r == null)
{
break;
}
// do work
offset++;
}
В чем смысл этого кода? Смысл в том, чтобы «сделать некоторую работу с каждой записью в файле». Но это не то, на что похож код . Код выглядит как «поддерживать смещение. Откройте файл. Введите цикл без конечного условия. Прочитайте запись. Проверьте на ничтожность». Все это, прежде чем мы приступим к работе! Вопрос, который вы должны задать: « Как я могу сделать внешний вид этого кода соответствующим его семантике? » Этот код должен быть:
foreach(Record record in RecordsFromFile())
DoWork(record);
Теперь код читается как его намерение. Отделите свои механизмы от вашей семантики . В своем исходном коде вы смешиваете механизм - детали цикла - с семантикой - работу, проделанную с каждой записью.
Теперь мы должны реализовать RecordsFromFile()
. Каков наилучший способ реализации этого? Какая разница? Это не тот код, на который будет смотреть кто-либо. Это базовый код механизма и его длина в десять строк. Напиши как хочешь. Как насчет этого?
public IEnumerable<Record> RecordsFromFile()
{
int offset = 0;
while(true)
{
Record record = Read(offset);
if (record == null) yield break;
yield return record;
offset += 1;
}
}
Теперь, когда мы манипулируем лениво вычисляемой последовательностью записей, возможны все виды сценариев:
foreach(Record record in RecordsFromFile().Take(10))
DoWork(record);
foreach(Record record in RecordsFromFile().OrderBy(r=>r.LastName))
DoWork(record);
foreach(Record record in RecordsFromFile().Where(r=>r.City == "London")
DoWork(record);
И так далее.
Каждый раз, когда вы пишете цикл, спрашивайте себя: «Этот цикл читается как механизм или как смысл кода?» Если ответ «как механизм», попробуйте переместить этот механизм в его собственный метод и напишите код, чтобы значение стало более видимым.