el Guille, la Web del Visual Basic, C#, .NET y más...

Clasificar en .NET Framework (con IComparable o IComparer)

 
Publicado el 17/Jul/2008
Actualizado el 17/Jul/2008
Autor: Guillermo 'guille' Som

En este artículo te explico cómo puedes definir una clase (o tipo) para que se pueda clasificar (implementando la interfaz IComparable) y cómo poder clasificar elementos de tipos que no implementan esa interfaz.



 

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
 


Código de ejemplo (comprimido):

 No hay código para descargar, pero te recomiendo que leas el artículo que publiqué en dotNetManía en el número 7 de Septiembre de 2004 sobre este tema y que puedes descargar en formato PDF.


 


La fecha/hora en el servidor es: 26/11/2024 6:07:09

La fecha actual GMT (UTC) es: 

©Guillermo 'guille' Som, 1996-2024