C# - Apresentando o delegate Predicate


 Neste artigo eu vou apresentar os conceitos básicos envolvidos na utilização do delegado Predicate na linguagem C#.

Para você poder acompanhar e entender este artigo você tem que saber o que é um delegate. Se você tem dúvidas leia o meu artigo : C# - Delegates e Eventos : Conceitos básicos e depois prossiga na leitura.

Eu não vou entrar em detalhes sobre o que é um delegate (para detalhes leia o artigo) vou apenas apresentar a sua definição (uma delas):

Um Delegate é um ponteiro para um método. Um Delegate pode ser passado como um parâmetro para um método. Podemos mudar a implementação do método dinamicamente em tempo de execução, a única coisa que precisamos para fazer isso seria manter o tipo de parâmetro e o tipo de retorno.

Conceitos Básicos

1- O delegate Predicate

O Delegate Predicate representa o método que define um conjunto de critérios e determina se o objeto especificado atende os critérios.

O delegate Predicate<T> que usa parâmetro(s), executa o código usando os parâmetro(s) e sempre retorna um valor booleano.

Exemplo:

static void Main(string[] args)
 {
      // Este Predicate retorna true se o argumento for 1
      Predicate<int> EUm =  x => x == 1;
      // Predicate retorna true se o argumento for maior que 5
      Predicate<int> EmaiorQueCinco = (int x) => x >= 5;

      // Teste das intancias de Predicate com parâmetros
      Console.WriteLine(EUm.Invoke(1));
      Console.WriteLine(EUm.Invoke(2));
      Console.WriteLine(EmaiorQueCinco.Invoke(3));
      Console.WriteLine(EmaiorQueCinco.Invoke(10));
      Console.ReadKey();
 }

Nestes exemplos eu estou usando expressões lambdas para definir o delegate.

Sintaxe:

public delegate bool Predicate<in T>(T obj )

Parâmetros de tipo

in T -
O tipo do objeto a ser comparado.

Este parâmetro de tipo é contravariante. Ou seja, você pode usar o tipo especificado ou qualquer tipo que seja menos derivado. Para obter mais informações sobre covariância e contravariância, consulte Covariância e contravariância em genéricos.

Parâmetros

obj
Tipo: T
O objeto a ser comparado com os critérios definidos dentro do método representado por esse delegação.

Valor de retorno

Tipo: System.Boolean
true se obj atende aos critérios definidos dentro do método representado por esse delegado; caso contrário, false.

Este delegado é usado por vários métodos das classes de Array e de List<T> para pesquisar os elementos na coleção.

Normalmente, o delegate Predicate<T> é representado por uma expressão de lambda.

Como as variáveis com escopo local estão disponíveis para a expressão de lambda, é fácil testar uma condição que não é conhecida precisamente em tempo de compilação.

Usando o delegate Predicate<T>

No exemplo a seguir temos um exemplo clássico de uso do delegate Predicate<T> para encontrar um 
número interiro em um array de número que atende a um determinado critério. 
No exemplo o número deve ser maior que 7000.

Exemplo :

using System;
namespace Delegate_Predicate
{
    class Program
    {
        static void Main(string[] args)
        {
            // Cria um array de números inteiros
            int[] numeros = { 2343, 5349, 6039, 4326, 3038, 1987, 8762, 1098 };
            // Define o delegato Predicate<T>
            Predicate<int> predicate = EncontraNumeros;
            // Encontra o numero que é maior que 7000
            int numero = Array.Find(numeros, predicate);
            // exibe o primeiro numero encontrado que atente o critério
            Console.WriteLine("Encontrado: n = {0} ", numero);
            Console.ReadKey();
        }
        //função que retorna true (n1>7000) / false (n1<=7000) 
        private static bool EncontraNumeros(int n1)
        {
            return n1 > 7000;
        }
    }
}

Vejamos o mesmo exemplo acima usando uma expressão lambda para representar o delegate Predicate<T> :

using System;

namespace Delegate_Predicate
{
    class Program
    {
        static void Main(string[] args)
        {
            // Cria um array de numeros inteiros
            int[] numeros = { 2343, 5349, 6039, 4326, 3038, 1987, 8762, 1098 };

            // Encontra o numero que é maior que 7000
        
   int numero = Array.Find(numeros, n => n > 7000);

            // exibe o primeiro numero encontrado que atente o criterio
            Console.WriteLine("Encontrado: n = {0} ", numero);
            Console.ReadKey();
        }
    }
}

Cada elemento do array de números é passado para a expressão de lambda até que a expressão localize um elemento que atende aos critérios de pesquisa. Nesse caso, a expressão de lambda retorna true se o número for maior que 7000.

Note como o código ficou mais conciso usando a expressão lambda.

Então podemos simplificar muito o código no uso dos delegates Predicate<T> usando expressões lambadas. Veja abaixo um exemplo que mostra a evolução desde o código tradicional, o uso do delegate e finalmente da expressão lambada:

Para este exemplo vamos criar uma classe Inventario :

   public class Inventario
   {
        public int Id { get; set; }
        public int Estoque { get; set; }
        public double Preco { get; set; }
   }

E vamos criar uma lista de itens no inventário :

public static List<Inventario> CriaListaInventario()
{
   List<Inventario> listaInventario = new List<Inventario>();
   listaInventario.Add(new Inventario { Id = 1, Estoque = 5, Preco = 1.99 });
   listaInventario.Add(new Inventario { Id = 2, Estoque = 5, Preco = 2.09 });
   listaInventario.Add(new Inventario { Id = 3, Estoque = 5, Preco = 0.75 });
   listaInventario.Add(new Inventario { Id = 4, Estoque = 5, Preco = 4.25 });
   listaInventario.Add(new Inventario { Id = 5, Estoque = 5, Preco = 5.60 });
   listaInventario.Add(new Inventario { Id = 6, Estoque = 5, Preco = 2.15 });
   return listaInventario;
}

A seguir vamos definir um código para localizar itens no inventário usando o código tradicional, o delegate Predicate<T>, e simplificar o código uma usando uma expressão lambda. Veja como ficou:

 public Inventario LocalizaInventario(int inventarioId)
  {
            foreach (Inventario item in listaInventario)
            {
                if (item.Id == inventarioId)
                    return item;
            }

            return null;
 }
Este é o código tradicional que pode ser usado para localizar item na lista de inventário.

Usamos o laço foreach e percorremos a lista verificando se o código do item informado esta na lista.

  public static Inventario LocalizaInventario(int inventarioId)
   {
            Predicate<Inventario> pred = delegate(Inventario item)
            {
                return item.Id == inventarioId;
            };

            return listaInventario.Find(pred);
  }
Podemos melhorar um pouco usando o método Find da classe List<T>.

Aqui o argumento do método Find é o delegate Predicate<T>

Aqui temos um delegate inline que cria um método anônimo e pode ser atribuído à variável Predicate que é passado ao método Find.

  public static Inventario LocalizaInventario(int inventarioId)
   {
     Predicate<Inventario> pred =
item => item.Id ==inventarioId;
     return listaInventario.Find(pred);
  }
Podemos simplificar ainda mais passando usando uma expressão lambda.

Aqui a expressão lambda esta sendo atribuída ao delegate Predicate pred que pode ser reutilizado.

  public static Inventario LocalizaInventario(int inventarioId)
  {
    return listaInventario.Find(item => item.Id == inventarioId);
  }
Podemos simplificar ainda mais ignorando a variável Predicate pred em favor da utilização da expressão lambda no método Find.

Vimos então que o tipo Predicate na linguagem C# armazena um método que recebe um parâmetro e retorna um valor de verdadeiro ou falso. Assim, um delegate Predicate retorna sempre verdadeiro ou falso.

A seguir temos outros métodos de List<T> que também usam o tipo Predicate como argumento :

Para ver a lista completa, consulte http://msdn.microsoft.com/en-us/library/s6hkc2c4.aspx

Pegue o exemplo do projeto aqui:  Delegate_Predicate.zip

Porque os judeus pedem sinal, e os gregos buscam sabedoria;
Mas nós pregamos a Cristo crucificado, que é escândalo para os judeus, e loucura para os gregos.
Mas para os que são chamados, tanto judeus como gregos, lhes pregamos a Cristo, poder de Deus, e sabedoria de Deus.

1 Coríntios 1:22-24

Referências:


José Carlos Macoratti