CodeGym /Courses /C# SELF /Capturing Variables with Local Functions

Capturing Variables with Local Functions

C# SELF
Level 11 , Lesson 5
Available

1. Introduction

Remembering what scope is

Let's imagine variables are employees in a big office, and methods, loops, and code blocks are rooms and offices. Some employees are only allowed in their own room, while others can go anywhere in the building. Where each person can be — that's their scope (scope).

Scope defines where a declared variable is “visible” in your program and where you can use it.

Main types of scopes

In C#, you can highlight these main scopes:

Scope Example Where the variable is "visible"
Local Inside a method or block Only inside that block
Method parameter In the method signature Only inside the method
Class variable (field) In the class body outside methods In all methods of that class
Variable inside a loop/condition Inside
{}
of a loop/if
Only inside those
{}

Example with explanations

public class Office
{
    
  
int buildingNumber = 50; // Class field: visible in all methods public void PrintInfo() {
int roomNumber = 101; // Local variable: only visible inside PrintInfo if (roomNumber > 100) {
int deskNumber = 5; // Only visible inside this if block Console.WriteLine(deskNumber);
} Console.WriteLine(deskNumber); // Error! deskNumber isn't visible here anymore
}
}

2. Local functions and scope

Who can "see" whom?

When you declare a local function inside a method (or even inside a loop or condition), it ends up in the same scope as the variables declared above. A local function is kinda like “part of the same room.”

Example

A local function can see variables from the surrounding scope


    void PrintWithPrefix(string message)
{
    
  
string prefix = "[LOG]: "; void Print() {
Console.WriteLine( prefix + message); // sees both variables!
} Print();
}

Here, the variables prefix and message are visible inside the local function Print because they're declared in the same or a wider scope.

How many scopes are there?

In the example above:

  • there's the scope of the method PrintWithPrefix
  • inside it — the scope of the function Print

Local functions don't create their own separate “room” completely: they can see everything that's in the hallway (the method).

3. Capturing variables (Capture)

Capturing variables is when a local function uses variables that were declared outside this function, but in the same scope.

Local functions and anonymous methods (lambda expressions, we'll get to those soon) remember (or “capture”) all the variables that were available to them when they were declared.

You can say that functions kinda take a snapshot (capture) of the surrounding world — and can use those variables even when they're called much later.

Diagram

Main method
└─ variable x
└─ local function F() ← "captures" x

Example — the simplest capture

void CounterExample()
{
    int counter = 0;

    void Increase()
    {
        counter++; // This function captures the variable counter
    }

    Increase();
    Increase();
    Console.WriteLine(counter); // Will print 2
}
The local function captures a variable from the outer scope

Here, after two calls to the local function Increase, the value of counter increases to 2.

4. Using variable capture

Capturing variables lets you easily “pass” data between the method scope and local functions, without messing around with extra parameters.

If there was no capture, you'd have to pass all the variables as parameters:

void CounterExampleWithoutCapture()
{
    int counter = 0;

    void Increase(ref int c)
    {
        c++;
    }

    Increase(ref counter);
    Increase(ref counter);
    Console.WriteLine(counter);
}

That's annoying — why keep writing ref and mess up the function signature, if it can just “see” the variables outside?

5. Local functions and variable lifetime after the method exits

Do variables live longer than the method?

If you pass a local function (or delegates with lambdas) somewhere outside the current method, the captured variables automatically stop “dying” when the method exits. The CLR (.NET virtual machine) will take care of it — everything you need will be “held by the ears” in memory.

Example: functions live outside the method

Func<int> GetCounter()
{
    int count = 0;
    int Increment()
    {
        count++;
        return count;
    }
    return Increment; // Returning the function outside!
}

var counter = GetCounter();
Console.WriteLine(counter()); // 1
Console.WriteLine(counter()); // 2
Closure: the variable count lives after the method exits

Here, even after the GetCounter method finishes, the variable count keeps living, because the returned function captured it. This is called a closure — we'll get to that in a separate lecture, but for local functions the mechanism is the same.

6. Typical mistakes and interesting scenarios

Overwriting a variable before calling the local function

Sometimes you might run into a situation where the variable captured by a local function gets changed before you call the function — and then the result might be different from what you expected.

Example:


void Example()
{
    int x = 42;
    void PrintX() {  Console.WriteLine(x);  } 

    x = 100; // Changed the variable!
    PrintX(); // Will print 100, not 42!
}

Trick: A local function always sees the latest value of the variable at the moment you call it.

Capturing variables in a for/foreach loop (again)

Classic pain: if you're writing logic in an interview or a big project, always check: am I capturing a “live” variable from a loop, and how will it behave?

2
Task
C# SELF, level 11, lesson 5
Locked
Simple use of a local function and variable capture
Simple use of a local function and variable capture
2
Task
C# SELF, level 11, lesson 5
Locked
Initializing a numeric counter with a local function
Initializing a numeric counter with a local function
1
Survey/quiz
Tuples and Local Functions, level 11, lesson 5
Unavailable
Tuples and Local Functions
Advantages of tuples over out and anonymous types
Comments
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION