C# - Usando expressões lambdas para os argumentos Predicate, Func e Action em listas genéricas


 Neste artigo eu vou mostrar como usar expressões lambdas para argumentos Predicate, Func e Action em listas genéricas usando a 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 seguir fazendo isso seria manter o tipo de parâmetro e o tipo de retorno.

Sugiro também que leia os artigos sobre os delegates Predicate, Func e Action já publicados no site. (veja as referências)

Recursos usados :

Conceitos Básicos

As listas genéricas são uma ótima maneira de armazenar, recuperar e geralmente trabalham com objetos. A maioria dos métodos em uma classe de lista genérica toma como argumentos tanto um Predicate, uma Func ou uma Action e isso pode confundir quem esta iniciando na linguagem.

Neste artigo eu mostro como usar esses recursos de forma a tornar o seu código mais elegante usando expressões lambda para todos esses três tipos de argumentos.

Apenas para recordar vejamos as definições desses 3 delegates :

  1. Predicate - Delegate que toma parâmetros, executa o código usando os parâmetros e sempre retorna um valor Boleano;
  2. Func - Delegate que pode tomar até 16 parâmetros, executa o código usando os parâmetros e retorna um tipo que você definiu;
  3. Action - Delegate que toma parâmetros, executa o código usando os parâmetros e não retorna nada;

Assim em uma lista genérica esses argumentos permitem ao desenvolvedor executar um método para cada um dos itens na lista genérica.

Preparando o ambiente

Abra o VS Express 2013 for Windows Desktop e clique em New Project;

A seguir selecione a linguagem Visual C# e o template Console Application;

Informe o nome GenericList_Argumentos_Delegates e clique no botão OK;

A seguir no  meu PROJECT clique em Add Class e informe o nome Estoque definindo o código conforme abaixo:

namespace GenericList_Argumentos_Delegates
{
    public  class Estoque
    {
        public int Id { get; set; } 
        public string Item { get; set; }
        public int QuantidadeEmEstoque { get; set; } 
        public double PrecoUnitario { get; set; }

       public Estoque(int Codigo, string Nome, int Quantidade, double Preco)
       {
            Id = Codigo;
            Item = Nome;
            QuantidadeEmEstoque = Quantidade;
            PrecoUnitario = Preco;
       }
    }
}

Nossa classe Estoque possui 4 propriedades e um construtor que iremos usar para criar uma lista genérica de itens em estoque.

A seguir na classe Program vamos definir a criação da lista através do método preencheLista() e a sua exibição pelo método exibeLista() conforme o código a seguir:

namespace GenericList_Argumentos_Delegates
{
    class Program
    {
        public static List<Estoque> _listaInventario = new List<Estoque>();
        static void Main(string[] args)
        {
            preencheLista();
            exibeLista();
            Console.ReadKey();
        }
        static void preencheLista()
        {
            Estoque estq = new Estoque(1,"Caneta", 10, 1.75);
            _listaInventario.Add(estq);
            _listaInventario.Add(new Estoque(2, "Lapis", 30, 2.45));
            _listaInventario.Add(new Estoque(3, "Clipes", 10, 3.30));
            _listaInventario.Add(new Estoque(4, "Caderno", 20, 4.35));
            _listaInventario.Add(new Estoque(5, "Livro", 11, 9.05));
            _listaInventario.Add(new Estoque(6, "Estojo", 9, 6.45));
            _listaInventario.Add(new Estoque(7, "Regua", 15, 1.45));
            _listaInventario.Add(new Estoque(8, "Papel", 23, 5.50));
            _listaInventario.Add(new Estoque(9, "Grampos", 8, 6.15));
            _listaInventario.Add(new Estoque(10, "Grafite", 12, 1.45));
        }
        static void exibeLista()
        {
            Console.WriteLine("Lista de itens do estoque");
            foreach (Estoque estq in _listaInventario.ToList())
                Console.WriteLine(estq.Id + "\t" + estq.Item + "\t" + estq.QuantidadeEmEstoque + "\t" + estq.PrecoUnitario);
        }   
    }
}

Executando o projeto neste momento teremos a lista de itens em estoque conforme a figura a seguir:

Agora que já temos tudo pronto vamos mostrar como usar expressões lambdas com argumentos Predicate, Func e Action da lista genérica.

Métodos de uma lista genérica com argumento Predicate

O método para localizar um item, ou método Find, da lista genérica é um exemplo de método que toma um argumento Predicate.

Antes de criar a classe Predicate vamos dar uma olhada como a localização de um item é feita de forma tradicional:

 public Estoque FindEstoqueForeach(int itemId)
 {
     foreach (Estoque item in _listaInventario)
     {
                if (item.Id == itemId)
                    return item;
      }
      return null;
}

O código usa um laço foreach para percorrer a lista de itens e localizar o item pelo seu id.

O código funciona mas existe uma maneira melhor de realizar esta tarefa. A classe da lista genérica já possui um método Find e o argumento para este método é a classe Predicate.

Lembre que o delegate Predicate toma parâmetros, executa o código usando os parâmetros e sempre retorna um boleano.

Na linguagem C# um delegate cria um método anônimo e este pode ser atribuído a uma variável Predicate que pode então ser passada para o método Find. Veja como fica o método usando o argumento Predicate:

public Estoque FindEstoquePredicate(int itemId)
{
   Predicate<Estoque> pred = delegate(Estoque item)
   {
       return item.Id == itemId;
   };
  return _listaInventario.Find(pred);
}

 

Isto pode ser útil se você desejar armazenar o delegate para reutilizar. Agora vamos usar as expressões lambdas para simplificar o código. Veja como ficou mais enxuto:

public Estoque FindEstoque(int itemId)
{
    return _listaInventario.Find(item => item.Id == itemId);
}


Vamos entender o código:

1- A parte esquerda representa os parâmetros para a função a serem avaliados, neste caso, o tipo Estoque;

2- O lado direito é uma expressão booleana que irá avaliar como verdade quando o método Find encontra o item com 'itemId';

3- Se o item não for encontrado, o método FindEstoque retornará null;


Além do método Find temos os seguintes métodos de uma lista genérica que possuem um argumento Predicate:

Métodos de uma lista genérica com argumento Func

Existem muitos métodos de Lista genérica que usam um tipo Func como um argumento. Lembre-se da definição do delegate Func: "um delegado que leva parâmetro(s), executa o código usando o parâmetro(s) e retorna o tipo que você definir".

O argumento Func é mais flexível do que Predicate pois ele pode retornar qualquer tipo, não apenas um boleano. Isso depende do método da lista quanto à assinatura do Func. Alguns tomam um argumento, outros mais.

Um dos métodos mais úteis é o método de extensão Enumerable chamado Where.

Pense na cláusula SQL WHERE quando se utiliza este método. Ele permite que você obtenha um subconjunto da lista. A seguir temos um exemplo:
public List<Estoque> FindItemsWherePrecoMenorCustoFuncDelegate(double custoUnitario)
{
     Func<Estoque, bool> whereFunc = delegate(Estoque item)
     {
          return item.PrecoUnitario < custoUnitario;
     };
   return _listaInventario.Where(whereFunc).ToList<Estoque>();
}

Note que para retornar uma lista de itens do estoque você precisa chamar o método ToList() pois Where retorna um IEnumerable e a consulta não é executada até que o resultado seja enumerado.

Podemos simplificar o método acima usando uma expressão lambada:

public List<Estoque> FindItemsWherePrecoMenorCustoFuncDelegate(double custoUnitario)
{
     Func<Estoque, bool> whereFunc = item => item.PrecoUnitario < custoUnitario;    
     return _listaInventario.Where(whereFunc).ToList<Estoque>();
}

Uma simplificação melhor simplesmente passa a expressão lambda diretamente para o método Where conforme vemos abaixo:

public List<Estoque> FindItemsWherePrecoMenorQueCusto(double custoUnitario)
{
     return _listaInventario.Where(item => item.PrecoUnitario < custoUnitario).ToList<Estoque>();
}

A seguir temos outros métodos de uma lista genérica que usa o argumento Func:

Métodos de uma lista genérica com argumento Action

Existe um método de uma lista genérica que toma um argumento Action. É o método ForEach que executa o código especificado para cada um dos itens na lista. (Lembre-se que o delegate Action não retorna nada.)

A seguir temos um exemplo onde estamos alterando o valor do preço para todos os itens da lista:

public void AlterarPreco(double novoPreco)
{
      _listaInventario.ForEach(item => item.PrecoUnitario = novoPreco);
}

Da mesma forma podemos realizar qualquer operação sobre cada item da lista usando ForEach. A seguir temos um exemplo onde estamos combinando os argumentos Action, Func e Predicate para remover os itens da lista de estoque cujo código atende uma condição:

public void removeItens(int itemID)
{
  _listaInventario.Where(item => item.Id == itemID).ToList().ForEach(item => _listaInventario.Remove(item));
}

Pegue o exemplo do projeto aqui:   GenericList_Argumentos_Delegates.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

Veja os Destaques e novidades do SUPER DVD Visual Basic (sempre atualizado) : clique e confira !

Quer migrar para o VB .NET ?

Quer aprender C# ??

Quer aprender os conceitos da Programação Orientada a objetos ?

Quer aprender o gerar relatórios com o ReportViewer no VS 2013 ?


             Gostou ?   Compartilhe no Facebook   Compartilhe no Twitter
 

Referências:


José Carlos Macoratti