Course contentsShow
← C#
Lesson 502 of 1,9085. The .NET Runtime and BCL ConceptsFree lesson

The Impact of Capturing Variables

Identify how closure captures create hidden heap allocations in async methods.

In this lesson, you will learn how capturing local variables within an async method forces the .NET runtime to move those variables from the stack to the heap, creating hidden allocations.

The Problem with Async and the Stack

When you call a standard method, its local variables live on the stack. Once the method finishes, the stack frame is popped, and the memory is reclaimed instantly. However, async methods are different. Because they can suspend execution (using await) and resume later, the runtime cannot guarantee that the variables will still be on the stack when the method continues.

To solve this, the compiler performs a process called "closure capturing." It generates a hidden class (a state machine) and transforms your local variables into fields of that class.

Hidden Heap Allocations

When you capture a variable, it is "lifted" into that hidden class instance. Since class instances must live on the heap, every time you invoke an async method that captures a variable, the runtime performs an allocation on the managed heap.

Consider this example:

public async Task ProcessDataAsync(int value)
{
    // 'value' is a local variable. 
    // If we use it inside an async lambda or await 
    // in a way that requires state persistence, 
    // it moves from the stack to a heap-allocated object.
    await Task.Delay(100);
    Console.WriteLine(value);
}

In simpler scenarios, the compiler is smart about optimizing this, but as soon as your logic requires those variables to persist across an await point, they are no longer just stack-bound integers or references. They become part of the state machine's object graph. In performance-critical loops, creating these hidden objects on the heap triggers the Garbage Collector more frequently, increasing pressure on your allocation budget and potentially leading to generation promotions.

While closures are a powerful and necessary feature for writing clean asynchronous code, it is important to remember that they transform local, stack-based variables into heap-allocated fields of a hidden compiler-generated class.

Key Takeaway: Capturing variables in async methods forces the runtime to move variables from the stack to a heap-allocated state machine, which increases memory pressure and Garbage Collector activity.