Причина ошибки CS0161: не все пути кода возвращают значение
Я сделал основной метод расширения, чтобы добавить функциональность повтора в мой HttpClient.PostAsync
:
public static async Task<HttpResponseMessage> PostWithRetryAsync(this HttpClient httpClient, Uri uri, HttpContent content, int maxAttempts, Action<int> logRetry)
{
if (maxAttempts < 1)
throw new ArgumentOutOfRangeException(nameof(maxAttempts), "Max number of attempts cannot be less than 1.");
var attempt = 1;
while (attempt <= maxAttempts)
{
if (attempt > 1)
logRetry(attempt);
try
{
var response = await httpClient.PostAsync(uri, content).ConfigureAwait(false);
response.EnsureSuccessStatusCode();
return response;
}
catch (HttpRequestException)
{
++attempt;
if (attempt > maxAttempts)
throw;
}
}
}
приведенный выше код дает мне следующую ошибку:
ошибка CS0161 ' HttpClientExtensions.PostWithRetryAsync (HttpClient, Uri, HttpContent, int, Action)': не все пути кода возвращают значение.
если я добавить throw new InvalidOperationException()
в конце (или return null
Если на то пошло), ошибка уходит, как и ожидалось. Что я действительно хотел бы знать: есть ли код путь, который фактически выходит из этого метода без возвращаемого значения или исключения? Я его не вижу. Знаю ли я больше, чем компилятор в этом случае, или наоборот?
4 ответов
простая причина заключается в том, что компилятор должен иметь возможность статически проверить все пути потока выполнения заканчиваются инструкцией return (или исключением).
давайте посмотрим на ваш код, он содержит:- некоторые переменные, контролирующие
while
цикл - A
while
петлиreturn
заявление врезанный - нет
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
-петли сattempt
1 иmaxAttempts
по крайней мере 1. - если код внутри
try
заявление бросает!--25--> затемattempt
увеличивается, а если еще меньше или равноmaxAttempts
наwhile
-петли сделаем еще одну итерацию. Если он теперь больше, чемmaxAttempts
исключение будет пузыриться. - если выбрасывается какое-либо другое исключение, оно не будет обработано и выйдет из метода
- если исключение не создается, возвращается ответ.
таким образом, в принципе, можно сказать, что этот код всегда заканчивается либо исключением, либо возвращением, но компилятор не может статически проверить это.
так как у вас встроенный аварийный люк (attempt > maxAttempts
) в двух местах, оба в качестве критериев для while
-loop, и дополнительно внутри catch
блок я бы упростил код, просто удалив его из while
-loop:
while (true)
{
...
if (attempt > maxAttempts)
throw;
...
}
так как вы гарантированно запустите while
-цикл по крайней мере один раз, и что это будет на самом деле catch
блок, который выходит из него, просто формализуйте это, и компилятор снова будет счастлив.
теперь управление потоком выглядит это:
- на
while
цикл всегда execute (или мы уже создали исключение) - на
while
цикл никогда завершить (нетbreak
внутри, поэтому нет необходимости в коде после цикла) -
единственный возможный способ выйти из цикла-либо явный
return
или исключение, ни один из которых компилятор не должен проверять больше, потому что фокус этого конкретного сообщения об ошибке-флаг что потенциально есть способ избежать метода без явногоreturn
. Поскольку нет способа избежать метода случайно, остальные проверки можно просто пропустить.даже этот метод будет составлять:
public int Test() { while (true) { } }
Если он бросает HttpRequestException и блок catch выполняется, он может пропустить бросить оператор в зависимости от условия (попытка > maxAttempts), чтобы этот путь ничего не возвращал.
public static async Task<HttpResponseMessage> PostWithRetryAsync(this HttpClient httpClient, Uri uri, HttpContent content, int maxAttempts, Action<int> logRetry)
{
if (maxAttempts < 1)
throw new ArgumentOutOfRangeException(nameof(maxAttempts), "Max number of attempts cannot be less than 1.");
var attempt = 1;
while (attempt <= maxAttempts)
{
if (attempt > 1)
logRetry(attempt);
try
{
var response = await httpClient.PostAsync(uri, content).ConfigureAwait(false);
response.EnsureSuccessStatusCode();
return response;
}
catch (HttpRequestException)
{
++attempt;
if (attempt > maxAttempts)
throw;
else
return something; // HERE YOU NEED TO RETURN SOMETHING
}
}
}
но если вы хотите продолжить цикл, вам нужно вернуться в конце:
public static async Task<HttpResponseMessage> PostWithRetryAsync(this HttpClient httpClient, Uri uri, HttpContent content, int maxAttempts, Action<int> logRetry)
{
if (maxAttempts < 1)
throw new ArgumentOutOfRangeException(nameof(maxAttempts), "Max number of attempts cannot be less than 1.");
var attempt = 1;
while (attempt <= maxAttempts)
{
if (attempt > 1)
logRetry(attempt);
try
{
var response = await httpClient.PostAsync(uri, content).ConfigureAwait(false);
response.EnsureSuccessStatusCode();
return response;
}
catch (HttpRequestException)
{
++attempt;
if (attempt > maxAttempts)
throw;
}
}
return something; // HERE YOU NEED TO RETURN SOMETHING
}
as Error заявляет, что not all code paths return a value
вы не возвращаете значение для каждого пути кода
вы должны создать исключение или вернуть значение
catch (HttpRequestException)
{
++attempt;
if (attempt > maxAttempts)
throw;
else
return null;//you must return something for this code path
}
вы можете изменить свой код, чтобы весь путь кода возвращал значение. код должен быть примерно таким
public static async Task<HttpResponseMessage> PostWithRetryAsync(this HttpClient httpClient, Uri uri, HttpContent content, int maxAttempts, Action<int> logRetry)
{
HttpResponseMessage response = null;
if (maxAttempts < 1)
throw new ArgumentOutOfRangeException(nameof(maxAttempts), "Max number of attempts cannot be less than 1.");
var attempt = 1;
while (attempt <= maxAttempts)
{
if (attempt > 1)
logRetry(attempt);
try
{
response = await httpClient.PostAsync(uri, content).ConfigureAwait(false);
response.EnsureSuccessStatusCode();
}
catch (HttpRequestException)
{
++attempt;
if (attempt > maxAttempts)
throw;
}
}
return response;
}