C# > Functional Programming > Lambdas and Expressions > Closures in C#
Closure Example: Event Handler with Captured State
This snippet demonstrates how closures can be used to create event handlers that maintain state. A button click event handler is created which increments and displays a counter associated with that specific button. Each button's event handler has its own captured 'clickCount' variable, effectively creating an independent counter for each button.
Code Snippet
The AddClickHandler
method attaches an event handler to a button's Click
event. Inside AddClickHandler
, a local variable clickCount
is declared and initialized. The lambda expression that serves as the event handler 'closes over' this clickCount
variable. Each button receives its own call to AddClickHandler
, resulting in each button having its *own* independent clickCount
variable. The lambda expression increments and displays this button-specific count each time the button is 'clicked'.
// This example is simplified and assumes a WinForms or WPF context.
// To run this you would need to adapt to a suitable UI framework.
using System;
public class EventClosureExample
{
// Simulate a Button class
public class Button
{
public event EventHandler Click;
public string Name { get; set; }
public void SimulateClick()
{
Click?.Invoke(this, EventArgs.Empty);
}
}
public static void Main(string[] args)
{
// Simulate button creation
Button button1 = new Button { Name = "Button1" };
Button button2 = new Button { Name = "Button2" };
// Create event handlers with closures
AddClickHandler(button1);
AddClickHandler(button2);
// Simulate button clicks
button1.SimulateClick(); // Output: Button1 clicked 1 times.
button1.SimulateClick(); // Output: Button1 clicked 2 times.
button2.SimulateClick(); // Output: Button2 clicked 1 times.
button1.SimulateClick(); // Output: Button1 clicked 3 times.
button2.SimulateClick(); // Output: Button2 clicked 2 times.
}
public static void AddClickHandler(Button button)
{
int clickCount = 0; // Local variable captured by the closure.
button.Click += (sender, e) =>
{
clickCount++;
Console.WriteLine($"{button.Name} clicked {clickCount} times.");
};
}
}
Concepts Behind the Snippet
This example highlights the power of closures in event-driven programming. Each event handler needs to maintain its own state (in this case, the number of clicks). Closures provide a clean and natural way to associate state with a specific event handler instance without resorting to global variables or complex class hierarchies.
Real-Life Use Case
This pattern is common in GUI applications. Imagine a form with multiple text boxes. Each text box might have a TextChanged
event handler that needs to perform some specific action based on the text entered into *that* text box. Closures allow each event handler to access and manipulate data associated with its specific text box.
Best Practices
using
statement or implement IDisposable
.
Interview Tip
This example is a great way to demonstrate your understanding of closures in a practical context. Be prepared to explain how each button gets its own independent counter and why a simple global variable would not work correctly in this scenario.
When to Use Them
Use closures for event handlers when you need to associate state with the event handler and the state should be specific to the event source (e.g., a particular button, text box, or control).
Memory Footprint
The memory footprint of a closure in this scenario is relatively small. It primarily consists of the captured clickCount
variable (an integer) and a reference to the button object. The overall memory overhead is typically negligible unless you are creating a very large number of buttons and closures.
Alternatives
One alternative is to create a custom class that represents the button and its associated click count. The event handler would then be a method of this class. While this approach avoids closures, it can be more verbose and less elegant, especially for simple event handling scenarios. Another alternative could be to use a dictionary to map each button to its corresponding count, but this adds complexity to the management of the dictionary.
Pros
Cons
FAQ
-
Can I access the captured variables from outside the closure?
No, the captured variables are private to the closure. You cannot directly access them from outside the closure. This encapsulation is a key benefit of using closures. -
What happens if the button is disposed of? Will the closure prevent it from being garbage collected?
The closure will hold a reference to the button. Therefore, the button will not be garbage collected as long as the closure is still referenced (e.g., if the event handler is still attached to the button'sClick
event). It's important to detach the event handler when the button is no longer needed to allow the button to be garbage collected. -
Does the closure capture the button object by value or by reference?
The closure captures the button object by reference. This means that if the properties of the button object are changed after the closure is created, those changes will be reflected inside the closure. In this example, theName
property is accessed, so changes to it *would* be reflected.