Причина ошибки 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
- цикл
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
aconst
. Теперь он компилируется, но это потому, что оптимизатор теперь может удалить весь цикл, конечный IL просто таков:Test: IL_0000: ldc.i4.s 0A IL_0002: ret
Весь цикл
while
и локальная переменная исчезли, осталось только это:return 10;
Поэтому очевидно, что компилятор не смотрит на значения переменных, когда он статически анализирует эти вещи. Затраты на реализацию этой функции и ее правильное использование, вероятно, перевешивают эффект или обратную сторону отказа от нее. Помните, что " каждая функция начинается в отверстии на 100 пунктов, что означает, что он должен иметь значительный чистый положительный эффект на общий пакет, чтобы он вошел в язык.".
Так что да, это определенно тот случай, когда вы знаете о коде больше, чем компилятор.
Просто для полноты картины давайте рассмотрим все способы, которыми может протекать ваш код:
- он может выйти раньше с исключением, если
maxAttempts
меньше 1- It will enter the
while
-loop посколькуattempt
равно 1, аmaxAttempts
- по крайней мере 1.- Если код внутри оператора
try
выдаетHttpRequestException
, тоattempt
увеличивается, и если все еще меньше или равноmaxAttempts
, тоwhile
-цикл сделает еще одну итерацию. Если он теперь больше, чемmaxAttempts
, исключение всплывет.- Если выбрасывается какое-то другое исключение, оно не будет обработано и выйдет из метода
- Если исключение не возникает, то возвращается ответ.
Итак, в основном, этот код можно сказать, что всегда в конечном итоге либо выбрасывается исключение, либо возвращается, но компилятор не в состоянии статически проверить это.
Поскольку вы встроили аварийный люк (
attempt > maxAttempts
) в двух местах, как в качестве критерия дляwhile
-цикла, так и дополнительно внутри блокаcatch
я бы упростил код, просто удалив его изwhile
-цикла:while (true) { ... if (attempt > maxAttempts) throw; ... }
Так как вы гарантированно запустите
while
-цикл хотя бы один раз, и это будет фактически блокcatch
, который завершает работу это, просто формализуйте, и компилятор снова будет доволен.Теперь управление потоком выглядит следующим образом:
- цикл
while
будет всегда выполняться (или мы уже создали исключение)- цикл
while
будет никогда не завершится (нетbreak
внутри, поэтому нет необходимости в каком-либо коде после цикла)Единственным возможным способом выхода из цикла является либо явное
return
, либо исключение, ни одно из которых компилятору больше не нужно проверять. потому что фокус этого конкретного сообщения об ошибке заключается в том, чтобы отметить, что потенциально существует способ избежать метода без явногоreturn
. Поскольку больше нет возможности случайно избежать метода, остальные проверки можно просто пропустить.Даже этот метод будет компилировать:
public int Test() { while (true) { } }
Если он вызывает HttpRequestException и блок catch выполняется, он может пропустить throw оператор в зависимости от условия (попытка > 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; }