Продолжение выполнения внешнего цикла

Когда у вас есть вложенный цикл, иногда возникает желание перейти к следующей итерации внешнего цикла, а не внутреннего. Например, у нас есть последовательность критериев и последовательность элементов, и мы хотим определить, отвечает ли какой-либо из элементов всем критериям:

match = null;
foreach(var item in items)
{
foreach(var criterion in criteria)
{
if (!criterion.IsMetBy(item))
{
// В дальнейших проверках нет никакого смысла, этот элемент не подходит.

// Мы хотим перейти к следующей итерации внешнего цикла. Но как?

    }
  }
  match = item;
  break;
}

Существует несколько способов добиться этого. Ниже представлены несколько таких способов в порядке увеличения «изящности»:

Метод №1 (ужасный): В цикле, оператор “continue” по-сути является оператором “goto”, который передает управление вниз цикла, непосредственно перед проверкой условия цикла и началом выполнения следующей итерации. (Очевидно, что когда вы произносите “goto” в виде “continue”, мысли толпы типа «любые применения операторов “goto” является злом на все времена» не будут вас досаждать.) Вы, конечно, можете использовать оператор goto явно:

match = null;
foreach(var item in items)
{
  foreach(var criterion in criteria)
  {
    if (!criterion.IsMetBy(item))
    {
      goto OUTERCONTINUE;
    }
  }
  match = item;
  break;
OUTERCONTINUE:
  ;
}

Метод №2 (лучше): Когда я вижу вложенный цикл, я практически всегда рассматриваю возможность преобразования внутреннего цикла в отдельный метод.

match = null;
foreach(var item in items)
{
  if (MeetsAllCriteria(item, critiera))
  {
    match = item;
    break;
  }
}

где тело метода MeetsAllCriteria достаточно очевидно:

foreach(var criterion in criteria)
  if (!criterion.IsMetBy(item))
    return false;
return true;

Метод №3 (потрясающий): второй пример показывает «механизм» работы кода, но скрывает его назначение. Цель кода – дать ответ на вопрос: «какой элемент (если такой вообще есть) первым отвечает всем критериям?» Если значение кода соответствует этому вопросу, тогда следует написать код, из которого это следовало бы явно:

var matches = from item in items
              where criteria.All(
                criterion=>criterion.IsMetBy(item))
              select item;
match = matches.FirstOrDefault();

Т.е. найти в items элементы, отвечающие всем критериям, и если такие элементы есть, получить первый из них. Этот код теперь говорит о том, что он делает и, кроме того, из-за отсутствия циклов необходимость в операторе “continue” отпадает вовсе!

В LINQ мне больше всего нравится то, что он позволяет совершенно по-иному думать о решаемой задаче. Сегодня, когда я пишу цикл, я останавливаюсь и прежде чем набрать “for” я думаю о следующих двух вещах:

* Описал ли я где-то то, что делает этот код семантически, или код подчеркивает используемые механизмы ценой утаивания семантики?

* Существует ли способ изобразить решение задачи, как результат запроса к некоторому набору данных, а не как результат нескольких шагов в процедурном стиле?

Оригинал статьи