Подтвердить что ты не робот

Причина ошибки 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, если на то пошло), ошибка исчезнет, ​​как ожидалось. То, что я действительно хотел бы знать, это: есть ли какой-либо путь кода, который фактически выходит из этого метода без возвращаемого значения или генерируемого исключения? Я не вижу этого. Знаю ли я больше, чем компилятор в этом случае, или это наоборот?

4b9b3361

Ответ 1

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

Посмотрите на свой код, он содержит:

  • Некоторые переменные, управляющие циклом while
  • A while, с выражением return embedded
  • Без 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 -loop, так как attempt равен 1, а maxAttempts - не менее 1.
  • Если код внутри оператора try вызывает HttpRequestException, то attempt увеличивается и, если он меньше или равен maxAttempts, while -loop выполняет другую итерацию. Если теперь он больше, чем maxAttempts, исключение будет пузыриться.
  • Если вызывается какое-то другое исключение, оно не будет обработано и выйдет из метода
  • Если исключение не выбрасывается, возвращается ответ.

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


Поскольку вы вложили escape-штрих (attempt > maxAttempts) в два места, как в качестве критерия для while -loop, так и дополнительно внутри блока catch я бы упростил код, просто удалив его из while -loop:

while (true)
{
    ...
        if (attempt > maxAttempts)
            throw;
    ...
}

Так как вы гарантированно запустите while -loop хотя бы один раз и что на самом деле это будет блок catch, который выйдет из него, просто оформить это, и компилятор снова будет счастлив.

Теперь управление потоком выглядит следующим образом:

  • Цикл while всегда будет выполняться (или мы уже выбрали исключение)
  • Цикл while никогда не будет завершен (нет break внутри, поэтому нет необходимости в каком-либо коде после цикла)
  • Единственный возможный способ выхода из цикла - это явный return или исключение, ни один из которых компилятор не должен проверять, потому что фокус этого конкретного сообщения об ошибке должен означать, что есть потенциально способ избежать метода без явного return. Так как нет способа избежать этого метода случайно, остальные проверки можно просто пропустить.

    Даже этот метод скомпилирует:

    public int Test()
    {
        while (true)
        {
        }
    }
    

Ответ 2

Если он выдает исключение HttpRequestException и блок catch, он может пропустить инструкцию throw в зависимости от условия (попыткa > maxAttempts), чтобы путь не возвращал ничего.

Ответ 3

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
}

Ответ 4

В качестве ошибки указано, что 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;
}