Skip to content

[Validation] Analyzer diagnostics for loop iterators captured in closures #68490

Description

@oroztocil

Scenario contact: @ilonatommy

Scenario

A for loop that renders a button per item, with each handler referring to the loop counter, compiles cleanly and then does the wrong thing: every button acts on the last item, because the counter is one variable shared by every iteration. This validates that the build points the pattern out before it reaches a user.

Minimum build

.NET 11 Preview 7 or later.

Configurations to cover

  • Blazor Web App
    • Static SSR
    • Interactive Server
    • Interactive WebAssembly
    • Interactive Auto
  • Standalone WebAssembly
  • Hybrid (MAUI)

These diagnostics are produced by the compiler, so they do not vary by render mode. The configuration below is the one needed to reproduce the runtime behavior the warning predicts, not a matrix to work through.

Also exercise

  • Published output
  • An existing .NET 10 app upgraded to .NET 11
  • Trimming or ahead-of-time compilation
  • More than one server instance, or a proxy in front
  • Hot Reload
  • An IDE as well as the command line
  • Container

Setup

Id Title Code fix
BL0014 For loop iterator used inside a closure No

What to build

A component with a for loop rendering a list, covering each shape the analyzer is meant to flag:

  • A button whose @onclick lambda uses the loop counter.
  • A lambda that also takes event args and still uses the counter, @(e => Handle(e, i)).
  • An <input @bind="items[i]" />, where the counter is used as an indexer in a two-way binding.
  • A child component taking an EventCallback, <MyComponent OnClick="@(() => Handle(i))" />.
  • A child component whose ChildContent renders the counter, <MyComponent>Item @i</MyComponent>.

Then the shapes that are correct and must stay quiet:

  • The same loop with var index = i; copied inside the body.
  • The same list rendered with foreach.
  • foreach (var i in Enumerable.Range(0, items.Length)).

Show which item each control acted on, so the wrong behavior is visible rather than inferred.

Things to try

  • Building at the command line and in an IDE, and comparing what each reports.
  • Running the app and clicking every button in the flagged loop.
  • Typing into the @bind inputs and checking which item each one wrote to.
  • Copying the counter to a local inside the loop body, then rebuilding and clicking again.
  • Rewriting the loop as foreach, then rebuilding.
  • Using the counter in the loop body but not inside a closure, to see whether it is flagged.

Expected behavior

The pattern is flagged in every shape where it misbehaves, and the shapes that work are left alone.

Must hold

  • All five flagged shapes produce a BL0014 warning naming the iterator variable: the @onclick lambda, the lambda with event args, the @bind indexer, the EventCallback, and the ChildContent.
  • Clicking the buttons in the flagged loop reproduces the behavior the message predicts: they act on the final value rather than their own.
  • Typing into a flagged @bind input writes to the wrong item, matching what the warning predicts.
  • The copied-local loop, the foreach loop, and the foreach over Enumerable.Range produce no warning and behave correctly.
  • Using the counter outside a closure produces no warning.
  • The warnings appear both at the command line and in the IDE, at the same lines.

Expected differences between configurations

  • None. The warning is produced at compile time and is identical in every configuration.

Documentation to use

What to report

Report results using the format described in the validation testing manual. Include link to a repository with the test app.

Metadata

Metadata

Labels

ValidationThis issue is used to track validation effortsarea-blazorIncludes: Blazor, Razor Componentsvalidation-scenarioThis issue describes a validation testing scenario

Type

No type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions