Почему "continue" ведет себя как "break" в объекте Foreach?

Если я сделаю следующее в скрипте PowerShell:

$range = 1..100
ForEach ($_ in $range) {
    if ($_ % 7 -ne 0 ) { continue; }
    Write-Host "$($_) is a multiple of 7"
}

Я получаю ожидаемый результат:

7 is a multiple of 7
14 is a multiple of 7
21 is a multiple of 7
28 is a multiple of 7
35 is a multiple of 7
42 is a multiple of 7
49 is a multiple of 7
56 is a multiple of 7
63 is a multiple of 7
70 is a multiple of 7
77 is a multiple of 7
84 is a multiple of 7
91 is a multiple of 7
98 is a multiple of 7

Однако, если я использую конвейер и ForEach-Object, continue, похоже, выходит из цикла конвейера.

1..100 | ForEach-Object {
    if ($_ % 7 -ne 0 ) { continue; }
    Write-Host "$($_) is a multiple of 7"
}

Могу ли я получить поведение continue -like, все еще работая с ForEach-Object, чтобы мне не пришлось разбивать мой конвейер?

Ответ 1

Просто используйте return вместо continue. Этот return возвращается из блока сценария, который вызывается ForEach-Object на определенной итерации, таким образом, он имитирует continue в цикле.

1..100 | ForEach-Object {
    if ($_ % 7 -ne 0 ) { return }
    Write-Host "$($_) is a multiple of 7"
}

При рефакторинге нужно помнить о гоче. Иногда хочется преобразовать блок операторов foreach в конвейер с помощью командлета ForEach-Object (у него даже есть псевдоним foreach, который помогает упростить это преобразование и упростить ошибки). Все continue должны быть заменены на return.

П.С.: К сожалению, не так-то просто смоделировать break в ForEach-Object.

Ответ 2

Поскольку объект For-Each является командлетом, а не циклом, а continue и break к нему не применяются.

Например, если у вас есть:

$b = 1,2,3

foreach($a in $b) {

    $a | foreach { if ($_ -eq 2) {continue;} else {Write-Host $_} }

    Write-Host  "after"
}

Вы получите вывод в виде:

1
after
3
after

Это потому, что continue применяется к внешнему циклу foreach, а не к командлету foreach-object. В отсутствие цикла, самого внешнего уровня, следовательно, создается впечатление, что он действует как break.

Итак, как вы получаете поведение continue -like? Одним из способов является Where-Object, конечно:

1..100 | ?{ $_ % 7  -eq 0} | %{Write-Host $_ is a multiple of 7}

Ответ 3

Другой альтернативой является своего рода хак, но вы можете заключить свой блок в цикл, который будет выполняться один раз. Таким образом, continue даст желаемый эффект:

1..100 | ForEach-Object {
    for ($cont=$true; $cont; $cont=$false) {
        if ($_ % 7 -ne 0 ) { continue; }
        Write-Host "$($_) is a multiple of 7"
    }
}

Ответ 4

Простое утверждение else заставляет его работать так:

1..100 | ForEach-Object {
    if ($_ % 7 -ne 0 ) {
        # Do nothing
    } else {
        Write-Host "$($_) is a multiple of 7"
    }
}

Или в одном конвейере:

1..100 | ForEach-Object { if ($_ % 7 -ne 0 ) {} else {Write-Host "$($_) is a multiple of 7"}}

Но более элегантное решение - инвертировать тест и генерировать результаты только для ваших успехов

1..100 | ForEach-Object {if ($_ % 7 -eq 0 ) {Write-Host "$($_) is a multiple of 7"}}