← back to index

S4457 — Parameter validation in "async"/"await" methods should be wrapped

Language: C#  |  Type: CODE_SMELL  |  Severity: Major

Tags: async-await

Why is this an issue?

Because of the way async/await methods are rewritten by the compiler, any exceptions thrown during the parameters check are captured in the returned Task and will only be observed when the task is awaited. That could happen far away from the source of the buggy code or never happen for fire-and-forget tasks.

Therefore it is recommended to split the method into two: an outer method handling the parameter checks (without being async/await) and an inner method to handle the iterator block with the async/await pattern.

This rule raises an issue when an async method throws any exception derived from ArgumentException and contains the await keyword.

Noncompliant code example

public static async Task SkipLinesAsync(this TextReader reader, int linesToSkip) // Noncompliant
{
    if (reader == null) { throw new ArgumentNullException(nameof(reader)); }
    if (linesToSkip < 0) { throw new ArgumentOutOfRangeException(nameof(linesToSkip)); }

    for (var i = 0; i < linesToSkip; ++i)
    {
        var line = await reader.ReadLineAsync().ConfigureAwait(false);
        if (line == null) { break; }
    }
}

var task = SkipLinesAsync(null, -1); // No exception - captured in the task
await task;                          // ArgumentNullException thrown here

Compliant solution

public static Task SkipLinesAsync(this TextReader reader, int linesToSkip)
{
    if (reader == null) { throw new ArgumentNullException(nameof(reader)); }
    if (linesToSkip < 0) { throw new ArgumentOutOfRangeException(nameof(linesToSkip)); }

    return reader.SkipLinesInternalAsync(linesToSkip);
}

private static async Task SkipLinesInternalAsync(this TextReader reader, int linesToSkip)
{
    for (var i = 0; i < linesToSkip; ++i)
    {
        var line = await reader.ReadLineAsync().ConfigureAwait(false);
        if (line == null) { break; }
    }
}

var task = SkipLinesAsync(null, -1); // ArgumentNullException thrown here
await task;

Resources

Related rules