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
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
- Target Method: Intercept invocations of
Select. - Receiver Check: Require a fluent
IQueryable<T>orIEnumerable<T>receiver that the fixer can preserve directly. - Lambda Check: Inspect the lambda argument, including delegate-created statement lambdas.
- Type Check: Require the selector return type to match the source parameter type, nullability included, so casts such as
x => (object)xand null-forgiving projections such asx => x!overstring?elements are not treated as redundant. - Identity Check: If the lambda is an identity function (for example
x => xor{ 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.