CodeGym /Cursos /C# SELF /Tratamento de eventos: expressões lambda

Tratamento de eventos: expressões lambda

C# SELF
Nível 52, Lição 4
Disponível

1. Introdução

Em muitos projetos C# modernos quase não se usam métodos "comuns" para tratar eventos. Isso aconteceu porque expressões lambda permitem declarar rapidamente e de forma concisa um handler direto no ponto de assinatura (operador +=), quando o tratamento é simples e não é reutilizado em outras partes do código. É parecido com colar um bilhete pequeno direto na máquina de café com a instrução "pressione este botão" em vez de escrever um manual detalhado e guardar numa pasta separada. Se a tarefa for local e única, a lambda é ideal!

Onde isso é usado na prática

  • No ASP.NET (por exemplo, ao lidar com eventos do ciclo de vida da página),
  • No WPF/WinForms para UI (por exemplo, ao clicar em um botão),
  • Em programação de servidor (por exemplo, lógica dentro do pipeline),
  • Em testes, quando o handler não precisa de um nome separado.

Sintaxe: como isso fica no código

Vamos primeiro ver o tratamento de evento "tradicional":


// Declaração do evento
public event EventHandler? MyEvent;

// Assinatura do evento com um método comum
void Handler(object? sender, EventArgs e)
{
    Console.WriteLine("Evento ocorreu!");
}

public void Subscribe()
{
    MyEvent += Handler;
}

Agora — a versão com expressão lambda (função anônima):


public void Subscribe()
{
    MyEvent += (sender, e) => Console.WriteLine("Evento ocorreu (lambda)!");
}

Repare: não criamos um método separado, definimos o handler diretamente na assinatura do evento. A assinatura da lambda bate automaticamente com o tipo do evento (EventHandler).

Comparação de abordagens

Método comum Lambda
Volume de código Maior (método + assinatura) Menor, tudo no lugar
Reutilização Pode ser reutilizado Normalmente não
Localidade do código Distribuído Tudo junto
Clareza/legibilidade Bom para lógica complexa Ótimo para casos simples

2. Aplicação com tratamento de eventos e lambdas

Estrutura básica


public class Menu
{
    public event EventHandler? ItemSelected;

    public void SelectItem(int index)
    {
        Console.WriteLine($"Item do menu {index} selecionado.");
        ItemSelected?.Invoke(this, EventArgs.Empty);
    }
}

Assinatura com expressão lambda


class Program
{
    static void Main()
    {
        var menu = new Menu();

        // Assinatura no evento via lambda
        menu.ItemSelected += (sender, e) =>
        {
            Console.WriteLine("Obrigado pela sua escolha! O lambda-handler foi disparado.");
        };

        menu.SelectItem(1);
    }
}

Saída esperada:

Item do menu 1 selecionado.
Obrigado pela sua escolha! O lambda-handler foi disparado.

Captura de variáveis do contexto externo

Uma das vantagens da expressão lambda é que ela pode "lembrar" valores do escopo externo (closures). Por exemplo, contar quantas vezes um item do menu foi selecionado: a variável counter será capturada pelo closure.


static void Main()
{
    var menu = new Menu();
    int counter = 0;

    menu.ItemSelected += (s, e) =>
    {
        counter++;
        Console.WriteLine($"Item selecionado {counter} vez(es)!");
    };

    menu.SelectItem(1);
    menu.SelectItem(2);
}

Saída esperada:

Item do menu 1 selecionado.
Item selecionado 1 vez(es)!
Item do menu 2 selecionado.
Item selecionado 2 vez(es)!

Isso é a mágica dos closures em ação: a variável counter continua viva dentro da lambda!

Exemplo com parâmetros do evento

Se seu evento usa EventHandler<T>, onde T é uma classe própria com informação adicional, a expressão lambda simplesmente "se ajusta" à assinatura necessária.


public class MenuItemSelectedEventArgs : EventArgs
{
    public int ItemIndex { get; }
    public string Description { get; }

    public MenuItemSelectedEventArgs(int itemIndex, string description)
    {
        ItemIndex = itemIndex;
        Description = description;
    }
}

public class Menu
{
    public event EventHandler<MenuItemSelectedEventArgs>? ItemSelected;

    public void SelectItem(int index, string description)
    {
        Console.WriteLine($"Item do menu {index}: {description} selecionado.");
        ItemSelected?.Invoke(this, new MenuItemSelectedEventArgs(index, description));
    }
}

// Uso
static void Main()
{
    var menu = new Menu();

    // Lambda com desempacotamento dos argumentos do evento
    menu.ItemSelected += (sender, args) =>
    {
        Console.WriteLine($"Item escolhido #{args.ItemIndex}: {args.Description.ToUpper()}");
    };

    menu.SelectItem(3, "Sobre o programa");
}

Saída esperada:

Item do menu 3: Sobre o programa selecionado.
Item escolhido #3: SOBRE O PROGRAMA

3. Handlers locais e lambda

Lambdas são ideais quando:

  • A lógica do handler é curta e clara,
  • O handler é usado só em um lugar,
  • É preciso "capturar" variáveis do contexto local.

Se o tratamento é complexo, precisa ser reutilizado ou pode ser chamado fora do local de declaração, é melhor usar um método nomeado separado.

Exemplo: com lógica dentro e fora

Lambda (ideal):


button.Click += (s, e) => MessageBox.Show("Botão pressionado!");

Métodos (quando a lógica é mais complexa ou precisa ser reutilizada):


button.Click += Button_Click;

void Button_Click(object sender, EventArgs e)
{
    if (UserConfirmed())
    {
        SaveData();
        MessageBox.Show("Dados salvos!");
    }
}

4. Por baixo do capô: o que acontece com lambda-handlers

A lambda também é um delegate, assim como um handler comum. O compilador gera "on the fly" um método anônimo, e se houver variáveis capturadas, também gera uma classe escondida para armazená-las.

É importante lembrar (e isso é um erro comum): se você cria uma lambda dentro de um loop e a assina a um evento, todas as iterações podem capturar a mesma variável do loop.


for (int i = 0; i < 5; i++)
{
    buttons[i].Click += (sender, e) =>
    {
        Console.WriteLine($"Clique no botão #{i}");
    };
}

Para todos os handlers, o número do botão pode acabar sendo 5! Para evitar isso, crie uma cópia da variável dentro do loop:


for (int i = 0; i < 5; i++)
{
    int buttonIndex = i; // Cópia local
    buttons[i].Click += (sender, e) =>
    {
        Console.WriteLine($"Clique no botão #{buttonIndex}");
    };
}

Agora tudo funciona como esperado.

O poder dos lambda-handlers: vida real

Essas expressões lambda economizam tempo e aceleram o desenvolvimento, especialmente quando a lógica é simples. Elas deixam o código "próximo da tarefa", sem espalhá-lo por arquivos e classes. Em projetos reais você vai encontrar lambdas por toda parte: desde tratamento de eventos na UI até assinatura de mensagens em buses de eventos assíncronos.

5. Erros típicos

Erro nº1: esquecer de desinscrever (-=) em objetos de longa duração.
Se o objeto assinante não se desinscrever do evento do publisher, a referência no delegate mantém o assinante na memória — o garbage collector não vai coletá-lo, mesmo que não haja outras referências ao objeto. Isso causa "vazamentos" de memória e dependências pendentes, especialmente quando o publisher vive por muito tempo (eventos estáticos, singletons, serviços).
Como evitar: sempre desinscreva ao liberar/destruir (por exemplo, Dispose, OnDisable, OnDestroy). Considere weak events / WeakEventManager, usar um padrão de "event manager" ou IObservable/Rx se o cenário for complexo.

Erro nº2: capturar variáveis do loop sem cópia local.
Uma armadilha típica é escrever a assinatura dentro de um loop e o closure capturar a mesma variável do loop, então todos os handlers veem o valor final dela, não o valor que existia quando o handler foi criado. Isso leva a resultados inesperados (todos os handlers "imprimem" o mesmo número, etc.).
Como evitar: dentro do loop crie uma cópia local do valor e capture-a:


for (int i = 0; i < n; i++)
{
    int current = i;
    button.Click += (s, e) => Handle(current);
}

Ou passe o valor necessário para um método wrapper. Isso é mais robusto e deixa a intenção clara.

Erro nº3: usar lambdas enormes no ponto de assinatura.
Se você coloca lógica de negócio grande dentro de uma função anônima na assinatura, o código fica difícil de ler, testar e depurar. Além disso — com lambdas anônimas é mais difícil desinscrever corretamente, porque você precisa do mesmo instance do delegate. Como resultado, a lógica se espalha e perde estrutura.
Como evitar: extraia lógica complexa para métodos nomeados ou services; se precisar, armazene o delegate em uma variável/field para poder desinscrever; deixe na lambda apenas um pequeno wrapper/roteamento. Isso melhora legibilidade e manutenibilidade do código.

2
Tarefa
C# SELF, nível 52, lição 4
Bloqueado
Botão com dados personalizados
Botão com dados personalizados
1
Pesquisa/teste
Eventos em C#, nível 52, lição 4
Indisponível
Eventos em C#
Criando eventos usando delegates
Comentários
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION