domingo, 21 de setembro de 2008

A evolução dos delegates

Ao longo das versões do C#, uma das funcionalidades que mais me reteve atenção como desenvolvedor foram Lambda Expressions. Desta forma, resolvi fazer um artigo expondo uma abordagem evolutiva sobre o tema (nada muito didático, meramente informativo):

C# 1.0

Quando tínhamos que escrever “ponteiros para uma função/método” no C# (óbvio que muitos não conhecem por esse nome, que é muito popular em C++, vou explicar melhor mais a frente), nós podíamos (e ainda podemos) adicioná-las em estruturas denominadas delegates da seguinte forma:



1- Se escreve o delegate, que é uma estrutura que funciona como um tipo, o qual aquele método vai ser vinculado, ou seja, se define uma referência com assinatura de método específica.

2- Se cria o método o qual se quer que o ponteiro aponte.

3- Com isso você pode adicionar (e remover) nessa referência qualquer método que tenha essa mesma assinatura, como se fosse uma lista de execução.

A conclusão que podemos chegar com delegates é que são mecanismos que permitem o agrupamento de vários métodos de diferentes classes para um pipeline execução. E justamente por isso, ele é utilizado em várias partes do .net framework, como threading, eventos, serialização etc.

Eventos
Como falei, no topo da estrutura de delegates está construída uma arquitetura de invocação implícita que ficou muito popular: eventos de componentes. Eventos são delegates especiais. Ele tem uma assinatura restrita que não deve retornar nada a não ser void, e só podem ser executados pela classe que os possui (classes filhas não executam diretamente eventos da classe pai). Essa especificidade é atribuída ao declarar a referência delegate a palavra chave event.



C# 2.0

As coisas ficaram um pouco mais práticas com o advento dos Anonymous Methods...

1 – Em muitos casos se deseja simplesmente executar uma operação vinculada a um evento de um componente (ex: setar visible=false no click do botão).
Isso significa que escrever um método unicamente pra fazer essa operação ser “burocracia demais”.

Pensando nisso, na versão do C# 2.0 foi criada, entre outras funcionalidades, Anonymous Methods, que é uma forma simplificada de escrever um método no contexto de um delegate:




Realmente simplificou mais, mas ainda não terminou...


C# 3.0

Tudo estava indo bem, até que apareceram essas tais lambda expressions, mas afinal, o que são lambda expressions? A resposta que mais gostei e a que uso é que é uma forma simplificada de escrever um delegate:

if ( querSaberMais?) {

goto: especificação do C# 3.0

if ( naoEntendiMuitoBem) {
Console.WriteLine(" Vou explicar com o nosso exemplo :-) ");
}
}

Na versão 2.0, criar um método anônimo para isso era a possibilidade mais ágil. Mas agora é possível proceder para resolver o mesmo problema com lambda expressions. A diferença é meramente uma questão de sintaxe:

Assim, assinar eventos simples como click’s de botão ficaram tão mais fáceis, que começei a inscrever muitos "onclicks" manualmente. Detalhe: a inferência de tipo é que faz o compilador procurar saber o tipo de "obj" e "e", o que é muito legal para quem tem dificuldades (como eu) de ficar decorando todos os XXXXEventArgs que tem poraí para cada evento diferente em função da vasta quantidade de XXXEventHandlers diferentes que os controles usam. :-)

Bom, é isso pessoal, no próximo post falarei sobre interfaces e Extension Methods, aguarde!

2 comentários:

Anônimo disse...

Muito bom o artigo! Adorei!
Nossa, quando li tu falando sobre ponteiros para função no C++, pareceu um flash-back pra mim, me levou aos tempos de ensino técnico quando quebrava a cabeça e era só estresse pois sempre enfrentava erros de memória, ponteiro apontado para áreas não alocadas, etc.

Me chamou atenção a forma como tu declaras as lambda expressions e os anomymous methods, bem simples, como se fossem funções inline.
E para casos mais complicados, quando pretendes utilizar funções mais complexas, o uso delegates é mais apropriado, não é? Há alguma diferença de desempenho? Eu nunca notei diferença, mas gostaria de ouvir tua opinião.

Essas tecnologias novas de programaçao estão muito dinâmicas, se não se atualizar, fica parado no tempo.

Parabeéns pelo artigo e sucesso!!

Olavo Rocha Neto disse...

Olá morhfell!

Grato pelo elogio. :-)

Respondendo a sua pergunta, em qualquer dos casos, sejam lambdas, sejam anonymous methods, o compilador C# cria "por baixo dos panos" um método com um nome estranho (tipo "__a1") e o vincula ao delegate, ou seja, na prática não passa de uma "simplificação sintática", mas todas as situações usam delegates.

Para métodos mais complexos, a modularização é sempre bem vinda, então criar o método e vinculá-lo ao delegate pode ser uma saída mais indicada mesmo, principalmente quando o método em questão precisa ser vinculado a vários delegates no mesmo contexto.

Também deve-se ter em mente que o uso de delegates "puro" (ou seja, o modo descrito p/ C# 1.0) é o único que permite você não só adicionar métodos ao delegate, mas também retirá-los através do operador "-=".

Sobre desempenho, o uso de delegates consome um pouco mais de tempo do que a chamada direta de um método, então não é algo que se deva abusar. O linq é uma tecnologia que usa bastante delegates para iterar entre coleções, e seu desempenho não é dos melhores, por isso dispõem de outras técnicas para conter esse impacto no desempenho, ex: lazy evaluation.