Desenvolvedores.Net - TechBlog

Monthly Archives: junho 2016

Delegates e Eventos

0
1 Estrela2 Estrelas3 Estrelas4 Estrelas5 Estrelas (1 votos, média: 5,00 de 5)
Loading...
28 de junho de 2016

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

About 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

Delegate

0
1 Estrela2 Estrelas3 Estrelas4 Estrelas5 Estrelas (2 votos, média: 5,00 de 5)
Loading...
24 de junho de 2016

delegate

Olá Amigos.

Neste tópico estarei falando sobre delegates, sobre sua utilização em eventos, em funções, em ações, em lambda expressions, sobre multicasting e a construção de expressões lambdas e Linq.

Iremos começar da base, para entendermos o que realmente é um delegate e como ele pode nos ser útil. Iremos começar com exemplo simples e rápidos, ao modo que cada artigo vai sendo escrito, iremos nos aprofundando mais neste tópico, até chegarmos ao ponto de escrevermos nossas próprias expressões lambdas.

Índice

Vou apontar o índice para os demais artigos conforme forem sendo escritos

O que é delegate?

note-taking Podemos dizer que um delegate é uma função de retorno (callback function), um ponteiro para uma função que será chamada em outro momento. Com esta peculiaridade, podemos tratar o clique de um botão, o movimento do mouse, a seleção de um menu. O que chamamos de eventos.
Um delegate suporta chamadas assíncronas, síncronas e multicasting.
Iremos falar disso em outros tópicos

Neste artigo, iremos analisar sobre os diversos aspectos de um delegate, como criá-lo e como instanciá-lo e como utilizar o delegate.

Compreendendo o Delegate

Diferente dos ponteiros programados usando C que são apontamentos direto para um endereço de memória, os delegates em C# são mais seguros e fortemente tipados, mais orientado a objetos, um delegate é um type-safe

note-taking Type-Safe é a capacidade do C# de garantir que um objeto tem suas características preservadas. Está também ligado à forma de acesso em memória deste objeto.

Exemplo:
Em C, podemos declarar algo assim, de forma insegura;
Você declara um inteiro, faz um cast (conversão) para um tipo char e consegue acessar fora dos limites do int.

int i = 10;
char *s = (char*)i;
print(*(s+10));

Em c# isso não é possível, a não ser que você use a palavra chave “unsafe (inseguro)

int i = 10;
char *s >> //isto não será permitido fora de um contexto unsafe (inseguro)

Um delegate mantem 3 informações importantes:

      O endereço do método para o qual deverá ser chamado
      Os parâmetros, se existirem
      O retorno, se existir
note-taking Delegates podem apontar para métodos estáticos ou de instância.

Quando um objeto do tipo delegate é criado ele já vem adaptado para ser chamado de forma síncrona ou assíncrona, isto é muito útil quando iremos programar pensando em multi-tarefas, sem ter que criar um objeto de tarefas (Thread).

Iremos falar sobre este assunto,Multi-Tasking, em outro momento.

Definindo um delegate

A definição do delegate em C# segue esta regra:
modificador de acesso delegate retorno (ou void)nome (parâmetros (opcionais))

public delegate int FacaAlgoERetorne(int x, int y);
public delegate void SoFacaAlgo(int x, int y);
public delegate void FacaAlgo();

Quando o compilador C# interpretar o nosso objeto delegate, ele automaticamente irá criar uma classe que herda de “System.MulticastDelegate” esta classe terá os métodos que iremos utilizar para executar nosso delegate quando necessário. Iremos nos ater apenas ao método “Invoke()”

MaosAObra Chega de papo e mãos à obra

Mãos à obra

Para reforçar o entendimento, iremos escrever uma calculadora simples, que fará as quatro operações básicas. Abaixo o código completo em C#

using System;

namespace CalculadoraDelegate
{
    internal class Program
    {
        //========================================================
        //Criação do nosso delegate
        private delegate double Operacao(double x, double y);
        //========================================================

        /* veja que nosso delegate tem:
         * Modificador de acesso (private)
         * palavra reservada (delegate)
         * Tipo de retorno (double)
         * Parâmetros (x e y)
         * Isso determina como deverá ser a assinatura do método que será utilizado para o nosso delegate.
         * Caso o método não tenha esta assinatura o erro "Error CS0123	No overload for 'Nome do Método' matches delegate 'namespace.NomeDoDelegate'" será lançado
         */

        private static void Main(string[] args)
        {
            Console.Title = "Calculador delegate (CTRL + C para sair)";
            double x = LerNumero();
            string operacao = LerOperacao();
            double y = LerNumero();

            //========================================================
            //Criando o ponteiro para o delegate, iniciamos com null, pois não sabemos qual operação iremos realizar.
            Operacao handler = null;
            //========================================================

            //de acordo com a operação utilizada, iremos istanciar o nosso delegate e executar o método associado
            switch(operacao)
            {
                case "+":
                    handler = new Operacao(Somar);
                    break;
                case "-":
                    handler = new Operacao(Subtrair);
                    break;
                case "*":
                    handler = new Operacao(Multiplicar);
                    break;
                case "/":
                    handler = new Operacao(Dividir);
                    break;
                default:
                    Console.WriteLine("Operação não implementada.");
                    break;
            }

            //Executa o método associado
            Console.WriteLine($"\r\nO resultado da operação {x:N2}{operacao}{y:N2} é {handler.Invoke(x, y):N2}");

            Console.ReadKey();
        }

        #region Métodos de leitura

        private static string LerOperacao()
        {
            string validas = "+-*/";
            string result = "";

            do
            {
                Console.Clear();
                Console.WriteLine("Informe a operação desejada:");
                Console.WriteLine("(+) para Somar.");
                Console.WriteLine("(-) para Subtrair.");
                Console.WriteLine("(/) para Dividir.");
                Console.WriteLine("(*) para Multiplicar.");
                result = Console.ReadKey().KeyChar.ToString();

                if(!validas.Contains(result))
                {
                    Console.WriteLine("\r\nInforme uma operação válida.");
                    result = "";
                    Console.ReadKey();
                }
            } while(String.IsNullOrWhiteSpace(result));

            return result;
        }

        private static double LerNumero()
        {
            double result = 0;

            do
            {
                Console.Clear();
                Console.WriteLine("Informe um número.");
                string numero = Console.ReadLine();

                if(!double.TryParse(numero, out result))
                {
                    Console.WriteLine("\r\nInforme um número válido.");
                    Console.ReadKey();
                }
            } while(result == 0);

            return result;
        }

        #endregion Métodos de leitura

        #region Métodos de operação

        private static double Somar(double x, double y)
        {
            return x + y;
        }

        private static double Dividir(double x, double y)
        {
            return x / y;
        }

        private static double Multiplicar(double x, double y)
        {
            return x * y;
        }

        private static double Subtrair(double x, double y)
        {
            return x - y;
        }

        #endregion Métodos de operação
    }
}

Entendendo o código

Como podemos ver, a nossa calculadora utiliza o mesmo delegate nas quatro operações.

private delegate double Operacao(double x, double y);

Conforma a operação é definida o delegate é instanciado passando os parâmetros para o mesmo:

//de acordo com a operação utilizada, iremos istanciar o nosso delegate e executar o método associado
            switch(operacao)
            {
                case "+":
                    handler = new Operacao(Somar);
                    break;
                case "-":
                    handler = new Operacao(Subtrair);
                    break;
                case "*":
                    handler = new Operacao(Multiplicar);
                    break;
                case "/":
                    handler = new Operacao(Dividir);
                    break;
                default:
                    Console.WriteLine("Operação não implementada.");
                    break;
            }

E ao final, é invocado e recuperado o resultado da operação.

//Executa o método associado
            Console.WriteLine($"\r\nO resultado da operação {x:N2}{operacao}{y:N2} é {handler.Invoke(x, y):N2}");

 


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

About 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