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

C# Tips & Tricks – Enum.HasFlag

Na versão 4.0 do .NET Framework temos como novidade a possibilidade de utilizar o método HasFlag em enumerações que possuem o atributo FlagsAttribute, ou seja, enumerações que representam combinações de bits. Esse método é bastante útil, já que não é mais necessário verificar se um flag existe na enumeração usando o operador & (AND).

Para demonstrar como esse método nos ajuda, vou utilizar e enumeração FileAttributes (System.IO) que fornece atributos de arquivos e pastas.

Podemos verificar na documentação que essa enumeração possui o atributo FlagsAttribute.

[SerializableAttribute]
[FlagsAttribute]
[ComVisibleAttribute(true)]
public enum FileAttributes

Vamos criar uma instância dessa enumeração tentando recuperar os atributos existentes na pasta C:\Windows\Temp. Para isso, podemos utilizar o método estático GetAttributes da classe File.

var fileAttributes = File.GetAttributes(@"C:\Windows\Temp\");

Obs.: Não confunda os atributos do .NET, que são um mecanismo de atribuição de informação adicional em tipos e membros, com os atributos dos arquivos e pastas que são propriedades associadas a estes pelo sistema operacional.

Nas versões anteriores, se quiséssemos verificar se o arquivo (File) possui o flag FileAttributes.Directory por exemplo, o código ficaria assim:

if ((fileAttributes & FileAttributes.Directory) == FileAttributes.Directory)
    Console.WriteLine("É uma pasta...");

Com a adição do método HasFlag, o código anterior pode ser feito tal como abaixo.

if (fileAttributes.HasFlag(FileAttributes.Directory))
    Console.WriteLine("É uma pasta...");

É uma maneira mais simples e acho que também um código um pouco mais elegante.

O único inconveniente é que o método HasFlag possui como parâmetro um tipo System.Enum, não sendo possível pelo Visual Studio “adivinhar” (realizar type inference) e sugerir um flag da enumeração enquanto digitamos o código.

Até a próxima.

Enum.HasFlag
http://bit.ly/ceG0ls

FlagsAttribute Class
http://bit.ly/bfTdyP

FileAttributes Enumeration
http://bit.ly/9xZW79

WPF – Alterando o Background de um DataGridCell sem conhecer o Binding Path

Observação: Esse post sugere que o leitor possua um conhecimento intermediário de técnicas de binding e estilos no WPF. Estarei à disposição para eventuais dúvidas ou sugestão de leitura de links ou materiais sobre o assunto.

Essa semana no fórum de WPF do MSDN o Márcio Fábio Althmann postou uma pergunta interessante em uma thread e resolvi compartilhar aqui a minha experiência tentando resolver o problema. Na verdade eu consegui encontrar uma maneira de resolvê-lo, mas quando fui postar a resposta, vi que o Roberto Sonnino postou uma solução melhor que a minha. Vou demonstrar neste post duas maneiras de resolver o problema.

A dúvida do Márcio era como alterar o background das células (DataGridCell) de um DataGrid com base no valor da célula e sem conhecer o nome da coluna associada à célula, ou seja, desconhecendo o Binding Path. A única especificação seria que quando a célula tivesse o valor “Livre” o fundo deveria ser verde e quando o valor fosse “Reservado” o fundo deveria ser vermelho.

A thread pode ser consultada no link abaixo.

Pintar célula
http://bit.ly/cjvkyv

Vou mostrar primeiro a solução quando conhecemos o nome da propriedade, ou seja, o Binding Path.

O que vamos fazer primeiramente é criar uma classe que implementa a interface IValueConverter e realiza a conversão de uma String em um SolidColorBrush por meio de uma lógica qualquer, no caso a cor será determinada pelos valores “Livre” ou “Reservado”. Um Converter (classe que implementa IValueConverter) é uma classe que realiza uma lógica customizada de binding.

A classe então fica da seguinte maneira:

[ValueConversion(typeof(string), typeof(SolidColorBrush))]
public class DataGridCellBackgroundColorConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        string valor = value.ToString();

        if (valor == "Livre")
            return new SolidColorBrush(Colors.Green);
        else if (valor == "Reservado")
            return new SolidColorBrush(Colors.Red);
        return new SolidColorBrush(Colors.White);
    }

    public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

No arquivo XAML teríamos então que incluir o Converter como um Resource da Window.

<Window.Resources>
    <local:DataGridCellBackgroundColorConverter x:Key="DataGridCellConverter" />
</Window.Resources>

No DataGrid poderíamos então incluir um estilo com um setter para a propriedade Background usando o Binding para uma propriedade chamada NomePropriedade e também convertendo essa propriedade usando o Converter. Um tanto complicado né? Não é não, WPF é assim mesmo e com o tempo a gente acostuma.

<DataGrid AutoGenerateColumns="True"
          Name="seuDataGrid"
          ItemsSource="{Binding}">
    <DataGrid.CellStyle>
        <Style TargetType="{x:Type DataGridCell}">
            <Setter Property="Background"
                    Value="{Binding NomePropriedade, Converter={StaticResource DataGridCellConverter}}" />
        </Style>
    </DataGrid.CellStyle>
</DataGrid>

Tudo ok e tudo funcionando, mas e quando não temos o nome da coluna? Ou seja, no XAML acima imagine que não sabemos qual é o NomePropriedade a ser utilizado. Para isso, uma das soluções é realizar o binding por meio do código, usando o data source para incluir as colunas (DataGridTextColumn no caso).  Essa foi a solução que encontrei para o problema do Márcio.

Assim, o DataGrid agora fica da seguinte maneira no XAML.

<DataGrid AutoGenerateColumns="False"
          Name="seuDataGrid"
          ItemsSource="{Binding}" />

No evento Loaded da Window vamos popular um DataSet que funcionará como o data source e então vamos adicionar colunas do tipo DataGridTextColumn com os respectivos estilos e bindings. Os comentários do código servem como explicação do que está sendo realizado.

void SuaWindow_Loaded(object sender, RoutedEventArgs e)
{
    // criação do DataSet
    var dataSet = new DataSet();
    dataSet.Tables.Add("Tabela1");
    dataSet.Tables[0].Columns.Add("Coluna1");
    dataSet.Tables[0].Columns.Add("Coluna2");
    dataSet.Tables[0].Rows.Add(new object[] { "Reservado", "Livre" });
    dataSet.Tables[0].Rows.Add(new object[] { "Teste", "Livre" });
    dataSet.Tables[0].Rows.Add(new object[] { "Livre", "Livre" });
    dataSet.Tables[0].Rows.Add(new object[] { "Livre", "Reservado" });
    dataSet.Tables[0].Rows.Add(new object[] { "Reservado", "Reservado" });

    // conversor para as células
    var backColorConverter = new DataGridCellBackgroundColorConverter();

    foreach (DataColumn dc in dataSet.Tables[0].Columns)
    {
        // cria coluna
        var dataGridTextColumn = new DataGridTextColumn();

        // header e data binding
        dataGridTextColumn.Header = dc.ColumnName;
        dataGridTextColumn.Binding = new Binding(dc.ColumnName);

        // estilo da coluna
        var columnStyle = new Style();
        columnStyle.TargetType = typeof(DataGridCell);

        // setter para a propriedade Background
        var setter = new Setter();
        setter.Property = DataGridCell.BackgroundProperty;

        // binding do setter com o nome da coluna e converter
        var setterBinding = new Binding(dc.ColumnName);
        setterBinding.Converter = backColorConverter;
        setter.Value = setterBinding;
        
        // adiciona setter na coleção de setters do estilo
        columnStyle.Setters.Add(setter);

        // define o estilo da coluna
        dataGridTextColumn.CellStyle = columnStyle;

        // adiciona coluna no DataGrid
        this.seuDataGrid.Columns.Add(dataGridTextColumn);
    }

    this.seuDataGrid.DataContext = dataSet.Tables[0];
}

Acho uma solução simples, mas que ainda deve ser revista, visto que o tipo da coluna do DataGrid depende do tipo da coluna do data source.

Vamos agora então à solução do Roberto que sugere a criação de um Converter para pegar o valor da célula usando Reflection.

public class CelulaParaValorConverter: IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        // célula
        var cell = value as DataGridCell;

        if (cell != null)
        {
            // linha e coluna
            var row = DataGridRow.GetRowContainingElement(cell);
            var column = cell.Column as DataGridBoundColumn;

            if (column != null)
            {
                // binding associado à coluna
                var binding = column.Binding as Binding;

                if (binding != null)
                {
                    // nome da propriedade (Binding Path)
                    string boundPropertyName = binding.Path.Path;

                    // data item que a row representa
                    object data = row.Item;

                    // propriedades do item
                    var properties = TypeDescriptor.GetProperties(data);

                    // verifica total de propriedades
                    if (properties.Count > 0)
                    {
                        // propriedade associada ao binding
                        var property = properties[boundPropertyName];

                        // valor da propriedade associado ao item
                        return property.GetValue(data);
                    }
                }
            }
        }

        return null;
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

Adicionamos o Converter aos Resources da Window.

<Window.Resources>
    <local:CelulaParaValorConverter x:Key="CelulaParaValor" />
</Window.Resources>

Agora usamos um DataTrigger no estilo do DataGridCell para definir um Setter e também a propriedade RelativeSource para realizar binding com o próprio elemento (RelativeSource Self). O XAML fica da seguinte maneira:

<DataGrid AutoGenerateColumns="False"
          Name="seuDataGrid"
          ItemsSource="{Binding}">
    <DataGrid.CellStyle>
        <Style TargetType="{x:Type DataGridCell}">
            <Style.Triggers>
                <DataTrigger Value="Livre"
                             Binding="{Binding RelativeSource, RelativeSource={RelativeSource Self}, Converter={StaticResource CelulaParaValor}}">
                    <Setter Property="Background"
                            Value="Green" />
                </DataTrigger>
                <DataTrigger Value="Reservado"
                             Binding="{Binding RelativeSource, RelativeSource={RelativeSource Self}, Converter={StaticResource CelulaParaValor}}">
                    <Setter Property="Background"
                            Value="Red" />
                </DataTrigger>                        
            </Style.Triggers>
        </Style>
    </DataGrid.CellStyle>
</DataGrid>

Agora é só realizar o binding do DataGrid com o data source.

this.seuDataGrid.DataContext = MetodoQueRetornaUmDataSource();

A solução do Roberto é realmente mais simples e elegante.

Até a próxima !

Binding Class
http://bit.ly/dr7GrM

DataTrigger Class
http://bit.ly/bL6rKS