.NET Delegates, Funcs, Actions
Autor: Jone | Publicado: 2013-01-22T01:14:00-08:00

Delegate é um tipo especial de classe no .NET que quando instanciado, armazena referência a um método ou a uma lista de métodos, incluindo anonymous methods, desde que possuam assinatura compatível com sua definição.

Com esse recurso, podemos passar delegates como parâmetros a outros métodos e/ou guardar referências a esses métodos para uso posterior.

Delegates, assim como classes, devem ser previamente definidos e podem ser instanciados direta ou indiretamente, e após sua instanciação, se comportam como se fossem métodos comuns.
Quando invocados, executam o método referenciado.

Os delegates mais usados no .NET são os do tipo: EventHandler, Action e suas variações, Func&lt;TResult&gt; e suas variações.

Se você utiliza .NET 3.5 ou superior, provavelmente nunca irá criar um novo tipo de delegate, basta utilizar um delegate já existente.


Declarando Delegates



Para entendermos melhor os delegates, vamos declarar alguns antes de utilizá-los. Cada linha abaixo define um delegate diferente:



&nbsp; &nbsp;&nbsp;public&nbsp;delegate&nbsp;void&nbsp;VoidSemParametros();
&nbsp;&nbsp;&nbsp;&nbsp;public&nbsp;delegate&nbsp;void&nbsp;VoidComParametros(string&nbsp;valor);
&nbsp;&nbsp;&nbsp;&nbsp;public&nbsp;delegate&nbsp;int&nbsp;IntSemParametros();
&nbsp;&nbsp;&nbsp;&nbsp;public&nbsp;delegate&nbsp;int&nbsp;IntComParametros(int&nbsp;x,&nbsp;int&nbsp;y);



A primeira linha define um delegate chamado VoidSemParametros, e podemos associá-lo a um método em que seu retorno seja do tipo void, e que não possua nenhum parâmetro. Vamos então declarar um método que satisfaça essas condições:



&nbsp; &nbsp; public&nbsp;void&nbsp;MetodoSemParametros()&nbsp;{&nbsp; &nbsp; &nbsp; &nbsp; Console.WriteLine("MetodoSemParametros");

&nbsp; &nbsp; }


Agora que temos um método com assinatura e retorno compatível, podemos instanciar o delegate passando o método como parâmetro em seu construtor:



&nbsp; &nbsp; /* Instancia VoidSemParametros utilizando construtor (new()) */&nbsp; &nbsp; var&nbsp;delegateNew =&nbsp;new&nbsp;VoidSemParametros(MetodoSemParametros);
&nbsp;&nbsp;&nbsp;&nbsp;delegateNew();&nbsp;/* executa */



Um outro método que também é compatível com o delegate VoidSemParametros:



&nbsp; &nbsp; public&nbsp;void&nbsp;OutroMetodoSemParametros()&nbsp;{&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;Console.WriteLine("OutroMetodoSemParametros");&nbsp; &nbsp; }



Dessa vez, instanciaremos o delegate passando diretamente o método:



&nbsp; &nbsp;&nbsp;/* Instancia VoidSemParametros apontando direto para um método com assinatura compatível */&nbsp; &nbsp; VoidSemParametros delegateExplicito = OutroMetodoSemParametros;&nbsp; &nbsp; delegateExplicito();&nbsp;/* executa */



Quando rodarmos este programa, ao executar o delegate, veremos que o método que foi passado como referência será executado, imprimindo alguma coisa na saída do Console.

Podemos também instanciar um delegate via anonymous methods:



&nbsp; &nbsp; /* Instancia VoidSemParametros utilizando anonymous delegate */&nbsp; &nbsp; VoidSemParametros delegateInline =&nbsp;delegate()&nbsp;{&nbsp; &nbsp; &nbsp; &nbsp; Console.WriteLine("Anonymous delegate...");&nbsp; &nbsp; };




Também é possível criar instâncias de delegates através da combinação de dois ou mais instâncias de delegates diferentes. Quando este delegate é executado, executará os métodos referenciados na ordem em que foram "somados":




&nbsp; &nbsp; /* Instancia um delegate combinando vários delegates */&nbsp; &nbsp; var&nbsp;delegateCombinado = delegateInline&nbsp;+&nbsp;delegateNew;




Pode-se também utilizar concatenação de delegates, utilizando o operador += em um delegate previamente instanciado:




&nbsp; &nbsp; /* Concatena delegates */&nbsp; &nbsp; delegateCombinado&nbsp;+= delegateExplicito;



&nbsp; &nbsp;&nbsp;/* O método Console.WriteLine possui várias sobrecargas, uma delas possui assinatura compatível com VoidSemParametros */&nbsp; &nbsp; delegateCombinado&nbsp;+= Console.WriteLine;



&nbsp; &nbsp; /*executa todos os delegates que foram combinados na mesma ordem*/&nbsp; &nbsp; delegateCombinado();



Agora vamos utilizar o segundo delegate que declaramos, na segunda linha, identificado como VoidComParametros. Para isso, vamos criar um método com assinatura compatível. O retorno deve ser void, e deve possuir um parâmetro do tipo string:



&nbsp; &nbsp; public&nbsp;void&nbsp;Escreve(string&nbsp;valor)&nbsp;{&nbsp; &nbsp; &nbsp; &nbsp; Console.WriteLine("Valor: {0}",&nbsp;valor);&nbsp; &nbsp; }



Seguindo a mesma linha de raciocínio, instanciaremos o delegate VoidComParametros de duas maneiras para que fique bem claro:



&nbsp; &nbsp; var&nbsp;escreve =&nbsp;new&nbsp;VoidComParametros(Escreve);
&nbsp;&nbsp;&nbsp;&nbsp;escreve("Escrito via método programa.Escreve");
&nbsp; &nbsp; /* O método Console.WriteLine possui várias sobrecargas, uma delas possui assinatura compatível com VoidComParametros */&nbsp; &nbsp; VoidComParametros escreveDireto = Console.WriteLine;
&nbsp;&nbsp;&nbsp;&nbsp;escreveDireto("Escrito via Console.WriteLine");



O terceiro delegate identificado como IntSemParametros possui um retorno do tipo Int e não possui parâmetros. Um método com assinatura compatível ficaria assim:



&nbsp; &nbsp; public&nbsp;int&nbsp;FuncaoQueRetornaInt(){&nbsp; &nbsp; &nbsp; &nbsp; return&nbsp;1;&nbsp; &nbsp; }



Da mesma maneira, para instanciar IntSemParametros, o código é este:



&nbsp; &nbsp; var&nbsp;funcao =&nbsp;new&nbsp;IntSemParametros(FuncaoQueRetornaInt);
&nbsp;&nbsp;&nbsp;&nbsp;escreve(funcao().ToString());



Para finalizar vamos criar dois métodos compatíveis com a assinatura do último delegate, identificado como IntComParametros:



&nbsp; &nbsp; public&nbsp;int&nbsp;Soma(int&nbsp;x,&nbsp;int&nbsp;y)&nbsp;{&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;x&nbsp;+&nbsp;y;&nbsp; &nbsp; }
&nbsp; &nbsp; public&nbsp;int&nbsp;Multiplica(int&nbsp;x,&nbsp;int&nbsp;y){&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;x&nbsp;*&nbsp;y;&nbsp; &nbsp; }



Vamos inicializar IntComParametros primeiro apontando para o método Soma, executá-lo, e depois vamos alterar o delegate para que aponte para o método Multiplica:



&nbsp; &nbsp; var&nbsp;calculadora =&nbsp;new&nbsp;IntComParametros(Soma);
&nbsp;&nbsp;&nbsp;&nbsp;var&nbsp;resultadoSoma =&nbsp;calculadora(3,3);&nbsp; &nbsp; Console.WriteLine("Soma: {0}",&nbsp;resultadoSoma);
&nbsp; &nbsp; calculadora = Multiplica;&nbsp;//mudou a referência para outro método
&nbsp;&nbsp;&nbsp;&nbsp;var&nbsp;resultadoMultiplicacao =&nbsp;calculadora(3,3);&nbsp; &nbsp; Console.WriteLine("Multiplicação: {0}",&nbsp;resultadoMultiplicacao);



Encontramos neste útlimo exemplo um caso de uso real para a utilização de delegates. Imagine uma aplicação do tipo calculadora. O usuário troca a operação (soma, multiplicação, divisão, etc) e trocamos o método referenciado pelo delegate, retornando resultados diferentes.



Action

Disponibilizado no .NET 3.5, amplamente utilizado em sintaxe LINQ e em expressões Lambda, Action nada mais é que um delegate com a seguinte assinatura:

&nbsp; &nbsp;
&nbsp; &nbsp; public&nbsp;delegate&nbsp;void&nbsp;Action();



O .NET Framework possui previamente já definidos uma série de delegates/Actions com parâmetros genéricos para suportar LINQ: Action&lt;T&gt;, Action&lt;T1,T2&gt;, Action&lt;T1, T2, T3&gt;, Action&lt;T1, T2, T3, T4&gt;, etc.&nbsp;



&nbsp; &nbsp; public&nbsp;delegate&nbsp;void&nbsp;Action&lt;T&gt;(T parametro);

&nbsp; &nbsp; public&nbsp;delegate&nbsp;void&nbsp;Action&lt;T1, T2&gt;(T param1, T2 param2);

&nbsp; &nbsp; public&nbsp;delegate&nbsp;void&nbsp;Action&lt;T1, T2, T3&gt;(T param1, T2 param2, T3 param3);

//etc...



Cada "T" representa um tipo de parâmetro que o método deve possuir. Por exemplo, Action&lt;string&gt; corresponde a um delegate do tipo void com um parâmetro string. Action&lt;string, int&gt; corresponde a um delegate do tipo void que recebe dois parâmetros: string, int. De forma genérica, podemos utilizar esses delegates sem a necessidade de declarar delegates específicos. Vejamos alguns exemplos:



&nbsp; &nbsp; /* Action é um delegate semelhante ao VoidSemParametros */
&nbsp;&nbsp;&nbsp;&nbsp;var&nbsp;action =&nbsp;new&nbsp;Action(MetodoSemParametros);&nbsp; &nbsp; Console.WriteLine(action.Method.Name);

&nbsp;&nbsp;&nbsp;&nbsp;/* Action&lt;T&gt; é um delegate semelhante ao VoidComParametros se T for do tipo String */&nbsp; &nbsp; Action&lt;string&gt;&nbsp;actEscreve = Escreve;
&nbsp;&nbsp;&nbsp;&nbsp;actEscreve("Escrito via Action&lt;string&gt;");



Func&lt;TResult&gt;

Semelhantes a Action's, exceto pelo fato de que possuem retorno do tipo &lt;TResult&gt;. A assinatura do delegate Func&lt;TResult&gt; é a seguinte:



&nbsp; &nbsp; public&nbsp;delegate&nbsp;TResult Func&lt;out&nbsp;TResult&gt;();



Do mesmo modo que Actions possuem diversas implementações disponíveis no .NET Framework, Func's também. Um detalhe importante é que o último parâmetro é sempre o tipo genérico de retorno que o método deve possuir em sua assinatura.



&nbsp; &nbsp; public&nbsp;delegate&nbsp;TResult Func&lt;T,&nbsp;out&nbsp;TResult&gt;(T parametro);

&nbsp; &nbsp;&nbsp;public&nbsp;delegate&nbsp;TResult Func&lt;T1, T2,&nbsp;out&nbsp;TResult&gt;(T1 param1, T2 param2 );

&nbsp; &nbsp; public&nbsp;delegate&nbsp;TResult Func&lt;T1, T2, T3,&nbsp;out&nbsp;TResult&gt;(T1 param1, T2 param2, T3 param3);

&nbsp; &nbsp; //etc.





Veja algumas maneiras de utilizar Func:


&nbsp; &nbsp; Func&lt;int&gt;&nbsp;funqInt = FuncaoQueRetornaInt;&nbsp; &nbsp; Console.WriteLine("Func&lt;int&gt;: {0}",&nbsp;funqInt());
&nbsp; &nbsp; Func&lt;int&gt;&nbsp;funqLambda =&nbsp;()&nbsp;=&gt;&nbsp;{&nbsp;return&nbsp;FuncaoQueRetornaInt();&nbsp;};&nbsp; &nbsp; Console.WriteLine("Lambda: {0}",&nbsp;funqLambda());
&nbsp; &nbsp; Func&lt;int,&nbsp;int,&nbsp;int&gt;&nbsp;funqParameters = Soma;&nbsp; &nbsp; Console.WriteLine("funqParameters: {0}",&nbsp;funqParameters(2,&nbsp;3));
&nbsp; &nbsp; funqParameters = Multiplica;&nbsp; &nbsp; Console.WriteLine("funqParameters: {0}",&nbsp;funqParameters(2,&nbsp;3));



Eventos

Delegates são utilizados na implementação de eventos em aplicações .NET. Num próximo post, mostrarei como criar e implementar seus próprios eventos.


LINQ

A partir da versão .NET 3.5, com o advento do LINQ, delegates (na forma de Actions e Func's) são amplamente utilizados.


Resumo

Neste post, apresentei como declarar e utilizar delegates. É importante notar que a partir do .NET 3.5, com o advento do LINQ, foi disponibilizado os delegates genéricos Action e Func, e provavelmente você nunca mais precisará declarar um delegate novo. Basta utilizar uma das versões de Action e Func que se encaixa nas suas necessidades.



Código
O código completo para este programa encontra-se no&nbsp;Gist