Я б тут зробив крок назад. Ви зосереджуєтесь на прискіпливих деталях коду, але пропускаєте більшу картину. Давайте подивимось на один із ваших прикладів циклів:
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);
І так далі.
Щоразу, коли ви пишете цикл, запитайте себе: "Чи читається цей цикл як механізм чи як значення коду?" Якщо відповідь "як механізм", то спробуйте перенести цей механізм на власний метод і введіть код, щоб зробити сенс більш видимим.