Access To Modified Closure

ID

csharp.access_to_modified_closure

Severity

low

Remediation Complexity

trivial

Remediation Risk

low

Remediation Effort

low

Resource

Reliability

Language

CSharp

Tags

closure, reliability

Description

Reports references inside a lambda or anonymous method to a local variable that is mutated elsewhere in the enclosing function. Capturing a mutable local in a closure binds the variable, not its value at capture time, so the closure sees whatever value the variable has when the closure is later invoked.

Rationale

The classic foot-gun is a loop building a list of closures, each of which reads the loop variable. All closures share the same variable, so by the time they execute they all see the final value of the loop counter. Older C# (pre-5) had the same issue with foreach; even modern code that captures a for counter or any local that an outer caller reassigns falls into the same trap.

public IEnumerable<Action> Build()
{
    var actions = new List<Action>();
    for (int i = 0; i < 3; i++)
    {
        actions.Add(() => Console.WriteLine(i));         // FLAW — captured i mutates with the loop
    }
    return actions;
}

public IEnumerable<Action> Ok()
{
    var actions = new List<Action>();
    for (int i = 0; i < 3; i++)
    {
        int copy = i;
        actions.Add(() => Console.WriteLine(copy));      // OK — fresh local per iteration
    }
    return actions;
}

Remediation

Copy the captured local into a new variable scoped inside the loop body (or the block that creates the closure) so that each closure binds its own copy. With foreach in modern C#, the loop variable is already fresh per iteration; with for loops or any explicit mutation pattern, introduce the copy yourself.