λ1017: Prefer the named function over the lambda

A lambda that does what one of the functions in Funcky.Functional does should be replaced by that function.

Cause

A lambda is written where a named function from Funcky.Functional says the same:

LambdaNamed function
x => x, x => { return x; }Identity
() => true, _ => true, (_, _) => true, …True
() => false, _ => false, (_, _) => false, …False
() => { }, _ => { }, (_, _) => { }, …NoOperation

The rule applies to any delegate-typed position: arguments of LINQ and Funcky methods, variable initializers, assignments and return values.

Lambdas that are not exactly one of these are left alone: x => (long)x converts its parameter, _ => Flag returns a constant that may change, async _ => { } returns a task.

The rule only reports when the method group binds the same way as the lambda. items.Where(_ => true) is not reported because Where has an overload with an index parameter and True has overloads with one and two parameters, which makes Where(True) ambiguous. Lambdas inside expression trees are not reported either, because a method group cannot be converted to an expression tree.

Reason for rule

The named function states the intent at a glance: Where(True) reads as "keep everything", Switch(none: NoOperation, …) as "do nothing in that case". The lambda has to be read to arrive at the same conclusion, and a one-character typo turns x => x into something else entirely.

Since C# 11 the compiler caches the delegate created from a static method group, so there is no runtime cost compared to a lambda. Before C# 11 the method group allocates a delegate on each evaluation while a non-capturing lambda is cached once, so on older language versions the rule reports an info instead of a warning.

How to fix violations

Replace the lambda with the named function. A code fix is available; it adds using static Funcky.Functional; when the name is not yet in scope and uses Functional.Identity when the bare name is taken by another member.

Examples

Disallowed

static IEnumerable<int> Example(IEnumerable<Option<int>> options)
    => options.SelectMany(option => option.Select(x => x));

static bool Example(Option<int> option)
    => option.Match(none: false, some: _ => true);

static void Example(Option<string> option)
    => option.Switch(none: () => { }, some: Console.WriteLine);

Allowed

static IEnumerable<int> Example(IEnumerable<Option<int>> options)
    => options.SelectMany(option => option.Select(Identity));

static bool Example(Option<int> option)
    => option.Match(none: false, some: True);

static void Example(Option<string> option)
    => option.Switch(none: NoOperation, some: Console.WriteLine);

// Where(True) would be ambiguous, so the lambda stays.
static IEnumerable<int> Example(IEnumerable<int> items)
    => items.Where(_ => true);