web-dev-qa-db-ja.com

エラー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_)を追加すると、エラーは期待どおりになくなります。私が本当に知りたいのは、値が返されたり例外がスローされたりすることなく、実際にこのメソッドを終了するコードパスはありますか?見えません。この場合、コンパイラ以上のことを知っていますか、それとも逆ですか?

23
Martin Wedvich

簡単な理由は、すべての実行フローパスがreturnステートメント(または例外)で終わることをコンパイラーが静的に検証できる必要があるためです。

あなたのコードを見てみましょう、それは含まれています:

  • whileループを制御するいくつかの変数
  • whileステートメントが埋め込まれたreturnループ
  • ループ後のreturnステートメントの後

したがって、基本的にコンパイラはこれらのことを確認する必要があります。

  1. whileループが実際に実行されること
  2. returnステートメントがalways実行されること
  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;
}

唯一の違いは、aconstにしたことです。コンパイルは完了しましたが、これはオプティマイザーがループ全体を削除できるようになったためです。最終的なILは次のようになります。

Test:
IL_0000:  ldc.i4.s    0A 
IL_0002:  ret     

whileループ全体とローカル変数はなくなり、残りはすべてこれだけです。

return 10;

そのため、コンパイラーはこれらのことを静的に分析するときに変数値を見ないことは明らかです。この機能を実装して正しく機能させるためのコストは、おそらくそれを行わないことの効果またはマイナス面を上回ります。 「すべての機能は穴から100ポイントで始まります。つまり、言語全体に組み込むにはパッケージ全体に大きな正の効果をもたらす必要があります。」

はい、これは間違いなく、コンパイラよりもコードについて多くを知っている場合です。


完全を期すために、コードが流れるすべての方法を見てみましょう。

  1. maxAttemptsが1未満の場合、例外を発生して早期に終了できます
  2. whileが1でattemptが少なくとも1であるため、ItwillmaxAttempts- loopに入ります。
  3. tryステートメント内のコードがHttpRequestExceptionをスローする場合、attemptがインクリメントされ、maxAttempts以下の場合はwhile -loopは別の繰り返しを行います。現在maxAttemptsよりも大きい場合、例外が発生します。
  4. 他の例外がスローされた場合、処理されず、メソッドからバブルアウトします
  5. 例外がスローされない場合、応答が返されます。

そのため、基本的に、このコードは常に例外をスローするかリターンするかのどちらかと言えますが、コンパイラはこれを静的に検証することはできません。


エスケープハッチ(attempt > maxAttemptswhile- loopの基準として、さらにcatchブロックの内側の2つの場所で、whileから削除するだけでコードを簡素化できます。 -ループ:

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

while- loopを少なくとも1回実行することが保証されており、実際にそれがcatchブロックで終了するので、それを形式化するだけで、コンパイラは再び幸せになります。

これで、フロー制御は次のようになります。

  • whileループはalwaysを実行します(または既に例外をスローしています)
  • whileループはnever終了します(内部にbreakがないため、ループの後にコードは不要です)
  • ループを抜ける唯一の可能な方法は、明示的なreturnまたは例外のいずれかです。この特定のエラーメッセージの焦点は、潜在的に明示的なreturnなしでメソッドをエスケープします。誤ってメソッドをエスケープする方法はないため、残りのチェックは単純にスキップできます。

    このメソッドでもコンパイルされます:

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

HttpRequestExceptionがスローされ、catchブロックが実行されると、条件に応じてthrowステートメントがスキップされ(試行> maxAttempts)、パスが何も返さないようになります。

5
Volkan Paksoy
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
}
1
ehh

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;
}
1
Arjun Vachhani