Fala pessoal, tudo bom?

Nova categoria no blog, C#.  No primeiro post desse novo tópico vou falar sobre delegates. Antes de iniciarmos, aconselho a leitura da seguinte publicação que fala sobre tipos. Para os exemplos foi utilizado o Visual Studio 2013 com uma Windows Forms Application.

INTRODUÇÃO

No decorrer do desenvolvimento de um sistema é muito comum que em algum momento surja a necessidade de criar um método que receba por parâmetro outro método, esse tipo de parâmetro é chamado de função de retorno de chamada ou simplesmente callback. Em algumas linguagens como C++ não gerenciado e javaScript bastaria criar o método e passa-lo por parâmetro que tudo estaria funcionando. Em C# temos que realizar alguns passos para que um método possa ser passado por parâmetro.

Um método não é um tipo e C# é uma linguagem de código gerenciado, é preciso especificar o tipo de cada parâmetro que um método receberá para que o compilador realize validações nas chamadas desse método. Então podemos dizer que para passar um método por parâmetro este deverá ser de um tipo? Isso mesmo. O .NET Framework oferece um mecanismo que nos permite criar um tipo que referencie métodos, este mecanismo é chamado de delegate.

 

DELEGATE

Em C# um delegate é um tipo de referência que nos permite armazenar uma ou mais referência a método. A declaração de um delegate funciona como uma espécie de assinatura, onde, devemos especificar como deverão ser os métodos que esse tipo referenciará, parece um pouco complicado, mas acredite, não é! Para provar isso, vamos começar a ver como funciona na prática. Veja a declaração de um delegate:

[sourcecode language="csharp"]

public delegate void ExibirMensagem(string msg);

[/sourcecode]

Analisando a criação do delegate:

  1. public: Modificador de acesso;
  2. delegate:  Palavra chave para criação do tipo delegate;
  3. void: Especifica o tipo de retorno que o método deverá ter para que esse delegate seja capaz de armazenar sua referência, nesse caso é void, ou seja, sem retorno.
  4. ExibirMensagem: Definição de um nome para o tipo, a gosto do programador, desde que não seja uma palavra reservada.
  5. (string msg): Especifica quantidade e tipo de parâmetro que o método deverá ter para que esse delegate seja capaz de armazenar sua referência.

Feita essa análise, podemos dizer que ao instanciarmos um objeto do tipo que acabamos de criar, este objeto poderá referenciar qualquer método que siga o padrão de nossa análise. O trecho de código a seguir mostra um método com esse padrão.

[sourcecode language="csharp"]
//Criando um tipo delegate
public delegate void ExibirMensagem(string msg);

//Método com o mesmo padrão do tipo delegate criado acima.
public void EscreverNaTela(string texto)
{
Console.WriteLine(texto);
}

[/sourcecode]

O método EscreverNaTela tem o tipo de retorno void e recebe um único parâmetro do tipo string, seguindo o mesmo padrão do tipo delegate que criamos.

Já temos em nosso exemplo um tipo delegate e um método com o mesmo padrão.  Agora vamos criar um objeto do tipo ExibirMensagem que receberá a referência do método EscreverNatela. Veja isso no trecho de código abaixo:

[sourcecode language="csharp"]
//Criando um tipo delegate
public delegate void ExibirMensagem(string msg);

//Método com o mesmo padrão do tipo delegate criado acima.
public void EscreverNaTela(string texto)
{
Console.WriteLine(texto);
}

private Form1_Load(object sender, EventArgs e)
{
//criando um objeto do tipo delegate ExibirMensagem
ExbirMensagem MeuDelegate = new ExibirMensagem(EscreverNaTela);
}

[/sourcecode]

Criamos um objeto MeuDelegate do tipo delegate ExbibirMensagem. Ao criarmos uma nova instância de ExibirMensagem passamos por parâmetro o método EscreverNaTela. Como assim "passamos por parâmetro"? Vou explicar rapidamente essa parte para não nos aprofundarmos muito, esse não é o objetivo. Quando o compilador encontra uma linha de código de criação de um tipo delegate, ele transforma essa linha em uma classe com quatro métodos, um desse métodos é o construtor da classe que recebe dois parâmetros. Um desses parâmetros é do tipo objetc e o outro do tipo int32. Bom, então se pensarmos bem, quando o trecho de código da figura 3 for executando algum erro ocorrerá, já que estamos passando somente um parâmetro para um construtor que pede dois, certo? Errado! O compilador saberá que está sendo criado um novo objeto delegate e cuidará de passar os parâmetros corretos, sendo, o objeto que está sendo criado para o object e uma espécie de token que identifica o método que está sendo referenciado para o int32.

Agora que já temos tudo pronto, vamos utilizar nosso objeto delegate . Veja um exemplo de chamada e da saída do nosso objeto no trecho de código a seguir:

[sourcecode language="csharp"]
//Criando um tipo delegate
public delegate void ExibirMensagem(string msg);

//Método com o mesmo padrão do tipo delegate criado acima.
public void EscreverNaTela(string texto)
{
Console.WriteLine(texto);
}

private Form1_Load(object sender, EventArgs e)
{
//criando um objeto do tipo delegate ExibirMensagem
ExbirMensagem MeuDelegate = new ExibirMensagem(EscreverNaTela);

//Chamada do objeto delegate
MeuDelegate("Introdução ao tipo delegate do C#");
}

[/sourcecode]

Podemos ver que quando tudo está pronto fica mais simples de se entender. Para a chamada, utilizamos o objeto MeuDelegate passando um parâmetro string que é o que ele espera. Na saída foi executado o método Console.WriteLine() que foi definido no método EscreverNaTela. Veja na Figura 1 a saída dessa chamada.

[caption id="attachment_244" align="alignnone" width="1920"]Figura 1 - Chamada de um objeto delegate e sua saída Figura 1 - Chamada de um objeto delegate e sua saída[/caption]

Vamos ver agora o exemplo que foi citado no inicio do post "Passar um método por parâmetro". Para fazer isso, precisamos implementar um novo método que receberá por parâmetro um tipo ExibirMensagem. Veja essa implementação no trecho de código a seguir:

[sourcecode language="csharp"]
//Criando um tipo delegate
public delegate void ExibirMensagem(string msg);

//Método com o mesmo padrão do tipo delegate criado acima.
public void EscreverNaTela(string texto)
{
Console.WriteLine(texto);
}

private Form1_Load(object sender, EventArgs e)
{
//criando um objeto do tipo delegate ExibirMensagem
ExbirMensagem MeuDelegate = new ExibirMensagem(EscreverNaTela);

//Passando por parâmetro um delegate que está referenciando um método
Calculo(MeuDelegate);
}

//Método que recebe um delegate por parâmetro
//O delegate está referenciando um método
public void Calculo(ExibirMensagem ExibirMsg)
{
//Executando o delegate
ExibirMsg((1+1).ToString());
}

[/sourcecode]

No trecho acima, podemos ver que diferente do exemplo anterior a esse nós não estamos fazendo a chamada do delegate diretamente, nós estamos enviado por parâmetro para um método e lá será feita a chamada desse delegate. Ao ser executado, esse programa mostrará na tela o resultado do calculo (1+1).

Pessoal, estes foram exemplos bem simples, acredito que nunca serão usados no dia-a-dia. É importante saber como funcionam delegates, pois, vão existir situações onde o seu uso será a melhor opção. Um exemplo disso? Imagine que seu sistema precise de uma janela genérica para que o usuário insira alguma informação em algum momento (cpf, senha, código, cartão, etc), essas informações precisam ser validadas, como fazer isso? Criar uma janela para cada informação? Beleza, vai funcionar, mas, teremos uma aplicação bem poluída de forms. Outra opção, passar um parâmetro especificando qual informação deverá ser validada naquele momento, vai funcionar também, mas, imagine a quantidade de if's ou case's que precisarão ser implementadas? Na minha opinião, a melhor saída nessa situação seria o uso de um delegate. Crie uma instância de seu tipo delegate na sua janela e receba no construtor um delegate  que fará referência a um método que validará a informação inserida. Conseguem entender que só se saberá o que será executado por esse delegate em tempo de execução? Se conseguirem entender isso esse post atingiu seu objetivo! :)

[Editado 26/08/2014] Para complementar este estudo sobre delegates recomendo que leiam a publicação  Quando utilizar delegates, funcs ou actions no C#? do MPV André Alves de Lima. [/Editado]

Espero ter ajudado a esclarecer um assunto que as vezes é tratado como se fosse de outro mundo.

Até o próximo post!

REFERÊNCIAS

Programação Aplicada com .Net Framework - Jeffrey Richter

Delegate (Refêrencia de C#) - http://msdn.microsoft.com/pt-br/library/900fyy8e.aspx