Delegates e Eventos

Sumário

delegateAndEvents

Olá galera. 🙂

Veja o índice completo do tópico “Delegate”

Continuando nossa série sobre delegates, desta vez iremos falar sobre eventos, e quase impossível não entrar no assunto quando estamos falando de delegates.
Neste artigo iremos aprender a escrever os eventos de forma complexa e simples, com o uso da palavra reservada “event“.

note-taking Eventos, de forma geral, é um meio da classe, ou componente enviar mensagens aos seus ouvintes quando algo interessante acontece. Os eventos em C#, utilizam-se de delegates para a troca de mensagens.
Como por exemplo:

  • O Clique do botão;
  • O movimento do cursor;
  • O pressionamento de uma tecla;
  • Uma ação ocorrida;

Entenda eventos como mudanças ou ações que precisam ser notificadas para seus ouvintes, listenners, através de seus objetos, senders.

Além do uso tradicional, iremos ver neste artigo:

  • Eventos em herança;
  • Eventos em interfaces;

Criando eventos

Os eventos em C# são definidos em três passos:

  • Primeiro, criamos o delegate que irá tratar os métodos que serão chamados pelo evento. Opcionalmente podemos usar o delegate “System.EventHandler
  • Segundo, criamos a classe de argumentos para o evento;
  • Terceiro, criamos o evento com o uso da palavra reservada “event“.

Esta declaração segue o padrão de criação de eventos em C#.

Abaixo, iremos ver um exemplo simples da criação de eventos, completo e com o delegate genérico “System.EventHandler“. Preste atenção aos comentários no código.

using System;

//Criando eventos seguindo o padrão de criação definido pelo C#
namespace DelegatesEEventos
{
    //declara o tratador do evento com dois parâmetros
    //Esta declaração pode ser ocultada com o uso do delegate genérico System.EventHandler<>
    //como iremos ver mais abaixo
    public delegate void CustomHandler(object sender, CustomEventArgs args);
    
    //Cria a classe de argumentos do evento
    public class CustomEventArgs: EventArgs
    {
        public string Mensagem { get; private set; }

        public CustomEventArgs(string mensagem)
        {
            Mensagem = mensagem;
        }
    }

    //define uma classe com eventos
    public class ClasseQualquerComEvento
    {
        //Define um evento na classe, usando o nosso delegate
        public event CustomHandler OnFazendoAlgo;

        //Podemos declarar um evento genérico usando o System.EventHandler, 
        //neste caso não é necessário criar o nosso delegate
        public event EventHandler<CustomEventArgs> OnFazendoEventoGenerico;
    }
}

Invocando um evento

Para chamar um evento em C#, basta chamar o mesmo como se fosse um método, passando os parâmetros pedido pelo delegate, seja ele genérico ou não.

   public void FacaAlgo()
        {
            //Diferente do VB, em C# temos que verificar se o evento não é nulo antes de chamar.
            if(OnFazendoAlgo != null)
                //Basta chamar o evento como se fosse um método da classe, passando os parâmetros definidos no delegate
                OnFazendoAlgo(this, new CustomEventArgs("Olá, estou fazendo algo pelo handler customizado"));

            if(OnFazendoEventoGenerico != null)
                //O mesmo acontece com o delegate genérico
                OnFazendoEventoGenerico(this, new CustomEventArgs("Estou fazendo algo pelo delegate genérico"));
        }

O que acontece por baixo?

UnderTheHood O que acontece por baixo?

Aprendemos a criar o evento usando a palavra reservada “event” para diminuir o tamanho de código digitado. Mas, o que realmente acontece por baixo dos panos?

Quando o compilador encontra a palavra reservada “event“, é criado dois novos métodos pelo C# um “add” e um “remove” seguidos de “_NomeDoEvento“, mas nós também podemos usar esta técnica com as palavras reservadas “add” e “remove“. Veja como ficaria a nossa declaração de eventos no exemplo abaixo:

using System;

//Criando eventos seguindo o padrão de criação definido pelo C#
namespace DelegatesEEventos.AddRemove
{
    //declara o tratador do evento com dois parâmetros
    //Esta declaração pode ser ocultada com o uso do delegate genérico System.EventHandler<>
    //como iremos ver mais abaixo
    public delegate void CustomHandler(object sender, CustomEventArgs args);

    //Cria a classe de argumentos do evento
    public class CustomEventArgs: EventArgs
    {
        public string Mensagem { get; private set; }

        public CustomEventArgs(string mensagem)
        {
            Mensagem = mensagem;
        }
    }

    //define uma classe com eventos
    public class ClasseQualquerComEvento
    {
        //Definimos a instancia do nosso delegate para tratar os eventos
        private CustomHandler customHandler;

        //Define um evento na classe, usando o nosso delegate
        public event CustomHandler OnFazendoAlgo
        {
            add
            {
                lock (customHandler)
                {
                    //adiciona um evento à lista de chamadas
                    customHandler += value;
                }
            }
            remove
            {
                lock (customHandler)
                {
                    //remove um evento da lista de chamadas
                    customHandler -= value;
                }
            }
        }

        public void FacaAlgo()
        {
            //Diferente do VB, em C# temos que verificar se o evento não é nulo antes de chamar.
            if(customHandler != null)
                //Basta chamar o evento como se fosse um método da classe, passando os parâmetros definidos no delegate
                customHandler.Invoke(this, new CustomEventArgs("Olá, estou fazendo algo pelo add/remove"));
        }
    }
}
note-taking Esta abordagem foi apenas para mostrar que podemos criar os métodos de acesso ao evento. Na maioria dos casos a forma tradicional atende.

Eventos em Herança

Vou mostrar duas formas de realizar a chamada de eventos por herança em C#, depois iremos comentar as duas formas e mostrar qual a melhor.

Quando criamos classes, temos que ter em mente que os eventos são um tipo especial de delegates que só podem ser invocados dentro da própria classe, as classes filhas não podem executar estes eventos diretamente.

question Mas como posso fazer com que as minhas classes filhas utilizem os eventos da classe pai?

Para ilustrar a chamada, vamos analisar o código abaixo.

using System;

//Criando eventos seguindo o padrão de criação definido pelo C#
namespace DelegatesEEventos.Heranca
{
    //declara o tratador do evento com dois parâmetros
    //Esta declaração pode ser ocultada com o uso do delegate genérico System.EventHandler<>
    //como iremos ver mais abaixo
    public delegate void CustomHandler(object sender, CustomEventArgs args);

    //Cria a classe de argumentos do evento
    public class CustomEventArgs: EventArgs
    {
        public string Mensagem { get; private set; }

        public CustomEventArgs(string mensagem)
        {
            Mensagem = mensagem;
        }
    }

    //define uma classe com eventos
    public class ClasseQualquerComEvento
    {
        //Define um evento na classe, usando o nosso delegate
        //Veja, marcamos como "virtual" o nosso evento
        public virtual event CustomHandler OnFazendoAlgo;

        public void FacaAlgo()
        {
            //Diferente do VB, em C# temos que verificar se o evento não é nulo antes de chamar.
            if(OnFazendoAlgo != null)
                //Basta chamar o evento como se fosse um método da classe, passando os parâmetros definidos no delegate
                OnFazendoAlgo(this, new CustomEventArgs("Olá, estou fazendo algo pelo método FacaAlgo"));
        }
    }

    //classe herdada
    public class ClasseQualquerComEventoHeranca: ClasseQualquerComEvento
    {
        //Sobrescrevemos o evento que queremos chamar, pois como já foi dito, só podemos 
        //chamar eventos dentro da classe
        public override event CustomHandler OnFazendoAlgo;

        //método qualquer que utiliza o evento
        public void FacaAlgoFilho()
        {
            //Como sobrescrevemos o evento, podemos chamá-lo aqui
            if(OnFazendoAlgo != null)
                OnFazendoAlgo(this, new CustomEventArgs("Olá, estou fazendo algo pelo método filho"));
        }
    }
}
errado Isto está errado.
A abordagem acima está errada, pois ao chamar o método “FacaAlgo()” da classe pai o evento não será lançado.

ClasseQualquerComEventoHeranca classeQualquerComEventoHeranca = new ClasseQualquerComEventoHeranca();
            classeQualquerComEventoHeranca.OnFazendoAlgo += (s, a) =>
            {
                Console.WriteLine(a.Mensagem);
            };
            classeQualquerComEventoHeranca.FacaAlgo();
            classeQualquerComEventoHeranca.FacaAlgoFilho();
            /* Output:

            Olá, estou fazendo algo pelo método filho

            */

 

note-taking Não marque seus eventos como virtuais, o compilador do C# não irá definir a chamada corretamente, e ao sobrescrever o evento apenas a classe filha atende ao delegate. A ação definida no evento para o  método pai não será invocada.

O padrão correto

Agora, vamos utilizar o padrão correto para este tipo de caso.

  • Crie seu evento como já foi mostrado;
  • Crie um método protegido e virtual para notificar que o evento deverá ocorrer;
using System;

//Criando eventos seguindo o padrão de criação definido pelo C#
namespace DelegatesEEventos.Heranca
{
    //declara o tratador do evento com dois parâmetros
    //Esta declaração pode ser ocultada com o uso do delegate genérico System.EventHandler<>
    //como iremos ver mais abaixo
    public delegate void CustomHandler(object sender, CustomEventArgs args);

    //Cria a classe de argumentos do evento
    public class CustomEventArgs: EventArgs
    {
        public string Mensagem { get; private set; }

        public CustomEventArgs(string mensagem)
        {
            Mensagem = mensagem;
        }
    }

    //define uma classe com eventos
    public class ClasseQualquerComEvento
    {
        //Define um evento na classe, usando o nosso delegate
        //Veja, marcamos como "virtual" o nosso evento
        public event CustomHandler OnFazendoAlgo;

        public void FacaAlgo()
        {
            FazendoAlgoNotify(new CustomEventArgs("Olá, estou fazendo algo pelo método FacaAlgo"));
        }

        //Define o método de notificãção do evento OnFazendoAlgo
        protected virtual void FazendoAlgoNotify(CustomEventArgs args)
        {
            //Diferente do VB, em C# temos que verificar se o evento não é nulo antes de chamar.
            if(OnFazendoAlgo != null)
                //Basta chamar o evento como se fosse um método da classe, passando os parâmetros definidos no delegate
                OnFazendoAlgo(this, args);
        }
    }

    //classe herdada
    public class ClasseQualquerComEventoHeranca: ClasseQualquerComEvento
    {
        //método qualquer que utiliza o evento
        public void FacaAlgoFilho()
        {
            //Como sobrescrevemos o evento, podemos chamá-lo aqui
            FazendoAlgoNotify(new CustomEventArgs("Olá, estou fazendo algo pelo método filho"));
        }

        //se precisar, pode sobrescrever o método
        protected override void FazendoAlgoNotify(CustomEventArgs args)
        {
            //Se necessário. Faça alguma ação antes aqui

            //depois chama o método de base
            base.FazendoAlgoNotify(args);
        }
    }
}
certo Isto está correto.

Como podem ver, ao chamar os dois métodos, o evento será executado duas vezes, pois cada método quer notificar que fez algo.

ClasseQualquerComEventoHeranca classeQualquerComEventoHeranca = new ClasseQualquerComEventoHeranca();
            classeQualquerComEventoHeranca.OnFazendoAlgo += (s, a) =>
            {
                Console.WriteLine(a.Mensagem);
            };
            classeQualquerComEventoHeranca.FacaAlgo();
            classeQualquerComEventoHeranca.FacaAlgoFilho();
            /* Output:

            Olá, estou fazendo algo pelo método FacaAlgo
            Olá, estou fazendo algo pelo método filho

            */

Eventos em interfaces

Interfaces permitem definir a assinatura de seus eventos. Para isso, declare-os normalmente na interface.

Veja o exemplo:

using System;

namespace DelegatesEEventos.Interfaces
{
    //declara o tratador do evento com dois parâmetros
    public delegate void CustomHandler(object sender, CustomEventArgs args);

    //Cria a classe de argumentos do evento
    public class CustomEventArgs: EventArgs
    {
        public string Mensagem { get; private set; }

        public CustomEventArgs(string mensagem)
        {
            Mensagem = mensagem;
        }
    }

    //Define a interface
    public interface IImplementotoEvento
    {
        //Define o evento na interface
        event CustomHandler OnFazendoAlgo;

        //método faça algo
        void FacaAlgo();
    }

    //define uma classe com eventos, e implementa a interface IImplentoEvento
    public class ClasseQualquerComEventoEInterface: IImplementotoEvento
    {
        //Define o evento implementado pela interface
        public event CustomHandler OnFazendoAlgo;

        //define o método implementado na interface
        public void FacaAlgo()
        {
            //Diferente do VB, em C# temos que verificar se o evento não é nulo antes de chamar.
            if(OnFazendoAlgo != null)
                //Basta chamar o evento como se fosse um método da classe, passando os parâmetros definidos no delegate
                OnFazendoAlgo(this, new CustomEventArgs("Olá, estou fazendo algo."));
        }
    }
}

Para chamar, podemos criar uma instância da interface ou a classe diretamente, mas para o exemplo, iremos criar uma instância do tipo da interface.

IImplementotoEvento implentoEvento = new ClasseQualquerComEventoEInterface();
            implentoEvento.OnFazendoAlgo += (s, a) =>
            {
                Console.WriteLine(a.Mensagem);
            };
            implentoEvento.FacaAlgo();

            /* Output:

            Olá, estou fazendo algo.

            */

 


É isso ai pessoal 🙂
Até o próximo
♦ Marcelo

Marcelo

Nascido em Juruaia/MG em uma fazenda de criação de búfalos, e residindo na região Sul do Brasil.
Trabalha com desenvolvimento de aplicações desde os 17 anos. Atualmente é Arquiteto Organizacional na Unimake Software.
Para saber mais ... http://desenvolvedores.net/marcelo
[]'s

Você vai gostar de...

Postagens populares.

Deixe um comentário

Esse site utiliza o Akismet para reduzir spam. Aprenda como seus dados de comentários são processados.