Skip to content

Do not report MA0078 when the selector does more than casting its element - #1473

Merged
meziantou merged 1 commit into
mainfrom
feature/ma0078-select-cast-bug-f9fc16
Sep 12, 2026
Merged

meziantou merged 1 commit into
mainfrom
feature/ma0078-select-cast-bug-f9fc16

Conversation

@meziantou

Copy link
Copy Markdown
Owner

Problem

MA0078 took the first return operation and the first lambda found anywhere under the Select selector argument, without checking that the cast was the whole selector body. The code fix then replaced Select with Cast<T>(), which could change behavior:

static int count;

var values = new object[] { "a" }
    .Select(x => { count++; return (string)x; }).ToArray();
// Fixed to: (new object[] { "a" }).Cast<string>().ToArray();  -> count++ is silently removed

The same logic also reported:

  • selectors with several return paths, e.g. dt => { if (dt.Name is not null) return (BaseType)dt; return null; }
  • a cast in a lambda that is not the selector itself, e.g. source.Select(Wrap(dt => (BaseType)dt))

Fix

The selector must now be a lambda passed directly as the argument (IDelegateCreationOperation targeting an IAnonymousFunctionOperation), whose body is exactly one return of a cast of its first parameter. The existing checks (no as, no numeric/user-defined conversions, enum handling) are unchanged.

x => (T)x and x => { return (T)x; } are still reported and fixed.

Note for reviewers: a lambda explicitly cast to a delegate type, such as Select((Func<A, B>)(x => (B)x)), may no longer be reported. This errs on the safe side, and no existing test covered it.

Tests

  • Added no-diagnostic cases for the side effect, the multiple return paths, and the nested lambda. All three fail with the previous analyzer.
  • Added a code fix case for a statement-bodied selector with a single return.
  • OptimizeLinqUsageAnalyzerTests (157 tests) pass on Roslyn 4.8, 4.14, 5.0, 5.6 and 5.9. The full suite was not run locally.

Docs

docs/Rules/MA0078.md now states which selectors are reported, and its compliant example (Cast<object>(str), which did not compile) is fixed.

…ment

The analyzer used the first return operation and the first lambda found
anywhere under the selector argument, without checking that the cast was
the whole selector body. The code fix then replaced Select with Cast<T>(),
silently removing other statements (and their side effects), ignoring
other return paths, or matching a lambda that was not the selector itself
(e.g. Select(Wrap(x => (T)x))).

The selector must now be a lambda passed directly as the argument whose
body is a single return of a cast of its first parameter.
@meziantou
meziantou merged commit 8e2ece0 into main Sep 12, 2026
13 checks passed
@meziantou
meziantou deleted the feature/ma0078-select-cast-bug-f9fc16 branch September 12, 2026 02:10
This was referenced Sep 12, 2026
This was referenced Sep 17, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant