Перезапустить цикл foreach в С#?

Как я могу перезапустить цикл foreach в С#??

Например:

Action a;
foreach(Constrain c in Constrains)
{
   if(!c.Allows(a))
   {
      a.Change();
      restart;
   }
}

restart здесь похоже на continue или break, но он перезапускает foreach с самого начала. Это похоже на установку счетчика цикла for на 0 снова..

Возможно ли это в С#?

Изменить: я хочу поблагодарить Мехрдада Афшари и Махеша Велагу за то, что они позволили мне обнаружить ошибку (индекс = 0) в моей текущей реализации, которая иначе не была бы обнаружена.


person Betamoo    schedule 01.01.2011    source источник
comment
Было бы интересно узнать, где именно вам нужно использовать такой перезапуск. Вы используете список изменяемых объектов в каком-то алгоритме. Можете ли вы поделиться реальной проблемой, которую вы пытаетесь решить?   -  person Unmesh Kondolikar    schedule 01.01.2011
comment
Актуальная проблема: некоторые агенты пытаются перемещаться в среде. В окружении есть барьеры. После того, как каждый агент решает, какое следующее действие движения, среда проверяет, пересечет ли агент барьер, и если да, среда позволяет агенту выбрать другое действие; и здесь, где мне нужно перезапустить цикл foreach, чтобы снова проверить все барьеры с помощью вновь выбранного действия.. Надеюсь, это ясно...   -  person Betamoo    schedule 01.01.2011


Ответы (5)


Используйте старый добрый goto:

restart:
foreach(Constrain c in Constrains)
{
   if(!c.Allows(a))
   {
      a.Change();
      goto restart;
   }
}

Если по какой-то причине у вас диагностируют готофобию в 100% случаев (что плохо без причины), вы можете вместо этого попробовать использовать флаг:

bool restart;
do {
   restart = false;
   foreach(Constrain c in Constrains)
   {
      if(!c.Allows(a))
      {
         a.Change();
         restart = true;
         break;
      }
   }
} while (restart);
person mmx    schedule 01.01.2011
comment
будь осторожен! в dotnet3.5 и более поздних версиях (LINQ и т. д.) foreach() может использовать замыкание. Это решение или любое другое, которое выпрыгивает из цикла, будет правильно размещать замыкание. Но подумайте об этом; это то место, где тщательный дизайн сэкономит много времени на отладку. - person O. Jones; 01.01.2011
comment
зачем тебе это? Я имею в виду, какую проблему он решает? Было бы неплохо увидеть оригинал, а не надуманный код. - person default_avatar; 14.01.2014
comment
Мне нравятся старые добрые гото и готофобия. Впрочем, ваше предложение для готофобов ничем не лучше. break это goto только с другим именем. Он просто переходит в неправильное место, которое вы должны исправить с помощью флага и дополнительного цикла do-while, что намного уродливее, чем решение 1. Вы, наверное, знали это :-) Но как только вы хотите предложить версию без перехода, она должна быть настоящим не-гото. Или вы можете полностью удалить эту часть, поскольку она уже покрыта ответом Махеша. - person chiccodoro; 27.05.2014
comment
Готофобы @chiccodoro почему-то склонны думать, что break в порядке. - person Wolfzoon; 10.10.2016
comment
@Wolfzoon Это правда, что разрыв - это своего рода переход, точно так же, как бросок и возврат. Разница в том, что это стандартно используемый goto с четко определенной структурой, интуитивно понятной современным опытным программистам. При чтении необработанных переходов вам нужно уделить дополнительное время, чтобы найти метку и проверить наличие потенциальных ошибок. И, как показывает ответ чиккодоро, искушение использовать либо goto, либо добавить громоздкий флаг часто является намеком на то, что может помочь некоторый рефакторинг. - person relatively_random; 21.01.2021

Хотя это очень старая ветка - ни один из ответов не уделил должного внимания семантике этого кода:

  • У вас есть цепочка ограничений на a
  • Если a сломает любой из них, попробуйте другой a и протолкните его через цепочку.

То есть a.Change() следует отделить от цикла проверки ограничений, также придерживаясь принципа CQS:

while (!MeetsConstraints(a))
{
    a.Change();
}

bool MeetsConstraints(Thing a)
{
    return Constraints.All(c => c.Allows(a));
}

Никаких переходов, никаких уродливых циклов, просто и чисто. ‹/похлопывание себя по спине›

person chiccodoro    schedule 27.05.2014
comment
Этот ответ ясно демонстрирует, что такие утверждения, как не быть фанатиком, использовать goto, когда это уместно, обычно упускают из виду: поиск способа обойти goto почти всегда делает код чище. Даже часто упоминаемый случай разрыва нескольких циклов может быть реорганизован в отдельный метод с оператором возврата. Особенно теперь, когда C# поддерживает локальные функции с их замыканиями. - person relatively_random; 21.01.2021

Один из способов сделать это — использовать for, как вы уже упоминали:

перезапуск здесь аналогичен продолжению или прерыванию, но он перезапускает foreach с самого начала. Это как установка счетчика цикла for снова на 0

Action a;
for(var index = 0; index < Constratins.Count; index++)
{
   if(!Constraints[index].Allows(a))
   {
      a.Change();
      index = -1; // restart
   }
}
person Mahesh Velaga    schedule 01.01.2011
comment
Это не верно. index = -1 будет работать, если перечисляемый объект доступен по индексу, но даже в этом случае код будет трудно читать, а намерение будет неясным. Это прекрасный пример усугубления ситуации в результате слепой готофобии. По крайней мере, с goto цель совершенно ясна. - person mmx; 01.01.2011
comment
+1 Это первое решение, которое пришло на ум. Почему бы не использовать цикл for? Зачем создавать испорченное решение foreach. - person Chuck Conway; 01.01.2011
comment
@Chuck: Возможно, потому что не все коллекции доступны по индексу? - person mmx; 01.01.2011
comment
@Mehrdad это возможно, но я бы начал с цикла for. - person Chuck Conway; 01.01.2011
comment
@Chuck Есть и другие проблемы с этим подходом. Это подвержено ошибкам (только что доказано исходной версией ответа с index = 0). Манипулирование вещами с индексами просто вызывает больше проблем. Не говоря уже о том, что манипулирование индексом цикла осуждается. - person mmx; 01.01.2011
comment
@Mehrdad Точка принята, но я бы все равно начал с нее. Кажется, не существует решающего способа сброса цикла foreach. - person Chuck Conway; 01.01.2011
comment
@Chuck: Да, но я бы сказал, что вы не должны сбрасывать цикл for, манипулируя индексом цикла внутри цикла. Я считаю, что goto более читабелен и менее подвержен ошибкам, чем установка индекса на -1. - person mmx; 01.01.2011
comment
@Mehrdad Я должен признать, что из всех размещенных здесь решений решение goto является наиболее кратким. - person Chuck Conway; 01.01.2011
comment
@ Mehrdad: Я согласен со всеми упомянутыми пунктами, но это первое, что пришло на ум (несмотря на то, что манипулирование индексом цикла — это плохо). Мне нравится ваша реализация do-while для этого. - person Mahesh Velaga; 01.01.2011
comment
@Махеш: я понимаю. Извините, что звучит слишком резко! Причина, по которой я много спорил с вашим решением, на самом деле была не в вашем решении. Я нашел, что это хороший пример, чтобы продемонстрировать тот факт, что goto не только не является монстром, которого нужно бояться, но и действительно полезен и может привести к более ясному коду. - person mmx; 01.01.2011
comment
Я хочу поблагодарить Mehrdad Afshari и Mahesh Velaga за то, что они позволили мне обнаружить ошибку (индекс = 0) в моей текущей реализации, которая иначе не была бы обнаружена.. :) - person Betamoo; 01.01.2011
comment
+1, это было первое (ну, второе, после рекурсии) решение, которое пришло мне в голову. Я думаю, что это делает намерение совершенно ясным — так же, как и версия goto, на самом деле. Предостережение относительно индексируемых коллекций, конечно, остается в силе. - person Konrad Rudolph; 02.01.2011

person    schedule
comment
+1. Рефакторинг обычно является хорошим решением в таких случаях, если он осуществим и не усложняет ситуацию. - person mmx; 01.01.2011
comment
@John - это будет работать и для IEnumerable, если вы измените IList на IEnumerable - person Unmesh Kondolikar; 01.01.2011
comment
Я не был против, но вы должны изменить IList на IEnumerable. - person mmx; 01.01.2011
comment
Я объяснил. Вопрос не ограничился IList<T> - person John Saunders; 01.01.2011
comment
ИМО, название TryChangeList больше сбивает с толку, чем отражает значение кода. Во-первых, вы не меняете список, a — это просто SomeObject (или Action в коде ОП). Во-вторых, TryChangeList звучит так, как будто список/действие было изменено, если оно было успешным. Это наоборот. Если значение a не соответствует ограничениям, необходимо проверить новое значение. - person chiccodoro; 22.08.2014
comment
Правильное имя метода будет CheckConstraintsAndIfNotMetChangeAOnceWithoutCheckingConstraintsAfterwards. Или вы можете удалить строку a.Change() и поместить ее в тело while, сделав метод чистой проверкой ограничений: while (!MeetsConstraints(cons, a)) { a.Change(); }. Рефакторинг — хорошая идея, но делайте это правильно. Таким образом, метод по-прежнему смешивает логику изменения значения и проверки ограничений, а изменение значения теперь распределено по двум методам. - person chiccodoro; 22.08.2014

person    schedule
comment
Сначала вы захотите удалить оригинал. - person John Saunders; 01.01.2011
comment
@Джон: Действительно. И вам придется иметь дело с потенциальными исключениями, ... что в конечном итоге приведет к утверждению using, и в этот момент вы поймете, что вам вообще не следовало отказываться от foreach! - person mmx; 01.01.2011