Phase 10 of 30 · Topic 10.2

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.