La batallita del agüelo:
Tanto los arrays como algunos tipos de colecciones definen el método
Sort con el cual podemos clasificar el contenido de esa
colección o array. El problema con el que nos podemos encontrar es que los
elementos de dicha colección no estén preparados para que se puedan
clasificar. La mayoría de las clases de .NET están "preparadas" para que se
puedan clasificar, pero es posible que si los elementos que tiene esa
colección no son "clasificables" que todo el tema este de poder clasificar
esos elementos se vaya al garete.
Y tu te preguntarás ¿por qué no se van a poder clasificar esos elementos?
Y yo te contesto, por una razón muy sencilla, porque .NET comprueba si un
tipo de datos se puede clasificar o no.
Y nuevamente te preguntarás... ¿cómo sabe el .NET si se puede clasificar
o no?
Y yo te digo, porque .NET exige que todos los elementos que sean aptos para
poder clasificarlos tengan implementada la interfaz IComparable.
Y a raíz de esto último tu preguntas: y si un tipo no implementa
IComparable ¿se puede clasificar?
Y aquí estoy yo para decirte, sí, si se pueden clasificar los elementos que
no implementen IComparable.
Y ya con un poco de mosqueo por tu parte, puede que preguntes... a ver...
si antes dijiste que para clasificar los elementos, el .NET exige que esos
tipos implementen IComparable, cómo es que me dices que
incluso si no lo implementa también se puede clasificar...
Y yo que, ante tu "presunto" mosqueo, he contado hasta 10 (en
realidad no he necesitado contar, ya que las preguntas que supuestamente tú
estás haciendo, en realidad las hago yo), te digo: es que si no
implementa IComparable hay que usar una de las sobrecargas
de ese método Sort para que se pueda clasificar, en esas
sobrecargas se espera un objeto que implemente IComparer
que define un método Compare que será el que se encargue de
comparar dos objetos.
Bueno, ya está bien de tantas vueltas... ;-)))
Veamos cómo tienes que hacerlo para que en los dos casos expuestos puedas
clasificar los elementos de una colección o un array.
Caso 1: Hacer que nuestros tipos de datos se puedan clasificar
Si defines un tipo de datos y quieres que ese tipo de datos
se pueda clasificar usando el método Sort que
algunas colecciones y los arrays implementan, debes implementar
en tu tipo de datos la interfaz IComparable.
Esa interfaz define un método: CompareTo el
cual debes definir para que devuelva un valor entero que indica
en qué orden debe quedar cada elemento.
Ese método recibe como parámetro el elemento que quieres
comparar con la instancia actual y debe devolver un valor
dependiendo de que la instancia actual sea igual, menor o mayor
que el objeto indicado (el recibido como parámetro).
Aunque el valor devuelto por ese método es un valor entero (Int32,
Integer, int) se tendrá en cuenta estos valores para
decidir en qué orden deben estar esos dos elementos. Por tanto,
ese método debe devolver uno de estos valores:
- -1 (o menor de cero) si la instancia
actual es menor que el objeto recibido como parámetro.
- 0 (cero) si la instancia actual y el
otro objeto son iguales.
- 1 (o mayor de cero) si la instancia
actual es mayor que el objeto recibido como parámetro.
Nota:
La instancia actual es el objeto en el que se está ejecutando
ese método, y que debe compararse con otro objeto (ya sea del
mismo tipo o de otro tipo diferente).
Supongamos que tenemos un tipo de datos llamado Colega que
tiene (entre otras) una propiedad llamada Nombre y queremos que
a la hora de clasificar se tenga en cuenta esa propiedad.
La forma de definir ese método sería tal como te muestro a
continuación
(para simplificar el código, en lugar de una propiedad, Nombre
es un campo público):
Código para Visual Basic:
Class Colega
Implements IComparable
Public Nombre As String
Public Function CompareTo(ByVal obj As Object) As Integer _
Implements IComparable.CompareTo
' Comprobamos si el tipo a comparar es de este tipo
Dim co As Colega = TryCast(obj, Colega)
' Si no es de ese tipo o es nulo, indicar que son iguales
If co Is Nothing Then
Return 0
End If
Return Nombre.CompareTo(co.Nombre)
End Function
Código para C#:
class Colega : IComparable
{
public string Nombre;
public int CompareTo(object obj)
{
// Comprobamos si el tipo a comparar es de este tipo
Colega co = obj as Colega;
// Si no es de ese tipo o es nulo, indicar que son iguales
if(co == null)
{
return 0;
}
return Nombre.CompareTo(co.Nombre);
}
}
En el método CompareTo hacemos varias cosas que te
pueden resultar interesantes, la primera es usar TryCast en
Visual Basic y as en C#, para hacer el "casting" (o
conversión) del objeto recibido por parámetro, de esa forma, si ese objeto
no es del tipo Colega, la variable co
contendrá un valor nulo, y en caso de que sea nulo, simplemente devolvemos
un cero, es decir, un valor para indicarle a .NET que no haga nada especial
al clasificar esos elementos.
La comprobación de que si es nulo devuelva cero nos vale cuando el objeto
recibido no es del tipo Colega o cuando es del tipo
Colega, pero contiene un valor nulo.
Si no es un valor nulo, debe ser a la fuerza un objeto Colega
válido, por tanto, aprovechamos el método CompareTo de la
clase String (desde el Nombre) para
devolver el valor adecuado en cuanto a cómo se deben clasificar se refiere.
Nota:
Yo siempre suelo hacer esas comprobaciones a la hora de implementar el
método CompareTo, pero en las clases de .NET lo que se hace
si el tipo a comparar (el pasado por parámetro) no es del mismo tipo, es
lanzar una excepción.
Sabiendo esto, debes tener en cuenta que si los elementos que quieres
clasificar no son todos del mismo tipo, puede que no te los clasifique, por
ejemplo, si tienes una colección con elementos del tipo Colega
y elementos del tipo String o cualquier otro definido por
el propio .NET.
Si este es el caso, lo mejor es que uses el código indicado en el "caso 2"
para evitarte problemas o bien que el array o la colección solo permita
objetos de un mismo tipo, por ejemplo, usando colecciones de tipo
generic.
Caso 2: Cómo clasificar elementos que no implementan IComparable
Si quieres clasificar los elementos de una colección (o array) y resulta
que los elementos que contiene no son todos del mismo tipo o esos tipos no
implementan la interfaz IComparable, puedes crear un método
que se encargue de comparar dos elementos y devolver el valor adecuado
(recuerda: 0 si son iguales, menor de cero si el primero es menor que el
segundo o mayor de cero si el primer elemento es mayor que el segundo).
En realidad ese método que se encarga de comparar los elementos debe
estar definido en una clase que implemente la interfaz IComparer, ya que las
sobrecargas del método Sort lo que esperan es que usemos un objeto que
implemente esa interfaz y se use el método Compare, el cual recibe dos
elementos que son los que hay que comparar.
En el siguiente código vamos a definir una clase que
implementa IComparer y nos servirá para clasificar objetos del
tipo Colega o cualquier otro tipo. En el caso de que no sean de
tipo Colega, ser clasificarán según lo que devuelva el método
ToString, que en el caso de los números o de las cadenas,
devolverá el valor que contienen, y en otros casos... pues... lo
que devuelva ese método.
Código para Visual Basic:
Class Comparar
Implements IComparer
Public Function Compare(ByVal x As Object, _
ByVal y As Object) As Integer _
Implements IComparer.Compare
' Para aceptar otros tipos además de Colega,
' debemos hacer comprobaciones extras
' Primero si son nulos
If x Is Nothing OrElse y Is Nothing Then
Return 0
End If
' Si llega aquí es que no son nulos
' Comprobar si los dos objetos son del tipo Colega
Dim c1 As Colega = TryCast(x, Colega)
Dim c2 As Colega = TryCast(y, Colega)
' Si alguno es nulo es que no son de tipo colega
If c1 Is Nothing OrElse c2 Is Nothing Then
' comparar usando el método ToString
Return x.ToString().CompareTo(y.ToString())
End If
' Los dos son de tipo Colega y no son nulos
Return c1.Nombre.CompareTo(c2.Nombre)
End Function
End Class
Código para C#:
class Comparar : IComparer
{
public int Compare(object x, object y)
{
// Para aceptar otros tipos además de Colega,
// debemos hacer comprobaciones extras
// Primero si son nulos
if(x == null || y == null)
{
return 0;
}
// Si llega aquí es que no son nulos
// Comprobar si los dos objetos son del tipo Colega
Colega c1 = x as Colega;
Colega c2 = y as Colega;
// Si alguno es nulo es que no son de tipo colega
if(c1 == null || c2 == null)
{
// comparar usando el método ToString
return x.ToString().CompareTo(y.ToString());
}
// Los dos son de tipo Colega y no son nulos
return c1.Nombre.CompareTo(c2.Nombre);
}
}
Para usar esta forma de clasificar, debemos crear una
instancia de la clase Comparar y pasarla como
parámetro al método Sort, en ese caso, se usará
ese método Compare para clasificar los
elementos en lugar de llamar a la implementación del método
CompareTo que cada tipo defina porque
implemente la interfaz IComparable.
Por tanto, esto es útil para clasificar elementos de tipos
diferentes a nuestro tipo Colega (o cuando una
colección tenga tipos diferentes de datos).
El siguiente código demuestra que esto funciona
correctamente.
Código para Visual Basic:
Dim col As New ArrayList
Dim rnd As New Random
For i As Integer = 1 To 10
Dim n As Integer = rnd.Next(1, 50)
If n > 25 Then
col.Add("Cadena " & n.ToString())
Else
Dim c As New Colega
c.Nombre = "Nombre " & n.ToString("00")
col.Add(c)
End If
Next
Console.WriteLine("Antes de clasificar")
For Each o As Object In col
Console.WriteLine(o)
Next
Dim comp As New Comparar
col.Sort(comp)
Console.WriteLine()
Console.WriteLine("Después de clasificar")
For Each o As Object In col
Console.WriteLine(o)
Next
Código para C#:
ArrayList col = new ArrayList();
Random rnd = new Random();
for(int i = 1; i <= 10; i++)
{
int n = rnd.Next(1, 50);
if(n > 25)
{
col.Add("Cadena " + n.ToString());
}
else
{
Colega c = new Colega();
c.Nombre = "Nombre " + n.ToString("00");
col.Add(c);
}
}
Console.WriteLine("Antes de clasificar");
foreach(object o in col)
{
Console.WriteLine(o);
}
Comparar comp = new Comparar();
col.Sort(comp);
Console.WriteLine();
Console.WriteLine("Después de clasificar");
foreach(object o in col)
{
Console.WriteLine(o);
}
Si quieres probar qué pasa si no tuviéramos la clase
Comparar, sustituye las dos líneas que están entre los
dos bucles (las líneas que instancia un objeto del tipo
Comparar y la que llama al método Sort sobrecargado) por
un simple col.Sort (sin parámetros) y verás que
casi con toda seguridad se producirá un error, particularmente
cuando se vaya a comparar un objeto String con uno del tipo
Colega.
Espero que todo esto te sea de utilidad y te ayude a saber cómo
clasificar los elementos de un array o colección.
Nos vemos.
Guillermo
Espacios de nombres usados en el código de este artículo:
System
System.Collections