C# Tips & Tricks – Atualizar objetos de uma lista usando LINQ To Objects

Essa semana nos fóruns de LINQ do MSDN um desenvolvedor descreveu o seguinte problema: dado uma lista genérica de objetos, seria possível por meio do LINQ to Objects atualizar propriedades desses objetos com base em uma condição?

Por exemplo, dada a classe abaixo.

public class Customer
{
    public string Name { get; set; }
    public DateTime ExpirationDate { get; set; }
    public bool IsActive { get; set; }
}

E também uma lista genérica de objetos dessa classe.

var lista = new List<Customer>
{
    new Customer { Name = "Ari Raimundo", ExpirationDate = new DateTime(2010, 4, 13), IsActive = true},
    new Customer { Name = "Michael Jordan", ExpirationDate = new DateTime(1998, 12, 11), IsActive = true},
    new Customer { Name = "Anfernee Hardaway", ExpirationDate = new DateTime(1995, 5, 5), IsActive = true},
    new Customer { Name = "LeBron James", ExpirationDate = new DateTime(2009, 9, 7), IsActive = true}
};

É possível usando o LINQ To Objects desativar, ou seja, alterar a propriedade IsActive para False, todos os clientes cuja data de expiração (ExpirationDate) for anterior à 2010?

Uma solução seria fazer uma consulta com a condição da data de expiração e então realizar um loop no resultado da consulta alterando a propriedade IsActive.

var r = lista.Where(c => c.ExpirationDate.Year < 2010);

foreach (var customer in r)
    customer.IsActive = false;

Mesmo assim, uma parte do código não utiliza LINQ To Objects.

Podemos melhorar essa solução e ainda reutilizar nosso código em outras aplicações criando um método de extensão para a interface IEnumerable<T>. Esse método poderia se chamar UpdateAll e possuir um parâmetro Action<T>, ou seja, um delegate que encapsula uma ação qualquer.

Segue abaixo o código para o método.

public static class LINQToObjectExtensions
{
    public static void UpdateAll<T>(this IEnumerable<T> source, Action<T> action)
    {
        foreach (var item in source)
            action(item);
    }
} 

Agora fica mais simples realizar a atualização baseada na condição.

lista.Where(c => c.ExpirationDate.Year < 2010)
     .UpdateAll(c => c.IsActive = false);

Podemos também sobrecarregar o método de extensão e adicionar a condição como parâmetro.

public static void UpdateAll<T>(this IEnumerable<T> source, Func<T, bool> condition, Action<T> action)
{
    var r = source.Where(condition);
    foreach (var item in r)
        action(item);
}

Com isso, a atualização ficaria da seguinte maneira.

lista.UpdateAll(c => c.ExpirationDate.Year < 2010, c => c.IsActive = false);

Conforme comentei anteriormente, a vantagem dessa abordagem é que os métodos de extensão podem ser reutilizados em outras aplicações.

Os métodos foram baseados na thread abaixo do site Stack Overflow.

Update all objects in a collection using Linq
http://bit.ly/a5zUCe

Espero que tenham gostado.

Extension Methods (C# Programming Guide)
http://bit.ly/bjhbHe

LINQ To Objects
http://bit.ly/8OHCr3

3 comentários:

  1. Pessoal,

    Acho que não fui muito feliz em colocar o nome do método como UpdateAll. Talvez ForEach seria melhor, já que o método poderia ser utilizado para outras razões.

    Por exemplo, poderíamos utilizar o método para uma lógica de negócio qualquer, não necessariamente que atualizasse os objetos.

    Até mais.

    ResponderExcluir
  2. Olá Ari,

    E porque não utilizar o próprio método ForEach que já existe na classe List<>?

    lista.ForEach(c => c.IsActive = false);

    Abraços,

    Caio Proiete
    http://caioproiete.com

    ResponderExcluir
  3. Oi Caio,

    Na verdade não fui muito feliz com o título do post. Realmente a classe List<> possui o método ForEach, mas a idéia seria alterar os objetos com base em uma condição.

    Quando utilizo o Where no List<> o retorno é um IEnumerable<>, que não possui o método ForEach. Uma opção seria fazer assim:

    lista.Where(c => c.ExpirationDate.Year < 2010).ToList().ForEach(c => c.IsActive = false);

    Mas eu quis brincar um pouco com os métodos de extensão.

    Obrigado pelo feedback.

    Um abraço.

    ResponderExcluir