Descrição
Às vezes criamos delegates parecidos para diversos projetos,
mas nos esquecemos dos delegates “prontos” que existem para poupar nosso
trabalho. Este artigo mostra alguns dos delegates mais úteis do .NET Framework.
Tecnologias Relacionadas
Delegates, Threading, C# 3.0, .NET 3.5, Visual Studio 2008
Introdução
Como professor de linguagens .NET, percebo que existe grande
dificuldade do pessoal que não está acostumado ao paradigma de Programação
Orientada a Objetos (POO) em entender o que são delegates.
Na verdade, o conceito é simples: Delegates são tipos que,
ao invés criarem uma variável com valores e métodos, criam somente uma
referência a um método. Assim, podemos utilizar delegates para chamarmos a
execução de métodos sem que precisemos necessariamente saber quais métodos são
esses, já que a associação dos delegates aos métodos pode ser dinâmica e só
acontecer em tempo de execução.
Isso pode parecer um pouco paradoxal, já que podemos chamar
os métodos desejados diretamente. Claro, é óbvio. Mas e quando se tratarem de
chamadas “Cross-Threading”, ou seja, uma thread chamando um método ou
propriedade de outra thread? Aí a chamada direta de métodos não funciona. Se
tentarmos algo do gênero, teremos uma exceção do tipo InvalidOperationException,
com uma mensagem dizendo que “Operação entre controles ‘Cross-threading’ não é
permitida”. É aí que aparece o poder dos delegates.
Que os delegates são úteis não há dúvida. Mas será que precisamos
escrever um delegate para cada vez que precisarmos executar alguma coisa em
outra thread? Será que o time de desenvolvimento do .NET Framework já não
pensou eu algo tão simples? Pior que já.
O .NET Framework traz uma série de delegates padrão para que
possam ser utilizados em códigos com as mais diversas finalidades. Vamos
abordar aqui alguns dos delegates mais úteis e mais usados pelo pessoal do
“Cross-Threading”.
O Delegate Action
O Delegate Action encapsula um método que não retorna
nada (void) e pode receber de zero a quatro parâmetros de entrada. Sua
assinatura possui cinco sobrecargas:

Este delegate está contido no assembly System.Core.dll,
que faz parte do .NET Framework 3.5.
Um cenário bem comum para a utilização de um delegate Action
é quando precisamos efetuar uma ação sobre os itens de uma lista de elementos.
Veja.
![Caixa de texto: class Program
{
static void Main(string[] args)
{
var lista = new[] { "a", "b" };
Array.ForEach(lista, Console.WriteLine);
Console.WriteLine("Feito!");
}
}](/artigos/img_artigos/MarcioPauloMelloMartins/Delegates/image002.gif)
O primeiro parâmetro do método ForEach é a lista do tipo T
que se deseja manipular; já o segundo parâmetro é um método que respeita a
assinatura do delegate Action<T>. No caso acima, tanto a lista quanto o
método são do tipo System.String, o que possibilita a correta execução do
mesmo. Veja que também não há retorno para o método Console.WriteLine, o que
está de acordo com a assinatura do delegate Action.
A saída do código acima é:
a
b
Feito!
Podemos, como variação, declarar diretamente o delegate Action
para utilizamos na declaração do ForEach. Assim:
![Caixa de texto: class Program
{
static void Main(string[] args)
{
var lista = new[] { "a", "b" };
// O delegate Action substitui a chamada direta ao método
Action<string> acao = new Action<string>(Console.WriteLine);
Array.ForEach(lista, acao);
Console.WriteLine("Feito!");
}
}](/artigos/img_artigos/MarcioPauloMelloMartins/Delegates/image003.gif)
A saída do código acima é idêntica à do código anterior:
a
b
Feito!
Também é possível criarmos um método para executar outra
ação, conforme o código abaixo.
![Caixa de texto: class Program
{
static void Main(string[] args)
{
var lista = new[] { "a", "b" };
// Agora apontamos o delegate para o novo método
Action<string> acao = new Action<string>(ImprimirNoConsole);
Array.ForEach(lista, acao);
Console.WriteLine("Feito!");
}
private static void ImprimirNoConsole(string mensagem)
{
Console.WriteLine("Impressão customizada - {0}", mensagem);
}
}](/artigos/img_artigos/MarcioPauloMelloMartins/Delegates/image004.gif)
Ao executarmos o código acima, teremos:
Impressão customizada - a
Impressão customizada - b
Feito!
Para tornarmos tudo muito mais interessante, podemos fazer
um array de delegates que executam métodos diferentes, criando assim um método
de impressão diferente para cada iteração. Veja:
![Caixa de texto: class Program
{
static void Main(string[] args)
{
var lista = new[] { "a", "b" };
// Criando um array de delegates, podemos executar
// ambos os métodos para impressão da lista
var acoes =
new Action<string>[] { Console.WriteLine, ImprimirNoConsole };
// Agora usamos uma expressão lambda para passar
// um delegate de cada vez
Array.ForEach(acoes, a => Array.ForEach(lista, a));
Console.WriteLine("Feito!");
}
private static void ImprimirNoConsole(string mensagem)
{
Console.WriteLine("Impressão customizada - {0}", mensagem);
}
}](/artigos/img_artigos/MarcioPauloMelloMartins/Delegates/image005.gif)
Executando este código, teremos:
a
b
Impressão customizada - a
Impressão customizada - b
Feito!
Bem legal, não acha?
O Delegate Predicate
O Delegate Predicate<T> encapsula um método que
recebe apenas um parâmetro e retorna um booleano (true ou false). Este delegate
está localizado no assembly mscorlib.dll, versão 2.0.
Seu uso é bem simples, você passa um parâmetro do tipo T e o
método faz alguma validação e retorna se é true ou false. A
assinatura do delegate é a seguinte:
public delegate bool Predicate<T>( T
obj )

Os delegates permitem que possamos fazer coisas bem legais,
como colocar código dentro de uma variável. Vamos fazer dois exemplos que geram
o mesmo resultado: no primeiro, vamos atribuir uma expressão lambda para uma
variável Predicate; no segundo, vamos utilizar um método anônimo.
Vejamos:
![Caixa de texto: class Program
{
static void Main(string[] args)
{
//Predicate como expressão lambda
Predicate<string> p = objetoTeste => true;
// Isto sempre vai funcionar pois está sempre retornando true
if (p("Marcio"))
Console.WriteLine("Cara bacana!");
Console.WriteLine("Feito!");
}
}](/artigos/img_artigos/MarcioPauloMelloMartins/Delegates/image007.gif)
![Caixa de texto: class Program
{
static void Main(string[] args)
{
//Predicate como método anônimo
Predicate<string> p = delegate { return true; };
// Isto sempre vai funcionar pois está sempre retornando true
if (p("Marcio"))
Console.WriteLine("Cara bacana!");
Console.WriteLine("Feito!");
}
}](/artigos/img_artigos/MarcioPauloMelloMartins/Delegates/image008.gif)
A execução dos dois códigos acima gera a seguinte saída:
Cara bacana!
Feito!
Usamos um delegate Predicate diversas vezes sem nos
darmos conta, pois um uso muito comum deles é durante o uso das classes LINQ.
Por exemplo, o parâmetro do método Where é um Predicate<T> que vai dizer
se um elemento corresponde ou não ao critério de seleção do mesmo. Podemos
tanto utilizar a forma comum com a expressão lambda:
List<int> lista = new
List<int> { 1, 2, 3, 4 };
var pares = lista.Where(item => item % 2
== 0);
Quanto
podemos declarar um Predicate<T> passando a expressão lambda previamente:
List<int> lista = new
List<int> { 1, 2, 3, 4 };
Predicate<int> p = objetoTeste =>
objetoTeste % 2 == 0;
var pares = lista.Where(item => p(item));
Detalhe: o Predicate
pode estar apontando para um método, afinal ele é um delegate, lembram-se?
Basta que este método tenha um único parâmetro de entrada e retorne true
ou false.
O exemplo abaixo mostra como retornar somente os elementos
que tiverem o tamanho maior ou igual a quatro:
![Caixa de texto: class Program
{
static void Main(string[] args)
{
List<string> lista =
new List<string> { "um", "dois", "três", "quatro", "cinco" };
//Predicate como expressão lambda
Predicate<string> p = objetoTeste => objetoTeste.Length >= 4;
var i = from item in lista
where p(item)
select item;
i.ToList().ForEach(Console.WriteLine);
Console.WriteLine("Feito!");
}
}](/artigos/img_artigos/MarcioPauloMelloMartins/Delegates/image009.gif)
A saída do código acima será:
dois
três
quatro
cinco
Feito!
Vejamos agora um exemplo com dois Predicates: um checa se a
lista tem mais de seis itens e verifica se o item possui a string “idade”; o
outro checa se o tamanho do item é maior que seis. Os dois predicates são
combinados, então, em uma expressão LINQ para filtrar o resultado desejado.
![Caixa de texto: class Program
{
static void Main(string[] args)
{
List<string> lista =
new List<string> { "Marcio", "cidade", "caminhão", "túnel", "capacidade", "enfermidade", "orgulho" };
// Código dentro de uma expressão lambda
Predicate<string> p = objetoTeste =>
{
if (lista != null && lista.Count > 6)
{
return objetoTeste.Contains("idade");
}
return false;
};
Predicate<int> e = objetoTeste => objetoTeste > 6;
var i = from item in lista
where p(item) && e(item.Length)
select item;
i.ToList().ForEach(Console.WriteLine);
Console.WriteLine("Feito!");
}
}](/artigos/img_artigos/MarcioPauloMelloMartins/Delegates/image010.gif)
O código acima resulta em:
capacidade
enfermidade
Feito!
A capacidade de referenciar código ao invés de métodos
em um delegate é uma característica muito útil em C#, pois podemos evitar a
escrita de muitos métodos específicos, em determinados casos.
O Delegate Function
O delegate Function é, provalvelmente, o mais
poderoso de todos os delegates de uso comum. Ele também vem contido no assembly
System.Core.dll, que acompanha o .NET Framework 3.5.
Enquanto uma Action não retorna nada e um Predicate
retorna somente true ou false, uma Function pode retornar qualquer tipo
de dado, além de comportar entre zero e quatro parâmetros de entrada. Suas
assinaturas podem ser as seguintes:

No exemplo abaixo, eu crio uma Func cujos parâmetros
de entrada são uma List<int> e um inteiro. Para cada item é feita uma
soma com todos os itens da lista, e a Func retorna se a soma é par.
![Caixa de texto: class Program
{
static void Main(string[] args)
{
List<int> lista = new List<int> { 1, 2, 3, 4, 5, 6, 7 };
// Código dentro de uma expressão lambda
Func<List<int>, int, bool> f = (itens, item) =>
{
if (itens != null && itens.Count > 6)
{
int soma = 0;
itens.ForEach(it => soma += it + item);
return soma % 2 == 0;
}
return false;
};
var i = from item in lista
where f(lista, item)
select item;
i.ToList().ForEach(Console.WriteLine);
Console.WriteLine("Feito!");
}
}](/artigos/img_artigos/MarcioPauloMelloMartins/Delegates/image012.gif)
O código acima resulta em:
2
4
6
Feito!
Conclusão
Os delegates são uma mão na roda em diversos momentos de
nossas vidas como profissionais de desenvolvimento. Suas características mais
visíveis são durante a execução de chamadas “Cross-threading”, mas as
utilidades deles vão além, envolvendo técnicas refinadas de criação de
instâncias de delegates “on the fly”. Porém, estes três amigos podem nos ser
muito úteis em quaisquer situações, já que economizam tempo precioso. Que tal
nos lembrarmos deles da próxima vez em que formos escrever um delegate?