Ordenação dinâmica com LINQ

Pessoal, aqui vai uma dica para trabalhar com ordenação e LINQ. Hoje me perguntaram como fazia “order by dinâmico por string” no LINQ. Neste exemplo, vou utilizar Métodos de Extensão, que chegou junto com o C# 3.0. Como funciona o método de extensão OrderBy e OrderByDescending atualmente?

Para o exemplo, usarei uma classe simples:

public class Pessoa
{
    public string Nome { get; set; }
    public string Sobrenome { get; set; }
    public int Idade { get; set; }
}

E a seguinte lista para o exemplo:

pessoas.Add(new Pessoa { Nome = "Bruno", Sobrenome = "Kenj", Idade = 25 });
pessoas.Add(new Pessoa { Nome = "Rodrigo", Sobrenome = "Kono", Idade = 26 });
pessoas.Add(new Pessoa { Nome = "Evilázaro", Sobrenome = "Alves", Idade = 27 });
pessoas.Add(new Pessoa { Nome = "Taciana", Sobrenome = "Dória", Idade = 25 });

A ordenação padrão, utilizando lambda, poderia ser feita desta forma:

List<Pessoa> listaOrdenada = pessoas.OrderBy(p => p.Idade).ToList();

Bacana, funciona bem. Mas, se eu precisar passar o campo de ordenação de forma dinâmica? Usar um switch não seria muito legal, principalmente se a classe conter várias propriedades que poderão ser ordenadas.

switch (columnName)
{
    case "Nome":
        return pessoas.OrderBy(p => p.Nome);
    case "Idade":
        return pessoas.OrderBy(p => p.Idade);
    case ...
}

Nem pensar, né? Para facilitar, vamos criar dois métodos de extensão para a interface IEnumerable e um método privado.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;

namespace ConsoleApplicationTest
{
    public static class EnumerableExtensions
    {
        public static IEnumerable<T> OrderBy<T>(this IEnumerable<T> items, string propertyName)
        {
            return OrderByPropertyName<T>(items, propertyName, true);
        }

        public static IEnumerable<T> OrderByDescending<T>(this IEnumerable<T> items, string propertyName)
        {
            return OrderByPropertyName<T>(items, propertyName, false);
        }

        private static IEnumerable<T> OrderByPropertyName<T>(this IEnumerable<T> source, string propertyName, bool ascending)
        {
            if (source == null)
            {
                throw new ArgumentNullException("source");
            }

            if (String.IsNullOrEmpty(propertyName))
            {
                return source;
            }

            var Object = Expression.Parameter(typeof(T), "Object");
            var EnumeratedObject = Expression.Parameter(typeof(IEnumerable<T>), "EnumeratedObject");
            var Property = Expression.Property(Object, propertyName);
            var Lamda = Expression.Lambda(Property, Object);
            var Method = Expression.Call(typeof(Enumerable), ascending ? "OrderBy" : "OrderByDescending", new[] { typeof(T), Lamda.Body.Type }, EnumeratedObject, Lamda);
            var SortedLamda = Expression.Lambda<Func<IEnumerable<T>, IOrderedEnumerable<T>>>(Method, EnumeratedObject).Compile();
            return SortedLamda(source);
        }
    }
}

Importante: Para a sua classe conseguir enxergar esses dois métodos de extensão, é preciso fazer referência ao namespace criado. Nesse exemplo eu utilizei “ConsoleApplicationTest“. Você pode mudar conforme for conveniente.

Criamos dois métodos de extensão com os mesmos nomes OrderBy e OrderByDescending, porém, agora eles esperam uma assinatura com o nome em string da coluna a ser ordenada.

List<Pessoa> porIdadeAsc = pessoas.OrderBy("Idade").ToList();
List<Pessoa> porIdadeDesc = pessoas.OrderByDescending("Idade").ToList();

E pronto! Eu não entrei em detalhes de como funcionam os métodos de extensão (que salvam a vida) e nem como funciona a utilização de lambda expression mas caso alguém tenha dúvidas ou sugestões, basta entrar em contato comigo. Até lembrei agora do primeiro método de extensão que criei no trabalho, que converte um tipo anônimo em DataTable, para poder retornar o objeto entre camadas da forma como queríamos.

Para quem precisar, está disponível o download do arquivo EnumerableExtensions.cs

Até a próxima.

Abraços,

Comentário fechado.