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.