C# > Functional Programming > Lambdas and Expressions > Closures in C#
Closure Example: Counter with Encapsulation
This code demonstrates how closures in C# can be used to create a simple counter with encapsulated state. The CreateCounter
function returns another function (a lambda expression) that increments and returns the counter's value. The important aspect is that the returned function 'closes over' the count
variable, maintaining its state even after CreateCounter
has completed.
Code Snippet
The CreateCounter
function defines a local variable count
and returns a lambda expression. This lambda captures the count
variable from its surrounding scope. Each time the returned lambda is called, it increments the captured count
and returns the updated value. Crucially, each call to CreateCounter
creates a new count
variable that is independent of other calls. This means counter1
and counter2
each have their own private counter.
using System;
public class ClosureExample
{
public static Func<int> CreateCounter()
{
int count = 0;
// This lambda expression 'closes over' the 'count' variable.
return () =>
{
count++;
return count;
};
}
public static void Main(string[] args)
{
Func<int> counter1 = CreateCounter();
Func<int> counter2 = CreateCounter();
Console.WriteLine("Counter 1: " + counter1()); // Output: Counter 1: 1
Console.WriteLine("Counter 1: " + counter1()); // Output: Counter 1: 2
Console.WriteLine("Counter 2: " + counter2()); // Output: Counter 2: 1
Console.WriteLine("Counter 1: " + counter1()); // Output: Counter 1: 3
Console.WriteLine("Counter 2: " + counter2()); // Output: Counter 2: 2
}
}
Concepts Behind the Snippet
A closure is a function that has access to variables from its surrounding (lexical) scope, even after the scope has finished executing. In this example, the lambda expression returned by CreateCounter
'closes over' the count
variable. This means the lambda retains access to the count
variable's memory location, allowing it to modify and read its value between calls.
Real-Life Use Case
Closures are incredibly useful in scenarios where you need to maintain state across multiple function calls. Examples include event handlers, iterators, and managing resources. Imagine a GUI application: event handlers for button clicks might use closures to access and modify data specific to that button or window. Another example is creating a custom iterator where the 'current' position needs to be remembered between calls to MoveNext()
.
Best Practices
Interview Tip
When asked about closures, be sure to explain the concept of lexical scoping and how a function 'remembers' its environment even after the surrounding scope has exited. Be prepared to discuss potential pitfalls, such as the loop variable capture problem. Demonstrating an understanding of variable lifetimes is also crucial.
When to Use Them
Use closures when you need to encapsulate state and behavior together. They're particularly useful for creating callbacks, event handlers, and custom iterators. They offer a clean and concise way to manage state compared to using separate classes or structures.
Memory Footprint
Closures can have a higher memory footprint than simple functions because they need to store the captured variables. The size of the footprint depends on the size and number of captured variables. However, the increased complexity is often justified by the increased flexibility and expressiveness they provide. Always profile your code if memory usage is a critical concern.
Alternatives
Alternatives to closures include using classes or structs to explicitly store the state and methods that operate on that state. While this approach is more verbose, it can sometimes offer better control over memory management and make the code easier to reason about, especially in complex scenarios. Another alternative is to use local functions in C# which can sometimes offer a more readable and scoped alternative.
Pros
Cons
FAQ
-
What happens if I capture a variable that is later modified outside the closure?
Since closures typically capture variables by reference, changes made to the variable outside the closure *will* be reflected inside the closure, and vice versa. This can be both a powerful feature and a potential source of bugs, so be careful! -
Are closures the same as anonymous methods?
Closures are a broader concept. Anonymous methods (or lambda expressions) are a way to *create* closures. A closure is essentially an anonymous method that captures variables from its surrounding scope. -
Do closures only capture local variables?
Closures can capture any variable that is accessible in the scope where they are defined, including local variables, method parameters, and even class members (if the closure is defined within a class).