Event, Delegate e EventHandler: Manipulando eventos em sua aplicação .NET
14
0

Event, Delegate e EventHandler: Manipulando eventos em sua aplicação .NET

Descomplicando Events, Delegates e Event Handlers em .NET

Pedro Calado
6 min
14
0

Events, delegates e EventHandlers em C# são ferramentas muito importantes e poderosas mas que podem ser complexas e difíceis de entender em um primeiro momento, o que acaba impedindo algumas pessoas de as utilizarem em suas aplicações. A ideia deste tutorial é tentar simplificar um pouco esses conceitos, espero que gostem :)

Email image

Events

Eventos nada mais são do que um mecanismo de comunicação entre classes, permitindo que sejam enviadas mensagens específicas de uma classe para outra sem que essas classes tenham qualquer vínculo, o que viabiliza a criação de aplicações desacopladas e de fácil manutenção.

Essencialmente, existe uma classe (chamada de publisher ou event sender) responsável por disparar um evento, e outras classes (subscribers  ou event receivers) preparadas para receber este tipo de evento, ou seja, classes que ficam esperando o disparo de um determinado evento para que possam realizar determinada ação, independente de onde esse evento esteja vindo.

Trazendo para o mundo real, podemos fazer uma comparação com o toque do interfone ou campainha em nossas casas, por exemplo. Sempre que o interfone toca, nós realizamos a ação de ir atende-lo, sem que a gente saiba quem está emitindo esse evento de toca-lo. Ou seja, estamos preparados para receber esse tipo de evento (toque do interfone) e, sempre que ele acontece, realizamos a ação a atender.

Email image

Delegates

Delegates são, essencialmente, tipos que fazem referência a um método específico, definindo o retorno deste método e os seus parâmetros. Em outras palavras, considerando o contexto de envio de eventos, são os delegates que definem a assinatura do método que será chamado pelo subscriber sempre que um evento específico definido por esse delegate for enviado pelo publisher

Sei que esse conceito parece um pouco confuso, mas vai ficar mais claro olhando o exemplo mais abaixo.

EventHandlers

Com a ampla utilização dos delegates no contexto de eventos, o próprio .NET, a partir do .NET Framework 3.0, criou um delegate específico chamado EventHandler, que abstrai a criação de um delegate e já entrega a representação de um método que vai tratar eventos específicos.

Agora, vamos a um exemplo utilizando delegates e events e depois uma demonstração da abstração que o EventHandler nos oferece.

Exemplo

Vamos imaginar um cenário em que temos um serviço de pagamentos de um e-commerce que, sempre que um pagamento for feito, seja necessário enviar um e-mail para o comprador informando que seu pagamento foi realizado com sucesso.

Para isso, criei um projeto Console simples com as seguintes classes:

  • PaymentService.cs -> Essa será nossa publisher, classe responsável por realizar o pagamento e enviar um evento sempre que um pagamento for realizado.
  • EmailService.cs -> Classe responsável por receber o evento específico e enviar um e-mail, também conhecida como subscriber
  • Payment.cs -> Model contendo os dados sobre o pagamento
  • PaymentEventArgs.cs -> Classe responsável por levar os dados de pagamento sempre que um evento for enviado

PaymentService.cs

Para que um evento seja lançado, precisamos basicamente seguir 3 passos:

1. Criar o delegate responsável por definir a assinatura (tipo de retorno e argumentos) do método que será chamado pelo publisher para enviar o evento e do método invocado pelo subscriber depois que esse evento é emitido. Os delegates recebem como parâmetro um objeto (representando a classe responsável por enviar o evento) e os dados correspondentes a esse evento (nesse exemplo, criei uma classe customizada chamada PaymentEventArgs que herda de EventArgs - por padrão os delegates recebem aqui qualquer classe que herde de EventArgs)

 2. Definir um evento baseado nesse delegate

3. Invocar o evento por meio de um método chamado de event publisher que por convenção do .NET Framework deve ser protected, virtual e retornar void, além de possuir um nome composto por On + nome do evento (OnPaymentMade, por exemplo).

using System;
namespace EventsExample
{
public class PaymentService
{
public delegate void MakeAPaymentEventHandler(object source, PaymentEventArgs args);
public event MakeAPaymentEventHandler PaymentMade;
protected virtual void OnPaymentMade(Payment payment)
{
if(PaymentMade != null)
{
PaymentMade(this, new PaymentEventArgs(payment));
}
}
public void MakeAPayment(Payment payment)
{
Console.WriteLine("Processando...");
Console.WriteLine("Pagamento realizado com sucesso!");
OnPaymentMade(payment);
}
}
}

O método MakeAPayment é o responsável por realizar o pagamento (nesse caso, estou somente simulando que um pagamento foi enviado) e chamar o event publisher que emite o evento. Lembrando que ele só sabe qual evento enviar graças ao nosso acordo firmado no delegate, que vai permitir que tanto o publisher saiba qual evento enviar quanto o subscriber (EmailService) saiba qual evento receber.

 Além disso, é importante frisar que o PaymentService não conhece os métodos que vão receber o evento enviado (que nesse caso é o serviço de e-mail), favorecendo o desacoplamento entre as classes.

EmailService.cs

Aqui no serviço de envio de e-mails, temos um método SendEmail que será encarregado por receber os eventos enviados pelo serviço de pagamento. Este método deve seguir a assinatura especificada pelo delegate, ou seja, retornar void e receber como parâmetros o objeto responsável por enviar o evento e os argumentos daquele evento específico que, nesse caso, foi o PaymentEventArgs. Se este método tivesse como argumentos somente o objeto source e um EventArgs genérico, sendo criado como, por exemplo, public void SendEmail(object source, EventArgs e),  ele não receberia o evento de pagamento enviado por não seguir as regras especificadas pelo delegate.

using System;
namespace EventsExample
{
public class EmailService
{
public void SendEmail(object source, PaymentEventArgs e)
{
Console.WriteLine($"Enviando e-mail para {e.BuyerEmail} para informar sobre a compra de R${e.Value}...");
Console.WriteLine("E-mail enviado!");
}
}
}

PaymentEventArgs.cs

Classe que representa um EventArgs customizado, permitindo passar dados de um publisher para um subscriber por meio de um evento.

using System;
namespace EventsExample
{
public class PaymentEventArgs : EventArgs
{
public PaymentEventArgs(Payment payment)
{
BuyerEmail = payment.BuyerEmail;
Value = payment.Value;
}
public string BuyerEmail { get; set; }
public double Value { get; set; }
}
}

Payment.cs

Classe que representa um pagamento.

using System;
namespace EventsExample
{
public class Payment
{
public Payment(string buyerEmail, double value)
{
BuyerEmail = buyerEmail;
Value = value;
}
public string BuyerEmail { get; set; }
public double Value { get; set; }
}
}

Program.cs

Um outro ponto importante para que todo esse fluxo de envio e recebimento de eventos funcione, é fazer o registro dos subscribers ao evento. E aqui eu falo subscribers no plural porque podemos facilmente criar outros métodos responsáveis por realizar outras ações específicas como enviar um SMS, por exemplo, e registrá-los também ao evento de pagamentos.

Para fazer esse registro, é necessário utilizar a notação evento += handler (esse += não é a mesma coisa que a abreviação que usamos quando estamos somando um valor a uma variável e atribuindo a ela mesma, é uma notação específica para registrar subscribers à eventos).

using System;
namespace EventsExample
{
class Program
{
static void Main(string[] args)
{
var payment = new Payment("email@email.com", 199);
var paymentService = new PaymentService();
var emailService = new EmailService();
paymentService.PaymentMade += emailService.SendEmail;
paymentService.MakeAPayment(payment);
}
}
}

Executando a aplicação com os arquivos demonstrados acima, obtemos como resposta no console o texto abaixo, demonstrando que, logo após a finalização do pagamento, foi chamado o serviço de envio de e-mails:

Processando...
Pagamento realizado com sucesso!
Enviando e-mail para email@email.com para informar sobre a compra de R$199...
E-mail enviado!

Utilizando o EventHandler

Conforme dito anteriormente, a partir do .NET  Framework 3.0 foi implementado o delegate EventHandler, responsável por abstrair a criação de um delegate e um event em um só método. Implementando esse delegate, nosso PaymentService ficaria de seguinte maneira:

using System;
namespace EventsExample
{
public class PaymentService
{
public event EventHandler<PaymentEventArgs> PaymentMade;
protected virtual void OnPaymentMade(Payment payment)
{
if(PaymentMade != null)
{
PaymentMade(this, new PaymentEventArgs(payment));
}
}
public void MakeAPayment(Payment payment)
{
Console.WriteLine("Processando...");
Console.WriteLine("Pagamento realizado com sucesso!");
OnPaymentMade(payment);
}
}
}

Atualmente, essa última implementação utilizando o EventHandler é a mais utilizada. Além disso, é possível, também, criar um EventHandler sem um tipo específico de EventArgs (o que não permitiria passar dados de um evento para um subscriber) usando a seguinte assinatura public event EventHandler PaymentMade.

Conclusão

Events e delegates são muito utilizados principalmente em situações onde desejamos desacoplar os serviços de nossas aplicações, facilitando a manutenção e os testes. Espero que tenha ficado um pouco mais claro sobre como funciona o fluxo de envio e recebimento de eventos e que você consiga aplicar nos seus projetos.

Bons estudos!