Причина ошибки 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; }Единственная разница в том, что я сделал
aaconst. Теперь он компилируется, но это потому, что оптимизатор теперь может удалить весь цикл, конечный 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; }