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 Martins



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:

image

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!");
        }
    }

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!");
        }
    }

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);
        }
    }

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);
        }
    }

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 )

http://blogs.vertigo.com/personal/petar/blog/Media/WindowsLiveWriter/CoolDelegates_C55B/image_18.png

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!");
        }
    }

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!");
        }
    }

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!");
        }
    }

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!");
        }
    }

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:

image

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!");
        }
    }

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?

Marcio Paulo Mello Martins

Marcio Paulo Mello Martins - Bacharel em Ciência da Computação pela FASP; MCP, MCAD, MCSD, MCTS, MCPD e MCT. Atua há mais de 10 anos com desenvolvimento de software e treinamento em tecnologias Microsoft, trabalhando hoje como Analista Desenvolvedor na F|Camara (http://www.fcamara.com.br), além de ser proprietário da Logical Docs (http://www.logicaldocs.com.br), empresa do ramo de gerenciamento eletrônico de documentos. Quando sobra um tempinho, é pianista e toca em uma banda de Jazz.