Причина ошибки 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 сообщении после цикл

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

  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. это будет введите while-петли с attempt 1 и maxAttempts по крайней мере 1.
  3. если код внутри try заявление бросает!--25--> затем attempt увеличивается, а если еще меньше или равно maxAttempts на while-петли сделаем еще одну итерацию. Если он теперь больше, чем maxAttempts исключение будет пузыриться.
  4. если выбрасывается какое-либо другое исключение, оно не будет обработано и выйдет из метода
  5. если исключение не создается, возвращается ответ.

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


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