Closures & Compiler-Generated `DisplayClass` Allocations
1Concept
When a lambda captures an outer local variable, the Roslyn compiler synthesizes a hidden heap class (`<>c__DisplayClass`). In high-frequency loops, this creates severe memory allocation churn.
2Architecture Diagram
int threshold = 100;
items.Find(x => x > threshold); // Captures threshold!
│
▼
Compiler Generates:
class DisplayClass { public int threshold; }
DisplayClass env = new DisplayClass { threshold = 100 }; // HEAP ALLOCATION!3Code Example
C# 13 & .NET 9
using System;
using System.Collections.Generic;
public class ClosureAllocationDemo
{
public static void Main()
{
int multiplier = 10;
// Captures 'multiplier' -> Allocates DisplayClass on heap!
Func<int, int> scale = x => x * multiplier;
// Static lambda: compiler guarantees ZERO capture allocation
Func<int, int> doubleVal = static x => x * 2;
Console.WriteLine($"Scaled: {scale(5)} | Doubled: {doubleVal(5)}");
}
}4Expected Output
Scaled: 50 | Doubled: 10
5Key Takeaways
- ✓Use `static` lambdas (`static (x) => ...`) to guarantee zero variable captures.
- ✓Passing state through method overloads (`state` parameter) avoids closure allocations.
- ✓Be cautious with loop variables captured in lambdas.