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)