Ответ 1
Простая причина заключается в том, что компилятор должен иметь возможность статически проверять, что все пути потока выполнения заканчиваются оператором return (или исключением).
Посмотрите на свой код, он содержит:
- Некоторые переменные, управляющие циклом
while
- A
while
, с выражениемreturn
embedded - Без
return
после цикла
Итак, в основном компилятор должен проверить эти вещи:
- Выполняется выполнение цикла
while
- Оператор
return
всегда выполняется - Или что вместо этого всегда исключается какое-то исключение.
Компилятор просто не может проверить это.
Попробуйте простой пример:
public int Test()
{
int a = 1;
while (a > 0)
return 10;
}
Этот тривиальный пример будет генерировать ту же самую ошибку:
CS0161 'Test()': не все пути кода возвращают значение
Поэтому компилятор не может вывести это из-за этих фактов:
-
a
- локальная переменная (это означает, что на нее может воздействовать только локальный код) -
a
имеет начальное значение1
и никогда не изменяется - Если переменная
a
больше нуля (она есть), операторreturn
достигнут
тогда код всегда будет возвращать значение 10.
Теперь посмотрите на этот пример:
public int Test()
{
const int a = 1;
while (a > 0)
return 10;
}
Единственное отличие состоит в том, что я сделал a
a const
. Теперь он компилируется, но это связано с тем, что оптимизатор теперь может удалить весь цикл, конечный IL - это просто:
Test:
IL_0000: ldc.i4.s 0A
IL_0002: ret
Целая цепочка while
и локальная переменная исчезли, все осталось именно так:
return 10;
Так ясно, что компилятор не смотрит на переменные значения, когда он статически анализирует эти вещи. Стоимость реализации этой функции и ее правильное использование, вероятно, перевешивает эффект или недостаток, чтобы не делать этого. Помните, что "Каждая функция начинается в отверстии на 100 пунктов, а это означает, что она должна иметь значительный положительный эффект на общий пакет, чтобы он мог превратиться в язык" ..
Итак, это определенно случай, когда вы знаете больше о коде, чем компилятор.
Просто для полноты взгляните на все способы, которыми может работать ваш код:
- Он может выйти раньше с исключением, если
maxAttempts
меньше 1 - Он войдет в
while
-loop, так какattempt
равен 1, аmaxAttempts
- не менее 1. - Если код внутри оператора
try
вызываетHttpRequestException
, тоattempt
увеличивается и, если он меньше или равенmaxAttempts
,while
-loop выполняет другую итерацию. Если теперь он больше, чемmaxAttempts
, исключение будет пузыриться. - Если вызывается какое-то другое исключение, оно не будет обработано и выйдет из метода
- Если исключение не выбрасывается, возвращается ответ.
В принципе, этот код можно сказать, что он всегда заканчивается тем, что он бросает исключение или возвращает, но компилятор не может статически проверить это.
Поскольку вы вложили escape-штрих (attempt > maxAttempts
) в два места, как в качестве критерия для while
-loop, так и дополнительно внутри блока catch
я бы упростил код, просто удалив его из while
-loop:
while (true)
{
...
if (attempt > maxAttempts)
throw;
...
}
Так как вы гарантированно запустите while
-loop хотя бы один раз и что на самом деле это будет блок catch
, который выйдет из него, просто оформить это, и компилятор снова будет счастлив.
Теперь управление потоком выглядит следующим образом:
- Цикл
while
всегда будет выполняться (или мы уже выбрали исключение) - Цикл
while
никогда не будет завершен (нетbreak
внутри, поэтому нет необходимости в каком-либо коде после цикла) -
Единственный возможный способ выхода из цикла - это явный
return
или исключение, ни один из которых компилятор не должен проверять, потому что фокус этого конкретного сообщения об ошибке должен означать, что есть потенциально способ избежать метода без явногоreturn
. Так как нет способа избежать этого метода случайно, остальные проверки можно просто пропустить.Даже этот метод скомпилирует:
public int Test() { while (true) { } }