Sorted…!!! :)
Uma vez numa entrevista de trabalho perguntaram-me:
“Se tiveres um problema em mãos e não souberes como o resolver, o que fazes?!”
Comecei por achar a pergunta um tanto ou quanto estranha e vaga mas era nitidamente uma daquelas que poderia fazer a diferença entre ser contratado ou não. A resposta que o entrevistador estava à procura era:
“Quando tudo o resto falhar e todos os que conhecer não souberem… vou ao Google! Se não estiver no Google, então é porque não existe…”
Há uns tempos atrás andei à procura de uma forma de ordenar listas/colecções/arrays por múltiplas colunas. Nesta altura Linq não era opção. Como qualquer bom programador, fui ao Google procurar por quem já tivesse partido pedra e encontrei um artigo muito interessante:
Sorting with Objects on multiple fields
http://www.codeproject.com/KB/recipes/Sorting_with_Objects.aspx
“Nada como o belo de um copy/paste para dentro do nosso projecto para resolver o problema!” – pensei.
O código estava interessante, eu faria algumas coisas de forma diferente e rapidamente deu para perceber que não funcionava assim tão bem como haviam vendido. A ordenação só funcionava correctamente para o primeiro campo. Como o meu amigo Antoine diria:
“Não são bugs… são novas e entusiasmantes oportunidades de refactoring!”
Dito, feito… e resolvi criar a minha própria classe de ordenação utilizando reflection e alguma recursividade.
Geek stuff!
Aquilo que eu queria era no fundo ordenar um array de objectos (que a título de exemplo são os developers da fullsix)
// Initialize array var fullsixEmployees = new[] { new FullsixEmployee { Id = 1, Name = "César Silva", Birthdate = new DateTime(1977, 7, 16), IsPortuguese = true }, new FullsixEmployee { ... ...
A ideia era conseguir dizer de alguma forma que queria ordenar este array por um número ilimitado de propriedades e determinar a direcção dessa ordenação.
// Sort the fullsix employee's array Array.Sort(fullsixEmployees, new MultiColumnComparer<FullsixEmployee>("IsPortuguese DESC, Birthdate"));
A “magia” reside basicamente nos 2 métodos que determinam a comparação entre as propriedades de cada objecto. Ao invocar o método Sort da classe Array, passamos como argumentos o array a ordenar, seguido de um IComparer<T> que neste caso é a nossa classe MultiColumnComparer.
public int Compare(T x, T y) { // Initialize the source object's type var columnsOrder = GetColumnsOrder(this.OrderExpression); var comparissonResult = 0; if (0 < columnsOrder.Count) comparissonResult = ColumnCompare(x, y, columnsOrder, 0); return comparissonResult; }
Após esta primeira invocação há que chamar o segundo método de nome ColumnCompare de forma a permitir a recursividade entre as várias propriedades (caso a comparação de determinada propriedade resulte em igualdade).
public int ColumnCompare(T x, T y, IList<Column> columnsOrder, int columnIndex) { // Get the object's type var sourceType = x.GetType(); var columnProperty = sourceType.GetProperty(columnsOrder[columnIndex].Name); // Initialize the comparisson result object var comparissonResult = 0; comparissonResult = Comparer.DefaultInvariant.Compare(columnProperty.GetValue(x, null), columnProperty.GetValue(y, null)); // Invert the order if descending if (columnsOrder[columnIndex].Direction.Equals(ColumnDirection.Descending)) comparissonResult = -comparissonResult; // If the properties are equal then go to the next property (if available) if (comparissonResult.Equals(0) && columnIndex < columnsOrder.Count - 1) return ColumnCompare(x, y, columnsOrder, columnIndex + 1); return comparissonResult; }
Através de reflection conseguimos aceder às propriedades dos objectos a serem comparados e através de um ciclo recursivo aceder aos valores de cada uma delas para estabelecer as comparações. O ciclo mantém-se enquanto existir igualdade entre as propriedades a serem comparadas e a direcção da comparação é determinada pela negação do resultado, caso seja descendente.
Acabou por ser um exercício engraçado para brincar mais uma vez com a ordenação de listas/colecções/arrays.
Para quem quiser os ficheiros com a implementação deste exercício, pode retirá-los em Source code.
autor: cesar.silva