Anúncios

Delegate Chain Invocation

Callback functions são blocos de código executável que são passados como parâmetro para outro código, que fica responsável por invocá-los quando apropriado.

O modo como callback functions são suportados em cada linguagem de programação é diferente, mas são frequentemente implementados como subrotinas, expressões lambdas ou ponteiros de função.

O tratamento das linguagens não-gerenciadas sobre os callback functions é limitada a apenas um endereço de memória. Este endereço de memória não contém nenhuma informação adicional sobre o tipo de retorno, o número de parâmetros ou os tipos de dados dos parâmetros.

O .Net Framework expõe o mecanismo de callback functions por meio do uso de delegates.

Delegates asseguram que os callback functions são type-safe (i.e., tipados, fortemente tipado, de tipagem segura). Delegates também permitem a execução de métodos estáticos e execução sequencial de múltiplos métodos.

No .Net Framework os delegates são utilizados para diversas tarefas, como: notificação de exceções não tratadas, seleção de itens de menu, eventos em controles de formulários e término de operações assíncronas.

Tome como exemplo o código abaixo:

    using System;
    using System.Reflection;

    internal delegate void Answer(Guid value);

    class Program {
        static void Main(string[] args) {

            Program program = new Program();
            var fb0 = new Answer(Program.WriteSomething);
            var fb1 = new Answer(program.WriteSomethingInRed);
            var fb2 = new Answer(program.WriteSomethingInBlue);

            #region [ Single delegate ]

            Object instance = fb0.Target;
            MethodInfo method = fb0.Method;

            ExecuteAllStuff(fb0);

            #endregion

            #region [ Multiple delegates ]

            Answer allFeeds = null;
            allFeeds += fb0;
            allFeeds += fb1;
            allFeeds = (Answer)Delegate.Combine(allFeeds, fb2);

            var invocationList = allFeeds.GetInvocationList();

            ExecuteAllStuff(allFeeds);

            #endregion

            // The line below is just here to stop the screen and you see the results
            // Or delete the line below and execute with (Ctrl + F5)
            Console.Read();
        }

        public static void ExecuteAllStuff(Answer feedBackDelegate) {

            Console.WriteLine("*****************************");

            // Invoque method(s)
            feedBackDelegate(Guid.NewGuid());
        }

        public static void WriteSomething(Guid value) {

            Console.ForegroundColor = ConsoleColor.White;
            Console.WriteLine("The value is: {0}", value);
        }

        private void WriteSomethingInRed(Guid value) {

            Console.ForegroundColor = ConsoleColor.Red;
            Console.WriteLine("The red value is: {0}", value);
        }

        public void WriteSomethingInBlue(Guid value) {

            Console.ForegroundColor = ConsoleColor.Blue;
            Console.WriteLine("The blue value is: {0}", value);
        }
    }

No código acima é criado um delegate de nome Answer. Em sua declaração pode-se identificar a estrutura esperada para os callback functions: métodos com retorno void e cujo único parâmetro é do tipo Guid.

Também é definido um método privado estático chamado ExecuteAllStuff. Este método espera como parâmetro um delegate Answer e faz o invoque do callback function associado ao delegate. Note que para realizar o invoque do método, por meio do delegate, o parâmetro é passado entre parênteses:

    feedBackDelegate(Guid.NewGuid());  

… mas o compilador gera uma chamada ao método Invoke, como demonstrado abaixo:

  feedBackDelegate.Invoke(Guid.NewGuid());  

Isso demonstra que as duas operações são semelhantes e ambas podem ser utilizadas para obtenção dos mesmos resultados.

Em seguida existem três métodos: WriteSomething, WriteSomethingInRed e WriteSomethingInBlue. Veja que estes métodos tem a assinatura esperada pelo delegate Answer (retorno void com um único parâmetro do tipo Guid).

O método estático Main contém a parte mais interessante do código e está dividido em três partes: declaração dos delegates, uso de um simples delegate e uso de múltiplos delegates.

Veja que no bloco de declaração dos delegates são criadas três instâncias de Aswer. A primeira das instâncias criadas recebe como parâmetro uma referência para um método estático da classe Program. As demais instâncias criadas recebem como parâmetro dois métodos de instância, sendo um público e o outro privado.

No bloco "Single delegate" algumas informações sobre o delegate são acessadas. A propriedade Target de um delegate indica qual instância de objeto é a detentora do método a ser invocado. No caso dos callback functions que utilizam métodos estáticos o valor desta propriedade será null, pois realmente não existe uma instância ativa para um método estático. A propriedade Method de um delegate, como o próprio nome já diz, indica qual método será invocado pelo delegate.

    #region [ Single delegate ]

    Object instance = fb0.Target;
    MethodInfo method = fb0.Method;

    ExecuteAllStuff(fb0);
            
    #endregion

O bloco "Multiple delegates" demonstra o acumulo de delegates, conceito conhecido como Delegate Chain Invocation. Essa funcionalidade não existe nas linguagens não-gerenciadas e permite o acumulo de delegates, para que sejam invocados de uma única vez e de maneira sequencial. O acumulo de delegates pode ser feito de duas maneiras: utilizando o operador "+=" ou por meio do método estático Combine da classe Delegate. Perceba que para o acúmulo dos delegates foi criado um novo delegate chamado allFeeds.

    #region [ Multiple delegates ]

    Answer allFeeds = null;
    allFeeds += fb0;
    allFeeds += fb1;
    allFeeds = (Answer)Delegate.Combine(allFeeds, fb2);
            
    var invocationList = allFeeds.GetInvocationList();
      
    ExecuteAllStuff(allFeeds);
            
    #endregion

A execução de diversos delegates de uma única vez nem sempre é vantajosa. Caso algum dos delegates lance uma exceção todos os delegates subsequentes serão bloqueados.

Para estes cenários, podemos utilizar o método GetInvocationList. Este método que cria um array com todos os delegates encadeados a serem executados e o retorna ao ponto chamador. Caso o delegate não contenha delegates acumulados, isto é, caso o delegate contenha apenas um delegate, então o retorno deste método será null, pois não existe uma coleção de delegates a serem retornados já que a referência é feita a apenas um delegate. O código abaixo demonstra como isso pode ser feito:

        public static void ExecuteOneByOne(Answer feedBackDelegate) {

            if (feedBackDelegate == null)
                return;

            Delegate[] delegates = feedBackDelegate.GetInvocationList();

            if (delegates == null) {
                feedBackDelegate(Guid.NewGuid());
                return;
            }

            foreach (Answer delegateInstance in delegates) {
                try {

                    delegateInstance(Guid.NewGuid());
                }
                catch (Exception ex) {

                    Object component = delegateInstance.Target;

                    Console.WriteLine("Error executing callback function: {0}/{1} - Error: {2}", 
                                      (component != null)? component.GetType().ToString() : "(static)",
                                      delegateInstance.Method.Name,
                                      ex.Message);
                }
            }
        }

Por

MSc. Fernando Henrique Inocêncio Borba Ferreira

Microsoft Most Valuable Professional – Visual C#

Referências:
CLR Via C# 3.0 – Jeffrey Richter

Delegates (C# Programming Guide)

Anúncios

Deixe um comentário

Preencha os seus dados abaixo ou clique em um ícone para log in:

Logotipo do WordPress.com

Você está comentando utilizando sua conta WordPress.com. Sair / Alterar )

Imagem do Twitter

Você está comentando utilizando sua conta Twitter. Sair / Alterar )

Foto do Facebook

Você está comentando utilizando sua conta Facebook. Sair / Alterar )

Foto do Google+

Você está comentando utilizando sua conta Google+. Sair / Alterar )

Conectando a %s

%d blogueiros gostam disto: