BRO1150: Pass the predicate to the LINQ call instead of calling Where first¶
source.Where(p).Count() becomes source.Count(p), and the same for Any, LongCount, First, FirstOrDefault,
Last, LastOrDefault, Single and SingleOrDefault. Not a StyleCop rule: Sonar's
S2971 reports this pattern (among other LINQ simplifications), and
StyleBro also fixes it.
Example¶
Before¶
using System.Collections.Generic;
using System.Linq;
public static class Stock
{
public static bool AnyMissing(List<int> counts) => counts.Where(c => c == 0).Any();
public static int Low(List<int> counts) =>
counts
.Where(c => c < 5)
.Count();
}
After¶
using System.Collections.Generic;
using System.Linq;
public static class Stock
{
public static bool AnyMissing(List<int> counts) => counts.Any(c => c == 0);
public static int Low(List<int> counts) =>
counts
.Count(c => c < 5);
}
Why¶
Every one of these methods takes the predicate itself. The Where call adds a step to read (and an iterator to
allocate) without saying anything the call after it can't.
How the fix works¶
Where is renamed to the call after it, and that call (.Count()) goes; the predicate and the line breaks before it
stay, so a call chain split over lines keeps its layout. A Where inside the predicate is fixed in the same run.
Lists and arrays when Sonar wants their own methods¶
Where Sonar's S6605 ("Exists" instead of "Any") or S6602 ("Find" instead of "FirstOrDefault") is on, the fix writes the collection's own method instead of the LINQ call Sonar would report next:
| Before | S6605/S6602 off | S6605/S6602 on |
|---|---|---|
list.Where(p).Any() |
list.Any(p) |
list.Exists(p) |
list.Where(p).FirstOrDefault() |
list.FirstOrDefault(p) |
list.Find(p) |
array.Where(p).Any() |
array.Any(p) |
Array.Exists(array, p) |
array.Where(p).FirstOrDefault() |
array.FirstOrDefault(p) |
Array.Find(array, p) |
"On" means a severity set for the Sonar id (dotnet_diagnostic.S6605.severity in .editorconfig, a global config or a
ruleset), or, when none is set, the default of the SonarAnalyzer.CSharp package in the project: on before version 10
(9.x), off from 10 on. The package's version reaches StyleBro through its build targets (build_property.StyleBroSonar);
an analyzer referenced without them only sees configured severities. Where the collection's method wouldn't bind (a
Func<T, bool> variable isn't a Predicate<T>, Array without using System;), the LINQ call is written as usual.
Not reported¶
- Methods other than System.Linq's
EnumerableandQueryable(a type's ownWhereorCount), and calls where the version with the predicate would bind to something else (an instance methodAny(Func<T, bool>)on the source, the index overloadWhere((x, i) => ...)). - The two calls from different classes (
Queryable.WherethenEnumerable.Count). - A call with arguments (
FirstOrDefault(defaultValue)), explicit type arguments,?., arefargument. - Expression trees (
Expression<Func<...>>lambdas, query clauses overIQueryable): a query provider would see another tree. - A comment or directive in the text that goes.
Compared with other analyzers¶
Sonar's S2971 reports this (on Where) and more (Select(x => x as T) instead of OfType<T>(), ...); it has no fix.
Roslynator's RCS1077 covers it too, with a fix.
Also fixes Sonar's S2971 when the SonarAnalyzer.CSharp package is in the project: the fix is registered for S2971 too (the Where name, where Sonar reports it) and applies at the places this rule would fix, so dotnet format clears Sonar's warnings there even with this rule off. Where this rule skips the code (see Not reported), no fix is offered and Sonar's warning stays. Sonar's other S2971 cases (OfType<T>(), the Count property, ...) aren't fixed.
Compared with StyleCop¶
StyleCop has no such rule. stylebro-migrate leaves it off.