Причина ошибки 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 22

4 ответа:

Простая причина заключается в том, что компилятор должен иметь возможностьстатически проверить , что все пути потока выполнения заканчиваются оператором return (или исключением).

Давайте посмотрим на ваш код, он содержит:
  • некоторые переменные, управляющие циклом while
  • цикл while с вложенным оператором return
  • Нет return оператор после цикл

Так что в основном компилятор должен проверить их вещи:

  1. что цикл while фактически выполняется
  2. что return оператор всегда выполняется
  3. или , что вместо этого всегда выбрасывается какое-то исключение.
Компилятор просто не в состоянии это проверить. Давайте попробуем очень простой пример:
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 пунктов, что означает, что он должен иметь значительный чистый положительный эффект на общий пакет, чтобы он вошел в язык.".

Так что да, это определенно тот случай, когда вы знаете о коде больше, чем компилятор.

Просто для полноты картины давайте рассмотрим все способы, которыми может протекать ваш код:

  1. он может выйти раньше с исключением, если maxAttempts меньше 1
  2. It will enter the while-loop поскольку attempt равно 1, а maxAttempts - по крайней мере 1.
  3. Если код внутри оператора try выдает HttpRequestException, то attempt увеличивается, и если все еще меньше или равно maxAttempts, то while-цикл сделает еще одну итерацию. Если он теперь больше, чем maxAttempts, исключение всплывет.
  4. Если выбрасывается какое-то другое исключение, оно не будет обработано и выйдет из метода
  5. Если исключение не возникает, то возвращается ответ.

Итак, в основном, этот код можно сказать, что всегда в конечном итоге либо выбрасывается исключение, либо возвращается, но компилятор не в состоянии статически проверить это.


Поскольку вы встроили аварийный люк (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;
}