λ1015: Fallback is computed eagerly

The fallback passed to GetOrElse, OrElse or Match is computed even when there is a value and it is never used.

Cause

An argument passed to the eager overload of GetOrElse, OrElse or the none parameter of Match on Option<T>, Result<T> or Either<L, R> does work: it calls a method, creates an object, builds an interpolated string or awaits.

Reason for rule

Arguments are evaluated before the call. option.GetOrElse(LoadDefault()) therefore runs LoadDefault on every call, including the ones where the option has a value and the result is thrown away. Every fallback-taking method has a lazy overload that takes a function instead, which is only invoked when the fallback is needed.

This is a performance hint rather than a correctness issue, so the rule reports an info instead of a warning. The fallback might be cheap, or you might want its side effect regardless; in that case suppress the rule for that line.

Values that cost nothing are never reported: literals, constants, locals, parameters, field and property reads, Option<T>.None, Array.Empty<T>(), Enumerable.Empty<T>() and the constructors of the monads themselves (Option.Some(x), Result.Ok(x), Either<L, R>.Left(x)) as long as their arguments cost nothing either. A throw expression in the fallback is reported by λ1014 instead.

How to fix violations

Pass a function instead of the value: prefix the argument with () => for Option<T>, or with _ => for Result<T> and Either<L, R>, whose lazy overloads receive the exception or the left value. A code fix is available.

Examples

Disallowed

static Settings Example(Option<Settings> settings)
    => settings.GetOrElse(LoadDefaultSettings());

static List<int> Example(Option<List<int>> list)
    => list.GetOrElse(new List<int>());

static string Example(Result<string> result, int id)
    => result.GetOrElse($"missing #{id}");

Allowed

static Settings Example(Option<Settings> settings)
    => settings.GetOrElse(LoadDefaultSettings);

static List<int> Example(Option<List<int>> list)
    => list.GetOrElse(() => new List<int>());

static string Example(Result<string> result, int id)
    => result.GetOrElse(_ => $"missing #{id}");

// Plain values and the empty-collection singletons cost nothing.
static int Example(Option<int> option, int fallback)
    => option.GetOrElse(fallback);

static IEnumerable<int> Example(Option<IEnumerable<int>> sequence)
    => sequence.GetOrElse(Enumerable.Empty<int>());