Alura > Cursos de Programação > Cursos de .NET > Conteúdos de .NET > Primeiras aulas do curso C#: Eventos, Delegates e Lambdas

C#: Eventos, Delegates e Lambdas

Delegates - Apresentação

Olá, estudante! Eu sou o instrutor Marcelo Oliveira do curso "C# Eventos, Delegates e Lambdas".

Audiodescrição: Marcelo se declara como um homem branco. Tem com olhos escuros e barba e cabelos curtos e grisalhos. Ele está usando um óculos retangular de armação fina preta e vestindo uma camiseta cinza. Ao fundo, há uma parede iluminada por uma luz LED azul.

Público-alvo

Este curso foi desenvolvido para quem deseja aprender:

Para isso, seremos responsáveis pelo banco Bytebank, ajudando a criar um simulador de caixa eletrônico.

O que aprenderemos

Na Aula 01, aprenderemos sobre Delegates em C#. Iniciaremos um projeto Console Application, aprendendo a realizar operações bancárias de um caixa eletrônico. Entenderemos como os Delegates funcionam como intermediários entre um chamador e o método alvo.

Na Aula 02, introduziremos os eventos em C#, modificando nossa aplicação Console Application. Na Aula 03, nosso simulador de caixa eletrônico terá uma interface gráfica que ajudará a entender melhor os eventos em C#.

Aprenderemos a programar os eventos em uma aplicação de duas formas: tanto para a interface gráfica, com uma pessoa usuária, quanto para responder aos eventos da nossa camada de negócios. Demonstraremos um fluxo de eventos, no qual a pessoa usuária realiza um saque, produzindo o evento no clique do botão "Sacar". Esses eventos disparam diferentes ações, caso o saque seja bem-sucedido ou se o saldo for insuficiente.

Na Aula 04, aprenderemos o que são as expressões lambda. Veremos que as expressões lambda simplificam a sintaxe do nosso código, contendo parâmetros, o operador lambda e o corpo do lambda.

Na Aula 5, trabalharemos com o lambda no contexto de consultas LinQ. Começaremos aprendendo como os lambdas permitem o código assíncrono, com o operador await e o modificador async.

Depois, usaremos o lambda para filtrar, ordenar e retornar resultados a partir de uma consulta LinQ. Ao final, reescreveremos nossa consulta LinQ para usar a sintaxe de consulta, que se assemelha à linguagem SQL utilizada em bancos de dados.

O conhecimento em eventos, Delegates e lambdas, adquirido neste curso, certamente fará a diferença em seus projetos. Aproveite os recursos da plataforma Alura, pois além dos vídeos, também temos atividades, apoio do fórum e da comunidade do Discord.

Vamos estudar?

Delegates - Criando e usando delegates

Nesta aula, aprenderemos sobre delegates em C#, criando e Usando delegates.

Conhecendo o código

Atualmente, trabalhamos no desenvolvimento do banco Bytebank e iniciaremos um projeto de exemplo que simula o funcionamento de uma máquina de caixa eletrônico simples. No Visual Studio, abriremos o código do projeto Bytebank.Console, que conseguimos na atividade "Preparando o Ambiente".

Esse projeto conta com duas classes principais: a classe CaixaEletronico, que contém o código responsável por realizar as operações bancárias, e a classe Program, responsável por imprimir o console com as opções de menu. As quatro operações do menu são:

Essas opções estão no arquivo Program.cs, dentro do método MostrarMenu(), na linha 24.

Arquivo Program.cs

static void MostrarMenu()
{
    Console.WriteLine("\nEscolha uma opção:");
    Console.WriteLine();
    Console.WriteLine("1. Saldo");
    Console.WriteLine("2. Depositar valores");
    Console.WriteLine("3. Sacar valores");
    Console.WriteLine("4. Extrato");
    Console.WriteLine("5. Depositar e obter saldo");
    Console.WriteLine("6. Sacar e obter saldo");
    Console.WriteLine("7. Depositar, aplicar na poupança e obter saldo");
    Console.WriteLine();
    Console.Write("Digite o número da opção desejada: ");
}

Mais abaixo, a partir da linha 59, encontramos a declaração de um objeto caixaEletronico, que é uma instância da classe CaixaEletronico.

static CaixaEletronico caixaEletronico = new CaixaEletronico();

A partir da linha 61, temos os métodos que chamam as operações bancárias na classe CaixaEletronico, através do objeto caixaEletronico instanciado na linha 59. A partir da linha 61, temos quatro operações: saldo, depositar na linha 66, sacar na linha 73 e extrato na linha 79.

private static void Saldo()
{
    caixaEletronico.Saldo();

}

private static void Depositar()
{
    caixaEletronico.Depositar(100);
    caixaEletronico.Depositar(40);
    caixaEletronico.Depositar(25);
}

private static void Sacar()
{
    caixaEletronico.Depositar(100);
    caixaEletronico.Depositar(40);
    caixaEletronico.Depositar(25);
}

private static void Extrato()
{
    caixaEletronico.Extrato();

}

Precisamos agrupar esses métodos para que nosso código fique mais modular e flexível. O CaixaEletronico pode futuramente comportar mais operações agrupadas, e esse agrupamento facilitará nossa vida e de outras pessoas, quando o projeto escalar. Quando o projeto crescer e precisarmos alterar o código, ele já estará organizado e escalável.

Agruparemos primeiro as operações que não fazem movimentação de valores, ou seja, o Saldo() e o Extrato(). Passaremos ambos para um novo método chamado ConsultaBancaria(). As operações que movimentam valores, como Depositar() e Sacar(), serão agrupadas em outro método para realizar transações bancárias.

Métodos de agrupamento de operações

O Bytebank forneceu um código que copiaremos e colaremos no Program.cs para implementar esse agrupamento. O código que precisamos copiar é:

private static void ExecutarConsultaBancaria(consultaBancaria)
{
    consultaBancaria();
}

private static void ExecutarTransacaoBancaria(transacaoBancaria, decimal valor)
{
    transacaoBancaria(valor);
}

Após o fechamento de chaves do Extrato(){}, no final da classe Program, pressionaremos "Enter" duas vezes e colaremos o código. Com isso, temos dois novos métodos.

internal class Program
{

    // código omitido

    private static void ExecutarConsultaBancaria(consultaBancaria)
    {
        consultaBancaria();
    }

    private static void ExecutarTransacaoBancaria(transacaoBancaria, decimal valor)
    {
        transacaoBancaria(valor);
    }
    
    private static void ExecutarConsultaBancaria(consultaBancaria)
    {
        consultaBancaria();
    }

    private static void ExecutarTransacaoBancaria(transacaoBancaria, decimal valor)
    {
        transacaoBancaria(valor);
    }
}

O ExecutarConsultaBancaria(), recebe consultaBancaria como parâmetro do método, que pode ser Saldo ou Extrato. O ExecutarTransacaoBancaria, recebe dois parâmetros: transacaoBancaria, o método que pode ser Sacar ou Depositar, e um valor decimal, que é o valor da transação. Dentro do segundo método, temos a transacaoBancaria() recebendo exatamente esse valor.

Conhecendo os delegates

Para implementar essa atualização no nosso código, utilizaremos os delegates, então precisamos entender como eles funcionam. Para entender delegates, precisamos pensar no nosso objeto alvo, no caso, o caixa eletrônico. Dentro dele temos o método alvo, as nossas quatro operações bancárias.

À esquerda desse objeto, temos o chamador, representado pela classe Program. Atualmente, o chamador executa diretamente o método alvo. Com os delegates, teremos um intermediário.

Diagrama ilustrando o conceito de callback e delegate em programação. À esquerda, um retângulo rotulado "chamador" com uma linha conectando-o a um círculo rotulado "callback" no centro. O círculo "callback" está ligado a outro círculo intitulado "delegate". Uma segunda linha de "callback" conecta-se a um retângulo maior à direita, com uma inscrição "objeto alvo" e um retângulo interno menor rotulado "método alvo".

O chamador (classe Program), chamará um delegate, e esse delegate, fará a chamada para o método alvo. O método alvo retornará, via callback, para o delegate, retornará o callback para o chamador. O delegate é um intermediário entre o chamador e o método alvo.

A declaração de um delegate define a assinatura de um método callback. A instância do delegate armazenará duas referências. A primeira é do objeto alvo, nesse caso, a classe CaixaEletronico, e segunda é dos métodos alvos. Essa referência do método também é chamada de token.

Implementando Delegantes no ByteBank

Implementaremos o código com delegates, então retornaremos à classe Program.

O primeiro passo é encontrarmos a declaração do caixaEletronico, logo acima da chamada do Saldo(). Logo abaixo dela, declaramos o delegate de consulta bancária, começando com a palavra reservada delegate. Depois, precisamos passar o tipo de retorno do método, que será de consulta.

A consulta pode ser saldo ou extrato. Ao passarmos o mouse sobre o método Saldo(), descobrimos que o retorno é void, então escreveremos delegate void. Depois escrevemos o nome do delegate com os parênteses: ConsultaBancaria(). Como os métodos que chamaremos com o ConsultaBancaria() não recebem parâmetros, ele também ficará vazio.

Repetiremos o processo para criar o segundo delegate, logo na linha abaixo. Dessa vez será o TransacaoBancaria(), também do tipo void. Os métodos desse delegate recebem como parâmetro o um valor decimal, então passaremos o decimal valor como parâmetro.

Arquivo Program.cs

static CaixaEletronico caixaEletronico = new CaixaEletronico();
delegate void ConsultaBancaria();
delegate void TransacaoBancaria(decimal valor);

private static void Saldo()
{
    caixaEletronico.Saldo();
}

// código omitido

Com os dois delegates criados, faremos algumas mudanças no nosso código.

Ao retornarmos para o método ExecutarConsultaBancaria(), no final do código, notamos que ainda não temos o tipo consultaBancaria, que está nesse parâmetro. Ele precisa ser o nosso delegate, que representará os dois métodos de consulta saldo e extrato.

Para resolvermos, escreveremos ConsultaBancaria consultaBancaria como parãmetro. O mesmo se aplica ao ExecutarTransacaoBancaria(), que também receberá um delegate como parâmetro.

Arquivo Program.cs

// código omitido

private static void ExecutarConsultaBancaria(ConsultaBancaria consultaBancaria)
{
    consultaBancaria();
}

private static void ExecutarTransacaoBancaria(TransacaoBancaria transacaoBancaria, decimal valor)
{
    transacaoBancaria(valor);
}

Transformando as operações bancárias

Agora podemos fazer substituições no código para fazermos a chamada para as operações bancárias de forma indireta, com delegates, e não mais de forma direta. Para isso, modificaremos os métodos.

Dentro do método Saldo(), comentaremos o código que está entre chaves, e criaremos uma execução indireta através do delegate, chamando o ExecutarConsultaBancaria().

private static void Saldo()
{
    //caixaEletronico.Saldo();
    ExecutarConsultaBancaria(caixaEletronico.Saldo);
}

No método Depositar(), comentaremos as três linhas para substituímos também pelo ExecutarTransacaoBancaria(), passando como argumento caixaEletronico.Depositar seguido pelo valor da transação, separados por vírgula.

private static void Depositar()
{
    //caixaEletronico.Depositar(100);
    //caixaEletronico.Depositar(40);
    //caixaEletronico.Depositar(25);
    ExecutarTransacaoBancaria(caixaEletronico.Depositar, 100);
    ExecutarTransacaoBancaria(caixaEletronico.Depositar, 40);
    ExecutarTransacaoBancaria(caixaEletronico.Depositar, 25);
}

No método Sacar(), comentamos também o código atual e substituímos também por ExecutarTransacaoBancaria(), passando como argumento caixaEletronico.Sacar e o valor da transação, como antes.

private static void Sacar()
{
    //caixaEletronico.Sacar(50);
    //caixaEletronico.Sacar(20);
    ExecutarTransacaoBancaria(caixaEletronico.Sacar, 50);
    ExecutarTransacaoBancaria(caixaEletronico.Sacar, 20);
}

Por fim, no método Extrato(), comentamos a linha de código para chamar o ExecutarConsultaBancaria(), passando o caixaEletronico.Extrato.

private static void Extrato()
{
    //caixaEletronico.Extrato();
    ExecutarConsultaBancaria(caixaEletronico.Extrato);
}

Atenção: Reparem que passamos os métodos do CaixaEletronico sem abrir e fechar parênteses, apenas usando a referência do método na chamada de delegate.

Testando a chamada indireta

Agora que fizemos as chamadas para os métodos novos, executamos o código para verificar se as operações bancárias estão sendo feitas com sucesso, pressionando a tecla F5. O console do nosso programa abre com o caixa eletrônico, mostrando o menu de opções.

Começaremos testando a operação de saldo, digitando a opção 1 e pressionando "Enter". Recebemos o saldo de R$100. Testaremos a opção 2, o depósito de valores. Ele faz o depósito dos valores R$100, R$40 e R$25 com sucesso.

Pressionando a opção 3, para sacar valores, recebemos a operação de saque de R$50, seguida por um saque de R$20. Por último, testando a operação 4, de extrato, recebemos o extrato com saldo inicial e as operações de depósito e saque, resultando em um saldo de R$195.

Dessa forma, aprendemos e executamos o delegate, um tipo que se refere a métodos, permitindo que sejam passados como argumentos de outros métodos. Portanto, o delegate age como um "representante" de métodos.

Delegate: um tipo que se refere a métodos, permitindo que eles sejam passados como argumentos para outros métodos, servindo como "representante" destes.

No próximo vídeo, aprenderemos sobre métodos anônimos, compararemos delegates com métodos anônimos e discutiremos as diferenças entre eles.

Delegates - Delegates vs Métodos anônimos

No último vídeo, introduzimos o conceito de delegates. Agora, abordaremos o delegates versus métodos anônimos.

Entendendo o contexto

Neste vídeo, introduziremos o conceito de métodos anônimos, entenderemos o que são, o que fazem e como criá-los, além de compará-los com os delegates que já criamos. Analisaremos as diferenças e exemplos práticos de implementação no contexto de um caixa eletrônico.

Apresentaremos um problema: utilizamos delegates no último vídeo para realizar operações bancárias, mas cada delegate aponta para um único método.

Program.cs

// código omitido

private static void Saldo()
{
    //caixaEletronico.Saldo();
    ExecutarConsultaBancaria(caixaEletronico.Saldo);
}

private static void Depositar()
{
    //caixaEletronico.Depositar(100);
    //caixaEletronico.Depositar(40);
    //caixaEletronico.Depositar(25);
    ExecutarTransacaoBancaria(caixaEletronico.Depositar, 100);
    ExecutarTransacaoBancaria(caixaEletronico.Depositar, 40);
    ExecutarTransacaoBancaria(caixaEletronico.Depositar, 25);
}

// código omitido

Por exemplo, ao executar uma consulta bancária na linha 66 (ExecutarConsultaBancaria(caixaEletronico.Saldo)), podemos executar o saldo ou o extrato, que são métodos do objeto caixa eletrônico quando analisamos um pouco mais abaixo no código:

// código omitido

private static void Extrato()
{
    //caixaEletronico.Extrato();
    ExecutarConsultaBancaria(caixaEletronico.Extrato);
}

private static void ExecutarConsultaBancaria(ConsultaBancaria consultaBancaria)
{
    consultaBancaria();
}

// código omitido

Ao executar a transação bancária na linha 100, podemos escolher entre o método de sacar ou depositar.

// código omitido

    private static void ExecutarTransacaoBancaria(TransacaoBancaria transacaoBancaria, decimal valor)
    {
            transacaoBancaria(valor);
    }
}

// código omitido

Agora, precisamos combinar uma segunda operação, que é obter o saldo, após realizar um saque ou depósito. Vamos implementar essa solução utilizando métodos anônimos com delegates.

Métodos anônimos com delegates

Para isso, realizaremos uma modificação no método MostrarMenu() da classe Program, localizado na linha 24:

// código omitido

static void MostrarMenu()
{
        Console.WriteLine("\nEscolha uma opção:");
        Console.WriteLine();
        Console.WriteLine("1. Saldo");
        Console.WriteLine("2. Depositar valores");
        Console.WriteLine("3. Sacar valores");
        Console.WriteLine("4. Extrato");
        Console.WriteLine();
        Console.Write("Digite o número da opção desejada: ");
}

// código omitido

O objetivo é adicionar duas novas opções ao menu: uma para combinar o depósito com a consulta de saldo e outra para associar o saque com a verificação do saldo.

Na linha 31, já temos a exibição do extrato. Iremos inserir uma nova linha na linha 32, que incluirá um Console.WriteLine() responsável por exibir a opção 5: "5. Depositar e obter saldo". Em seguida, na linha 33, adicionaremos o código para exibir a opção 6: "6. Sacar e obter saldo".

// código omitido

static void MostrarMenu()
{
        Console.WriteLine("\nEscolha uma opção:");
        Console.WriteLine();
        Console.WriteLine("1. Saldo");
        Console.WriteLine("2. Depositar valores");
        Console.WriteLine("3. Sacar valores");
        Console.WriteLine("4. Extrato");
        Console.WriteLine("5. Depositar e obter saldo");
        Console.WriteLine("6. Sacar e obter saldo");
        Console.WriteLine();
        Console.Write("Digite o número da opção desejada: ");
}

// código omitido

Com isso, temos ambas as linhas exibidas no console. Agora, precisamos criar as opções do menu.

Inserindo as oções no método ExecutarEscolha()

No método ExecutarEscolha(), localizado na linha 38, iremos adicionar as duas novas opções, começando a partir da linha 54. Inseriremos o case 5: para a opção DepositarEObterSaldo();, seguido por um break; na linha 56, para finalizar o tratamento dessa opção. Na linha 57, será adicionado o case 6:, correspondente à opção SacarEObterSaldo(), e um break; na linha 59, para concluir o tratamento dessa segunda opção.

// código omitido

    static void ExecutarEscolha(int escolha)
    {
        switch (escolha)
        {
            case 1:
                Saldo();
                break;
            case 2:
                Depositar();
                break;
            case 3:
                Sacar();
                break;
            case 4:
                Extrato();
                break;
            case 5:
                DepositarEObterSaldo();
                break;
            case 6:
                SacarEObterSaldo();
                break;
            default:
                Console.WriteLine("Opção inválida. Tente novamente.");
                break;
        }
    }

// código omitido

Na linha 55, encontramos a referência à funcionalidade DepositarEObterSaldo();, que corresponde a um método que ainda precisa ser implementado.

Criando os métodos DepositarEObterSaldo() e SacarEObterSaldo()

Para isso, após o método Depositar(), na linha 85, será necessário declarar o novo método, que atenderá a essa funcionalidade específica.

// código omitido

private static void DepositarEObterSaldo()
{
    // Implementação do método
}

// código omitido

Vamos criar o próximo método, SacarEObterSaldo(), na linha 99 (após o método Sacar()):

// código omitido

private static void SacarEObterSaldo()
{
    // Implementação do método
}

// código omitido

Como desafio, precisamos depositar e obter o saldo na sequência. Então, no corpo do método DepositarEObterSaldo(), na linha 88, faremos a declaração de um delegate de transação.

Primeiro, inserimos o tipo do delegate (TransacaoBancaria, declarado no vídeo anterior) e, na sequência, inserimos uma variável que representa a instância do delegate chamada de transacao =. Iremos inicializá-la com delegate que será um método anônimo (sem nome) e estará embutido no método DepositarEObterSaldo(). Porém, será um método local.

Para especificar o método anônimo que atua como delegado, após o sinal de igual, utilizamos delegate(), especificando o tipo de parâmetro que será decimal valor.

A declaração será:

// código omitido

private static void DepositarEObterSaldo()
{
        TransacaoBancaria transacao = delegate (decimal valor)
}

// código omitido

No interior desse método anônimo, deixamos pulamos uma linha e utilizamos chaves para abrir e fechar. No final, note que o Visual Studio sinaliza a ausência de um ponto e vírgula. Isso ocorre devido ao término da definição de uma variável; observe que a definição da transacao se encerra em }; pois representa o fechamento do corpo desse método anônimo.

Dentro do corpo, realizaremos o depósito utilizando caixaEletronico.Depositar(valor) e, em seguida, iremos verificar o saldo com caixaEletronico.Saldo().

// código omitido

private static void DepositarEObterSaldo()
{
        TransacaoBancaria transacao = delegate (decimal valor)
        {
                caixaEletronico.Depositar(valor);
                caixaEletronico.Saldo();
        };
}

// código omitido

Assim, definimos a variável de transacao, que representa o delegate. Depois das chaves e do ponto e vírgula, };, realizaremos três invocações para a variável transacao() utilizando os valores 100, 40 e 25.

// código omitido

private static void DepositarEObterSaldo()
{
        TransacaoBancaria transacao = delegate (decimal valor)
        {
                caixaEletronico.Depositar(valor);
                caixaEletronico.Saldo();
        };

        transacao(100);
        transacao(40);
        transacao(25);
}

// código omitido

No método SacarEObterSaldo(), declaramos a variável TransacaoBancaria transacao = delegate (decimal valor), utilizando a sintaxe de um delegado anônimo. Em seguida, abrimos e fechamos as chaves, e dentro delas, chamamos os métodos caixaEletronico.Sacar(valor) e caixaEletronico.Saldo(). Após isso, atribuiremos valores ao delegado, passando 50 e 20 reais como parâmetros de transação, respectivamente.

// código omitido

private static void SacarEObterSaldo()
{
        TransacaoBancaria transacao = delegate (decimal valor)
        {
            caixaEletronico.Sacar(valor);
            caixaEletronico.Saldo();
        };

        transacao(50);
        transacao(20);
}

// código omitido

Executando

Ao executar o código pressionando a tecla "F5", o menu do console do caixa eletrônico será atualizado com duas novas opções: a opção 5, que corresponde a "Depositar e obter saldo", e a opção 6, que se refere a "Sacar e obter saldo".

Escolha uma opção:

  1. Saldo

  2. Depositar valores

  3. Sacar valores

  4. Extrato

  5. Depositar e obter saldo

  6. Sacar e obter saldo

Digite o número da opção desejada:

Ao teclarmos "5", obtemos:

Data/HoraDescriçãoValor (R$)
25/09/2024 21:24:30Depósito100,00
-Saldo200,00
25/09/2024 21:24:30Depósito40,00
-Saldo240,00
25/09/2024 21:24:30Depósito25,00
-Saldo265,00

Observe que a linha de depósito consta na cor verde e o saldo é o nosso segundo método anônimo do delegate.

Agora, ao inserirmos o valor "6", temos:

Data/HoraDescriçãoValor (R$)
25/09/2024 21:24:58Saque-50,00
-Saldo215,00
25/09/2024 21:24:58Saque-20,00
-Saldo195,00

Obtemos que aconteceu um saldo de R$50,00 e o saldo foi exibido na sequência. Portanto, o console exibirá as operações realizadas e os saldos resultantes de cada uma delas.

Recapitulando

Neste vídeo, aprendemos a diferença entre delegates e métodos anônimos, destacando como os métodos anônimos podem simplificar a implementação das operações do nosso caixa eletrônico. Aprendemos como introduzir, declarar e utilizar métodos anônimos, além de compará-los com os delegates tradicionais.

Próximo passo

No próximo vídeo, vamos explorar o conceito de delegates multicast, que permite a invocação de múltiplos métodos em sequência por meio de um único delegate, ampliando as possibilidades de implementação e tornando o código mais flexível.

Sobre o curso C#: Eventos, Delegates e Lambdas

O curso C#: Eventos, Delegates e Lambdas possui 170 minutos de vídeos, em um total de 44 atividades. Gostou? Conheça nossos outros cursos de .NET em Programação, ou leia nossos artigos de Programação.

Matricule-se e comece a estudar com a gente hoje! Conheça outros tópicos abordados durante o curso:

Aprenda .NET acessando integralmente esse e outros cursos, comece hoje!

Conheça os Planos para Empresas