Desenvolvimento - C#
Conheça os Delegates Action, Predicate e Function em C#
À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 (C#): Action, Predicate e Function.
por Marcio Paulo Mello MartinsDescriçã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.
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:
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.
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:
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:
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:
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.
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.
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?