1. Introdução
Em programação closure — não é uma maneira de fechar a porta no JavaScript, e sim um mecanismo pelo qual uma expressão lambda ou um método anônimo captura variáveis do contexto ao redor e "lembra" delas mesmo depois que o bloco onde foram declaradas terminou. Simplificando, um closure é uma função que lembrou as condições em que nasceu, e guarda esses valores como uma pequena malinha com pertences pessoais (variáveis).
Closure — é uma função junto com o ambiente (scope) que existia no momento da sua criação.
Exemplo mais simples de closure
Vamos ver na prática:
Func<int> MakeCounter()
{
int count = 0;
return () =>
{
count++;
return count;
};
}
Chamando assim:
var counter = MakeCounter();
Console.WriteLine(counter()); // 1
Console.WriteLine(counter()); // 2
Console.WriteLine(counter()); // 3
Como isso funciona?
- A variável count é declarada dentro do método MakeCounter.
- A lambda () => { ... } é retornada para fora e agora vive fora do método.
- Mas! Ela lembra a variável count, mesmo que o próprio método MakeCounter já tenha terminado.
Isso é um closure: a lambda "capturou" a variável count do contexto ao redor.
O que exatamente a lambda "captura"?
- variáveis locais do método envolvente (scope),
- parâmetros do método,
- variáveis em blocos (for, foreach, etc.).
Importante: as variáveis são capturadas não por valor, mas por referência! Se mudarmos a variável dentro do closure, ela mudará "por fora" também. Na prática o compilador C# cria uma classe auxiliar para essas variáveis — mas é suficiente lembrar conceitualmente disso para usar closures com confiança.
2. Closure e escopo léxico
Vamos melhorar o exemplo da "fábrica de funções" com closure:
Func<int, int> PowerFactory(int power)
{
return x =>
{
int result = 1;
for (int i = 0; i < power; i++)
result *= x;
return result;
};
}
Uso:
var square = PowerFactory(2); // x^2
var cube = PowerFactory(3); // x^3
Console.WriteLine(square(5)); // 25
Console.WriteLine(cube(2)); // 8
As funções square e cube foram criadas com valores diferentes da variável power, e cada uma lembra o seu. Para cada chamada de PowerFactory é criado uma "mochila" própria com os valores capturados.
3. Mutação de variáveis capturadas
Às vezes surge a pergunta: o que acontece se num loop criarmos várias lambdas que capturam a variável do loop? Aí é fácil pisar na armadilha.
Exemplo: closure no loop
var actions = new List<Action>();
for (int i = 0; i < 3; i++)
{
actions.Add(() => Console.WriteLine(i));
}
foreach (var action in actions)
action(); // ???
O que você espera? 0, 1, 2? Na prática será:
3
3
3
Por que isso acontece? Todas as lambdas "referenciam" a mesma variável i. No fim do loop i já vale 3, e é esse valor que todas as nossas Action vão ver.
Versão corrigida
Para que cada lambda capture seu próprio valor, crie uma nova variável dentro do corpo do loop:
var actions = new List<Action>();
for (int i = 0; i < 3; i++)
{
int copy = i;
actions.Add(() => Console.WriteLine(copy));
}
foreach (var action in actions)
action(); // 0 1 2
Agora copy é uma nova variável em cada iteração, e o closure captura exatamente ela.
4. Aplicações de closures em problemas reais
Processamento de dados e callbacks
Quando você faz algo assíncrono ou adia a execução (handlers de eventos, filtragem, agendamento de tarefas) — closure permite "embalar" a lógica junto com os parâmetros. Por exemplo:
void ProcessList(List<int> list, int threshold)
{
var filtered = list.Where(x => x > threshold);
foreach (var item in filtered)
Console.WriteLine(item);
}
Aqui a lambda dentro de Where captura a variável threshold.
Criando "fábricas" de funções
Você passa um parâmetro — recebe uma função com esse parâmetro "embutido". Esse padrão é útil pra configurar filtros, comparadores de ordenação, reações de UI etc.
Gerenciamento de estado
Às vezes é preciso manter um pouco de estado sem criar uma classe separada:
Func<string, string> CreateGreeting()
{
string prefix = "Hello";
return name =>
{
return $"{prefix}, {name}!";
};
}
5. Nuances úteis
Debaixo do capô: como closure funciona em C#
Tudo que é capturado pela lambda o compilador transforma em uma classe auxiliar: variáveis viram campos, e a lambda vira um método. Por isso o estado das variáveis "vive" entre chamadas.
Cada "fábrica" gera um pequeno objeto. Isso é normal — o .NET gerencia esses objetos de forma eficiente e os aloca só quando realmente necessário.
Lembrete: não capture objetos "grandes" sem necessidade
Se você capturar um objeto grande (por exemplo, uma UI form), ele não será liberado enquanto a lambda estiver viva. A razão clássica de vazamento é assinar eventos com uma lambda que capturou um contexto "pesado" e não desassinar (+=/-=).
Closures e vida de coleções — exemplo com LINQ
Closures tornam o LINQ flexível: filtros lembram seus parâmetros.
List<string> colors = new List<string> { "Red", "Green", "Blue", "Yellow" };
string startsWith = "B";
var filtered = colors.Where(c => c.StartsWith(startsWith));
foreach (var color in filtered)
Console.WriteLine(color); // "Blue"
Se depois você mudar startsWith, o resultado também muda:
startsWith = "R";
foreach (var color in filtered)
Console.WriteLine(color); // "Red"
Isso acontece porque o closure referencia a mesma variável startsWith, e o método StartsWith checa o valor atual cada vez.
6. Erros típicos ao trabalhar com closures
Erro nº1: capturar uma variável que muda antes do uso.
Situação clássica em loops: a lambda dentro do closure "olha" para a mesma variável do loop, que no momento da chamada já tem outro valor. No fim a função trabalha com dados diferentes dos esperados. Resolve-se introduzindo uma variável separada dentro do loop.
Erro nº2: capturar contexto grande demais.
O closure carrega o objeto inteiro em vez de um campo/valor específico. Isso cria dependências desnecessárias e deixa o código mais complexo. Capture só o que precisa.
Erro nº3: manter recursos pesados e vazamento de memória.
Assinar um evento com uma lambda que capturou um objeto "pesado" e não desassinar — o objeto não é liberado. Fique de olho no tempo de vida das assinaturas e use desinscrição explícita (-=).
Erro nº4: perda de gerenciabilidade do código.
Uso excessivo de closures torna difícil entender de onde vêm os dados e quando eles mudam, especialmente quando o closure é declarado longe do ponto de chamada. Mantenha a lógica próxima e não abuse.
GO TO FULL VERSION