Analyzer rule

LC029: Redundant Identity Select

LC029 flags Select(x => x) in LINQ and EF Core query chains. The identity projection does nothing and only adds noise to the expression tree.

Default severity
Info
Code fix
Available
Category
Performance

Change the severity in .editorconfig:

[*.cs]
dotnet_diagnostic.LC029.severity = error

Sample code · Analyzer source

LC029: Redundant Identity Select

In Plain Terms

Imagine you ask a friend to go to the store and buy apples. But then you say, “And for every apple you find, make sure the apple you bring back is an apple.” Your friend will look at you funny because they were already going to do that!

Goal

Detect usage of Select(x => x) on queryable or enumerable chains.

The Problem

Select(x => x) is redundant as it returns the identity of the object. While logically harmless, it adds unnecessary noise to the expression tree and can occasionally prevent the query provider from performing certain SQL optimizations.

Example Violation

// Violation: Identity select is redundant
var users = db.Users.Select(u => u).ToList();

Statement-bodied identity lambdas are equivalent when the source is enumerable:

// Violation: Still returns the same element unchanged
var users = cachedUsers.Select(u => { return u; }).ToList();

The Fix

Remove the .Select(x => x) call.

// Correct
var users = db.Users.ToList();

If the Select was being used as a marker for an intentional boundary, keep the boundary explicitly and remove only the identity projection:

// Violation: the boundary is real, the identity projection is not
var users = db.Users.AsEnumerable().Select(u => u).ToList();

// Correct: the client-side boundary is still visible
var users = db.Users.AsEnumerable().ToList();

Analyzer Logic

ID: LC029

Category: Performance

Severity: Info

Algorithm

  1. Target Method: Intercept invocations of Select.
  2. Receiver Check: Require a fluent IQueryable<T> or IEnumerable<T> receiver that the fixer can preserve directly.
  3. Lambda Check: Inspect the lambda argument, including delegate-created statement lambdas.
  4. Type Check: Require the selector return type to match the source parameter type, nullability included, so casts such as x => (object)x and null-forgiving projections such as x => x! over string? elements are not treated as redundant.
  5. Identity Check: If the lambda is an identity function (for example x => x or { return x; }), report a violation.

Test Cases

Violations

query.Select(x => x);
items.Select(x => { return x; });

Valid

query.Select(x => x.Name);

// Turns IEnumerable<string?> into IEnumerable<string> for nullable analysis.
names.Where(n => n != null).Select(n => n!);

Intentional boundaries

Use APIs that state the boundary directly: AsEnumerable(), AsAsyncEnumerable(), ToList(), ToArray(), or a reviewed suppression when the full scan is intentional. LC029’s fixer preserves the receiver, so query.AsEnumerable().Select(x => x) becomes query.AsEnumerable().

Shipped Behavior

LC029 reports identity projections such as Select(x => x) on queryable chains and Select(x => { return x; }) on interface-shaped enumerable chains. The fixer removes the redundant projection while preserving the rest of the fluent query.

Static extension calls such as Enumerable.Select(items, x => x), concrete enumerable receivers such as List<T>, awaited-task projections such as items.Select(async x => await x), explicit-cast projections such as items.Select<Base, Base>(x => (Derived)x), type-changing projections such as items.Select(x => (object)x), and null-forgiving projections that change element nullability such as names.Select(n => n!) over IEnumerable<string?> are not reported because removing the call would either require a different rewrite shape or change the projected type/surface.

Fluent receivers remain supported when they are parenthesized, explicitly cast, or null-forgiven, so (query).Select(x => x), ((IQueryable<User>)query).Select(x => x), and query!.Select(x => x) are still treated as redundant identity projections.