Hoy vamos a ver cuales son, en mi opinión, algunas de las interfaces de .NET que todo programador debería conocer.
Las interfaces que vamos a ver en este post son:
- IComparable
- IComparer
- ICloneable
- IDisposable
- IEnumerable
- IEnumerator.
Está claro que hay muchas más que serán relevantes según las necesidades de nuestro código, pero lo que está claro es que si no conocemos estas tenemos un problema en el contexto de una conversación con nuestro colegas programadores ;-)
Comenzaremos por IComparable.
Para este y siguientes ejemplos, trabajaremos con una clase Persona como la siguiente.
Public Class Persona
' Propiedades autoimplementadas
Public Property Nombre As String
Public Property Edad As Integer
' Constructor con parámetros
Public Sub New(ByVal nombre As String, ByVal edad As Integer)
Me.Nombre = nombre
Me.Edad = edad
End Sub
' Sobreescribimos el método ToString
Public Overrides Function ToString() As String
Return String.Format("{0}, {1}", Nombre, Edad)
End Function
End Class
Con esta clase, si creamos una lista de personas e intentamos ordenarla, recibiremos el siguiente error:
Como vemos, no es posible ordenar la lista porque no se pueden comparar los objetos de tipo Persona. Para resolver esto tenemos que implementar la interfaz IComparable, y a partir de ese momento, el framework sabrá como comparar 2 instancias de tipo Persona.
La interfaz IComparable sólo declara un método, CompareTo, que recibe el objeto con el que se quiere comparar el objeto actual. El método tiene que devolver 1 (si el objeto actual es mayor que el suministrado), 0 (sin son iguales) o -1 (si el objeto actual es menor que el suministrado).
Public Class Persona
Implements IComparable
Public Function CompareTo(obj As Object) As Integer Implements System.IComparable.CompareTo
' obj es el objeto con el que se quiere comparar el objeto actual
' Si el método devuelve -1, el objeto actual es MENOR que el objeto recibido
' Si el método devuelve 0, ambos objetos son iguales
' Si el método devuelve 1, el objeto actual es MAYOR que el objeto recibido
Dim compare As Persona = TryCast(obj, Persona)
If compare Is Nothing Then
Throw New Exception("obj no es del tipo Persona.")
End If
' Se compara la Edad
If Edad > compare.Edad Then
Return 1
ElseIf Edad < compare.Edad Then
Return -1
Else
Return 0
End If
End Function
End Class
¡Ahora la salida ordena correctamente por Edad!
Además de implementar IComparable para que métodos como Sort comparen nuestros objetos automáticamente, también podemos nosotros mismos utilizar el método CompareTo en cualquier momento:
Dim comparacion As Integer
comparacion = sergio.CompareTo(antonio)
If comparacion = 1 Then
Console.WriteLine("Sergio es mayor que Antonio")
ElseIf comparacion = -1 Then
Console.WriteLine("Sergio es menor que Antonio")
Else
Console.WriteLine("Sergio y Antonio son iguales")
End If
Si nos fijamos, no es lo mismo Ordenar (que implícitamente Compara) que simplemente Comparar. Sé que un poco lioso pero imagina que lo queremos ahora es encontrar el índice de una persona concreta en nuestra lista. Escribiríamos el siguiente código:
Dim gente As New List(Of Persona)
Dim antonio As New Persona("Antonio", 35)
gente.Add(antonio)
Dim antonio2 As New Persona("Antonio", 35)
If gente.IndexOf(antonio2) = -1 Then
' Sólo queremos agregar a este otro Antonio si no existe ya un mismo Antonio en la lista
gente.Add(antonio2)
End If
Console.WriteLine(gente.Count)
Que devolvería 2 elementos, es decir, se comparan Antonio y Antonio2 y el resultado es que son distintos, aunque en realidad son iguales, ambos se llaman Antonio y tienen 35 años.
Sin embargo, si sobreescribimos el método Equals, ahora nuestra lista sólo tendrá 1 elemento, esto es 1 Antonio. Equals determina si 2 instancias son iguales. El comportamiento predeterminado (en caso de no sobreescribir) es que son iguales sin ambas variables apuntan a la misma instancia.
Public Overrides Function Equals(obj As Object) As Boolean
If obj Is Nothing OrElse Not Me.GetType() Is obj.GetType() Then
' obj es nulo o no es del tipo Persona
Return False
End If
With CType(obj, Persona)
If .Nombre <> Nombre Then
' obj y el objeto actual son distintos
Return False
End If
If .Edad <> Edad Then
' obj y el objeto actual son distintos
Return False
End If
End With
' obj y el objeto actual son iguales
Return True
End Function
Ahora podemos escribir esto y funcionará como esperamos:
If Not antonio2.Equals(antonio) Then
' Sólo queremos agregar a este otro Antonio si no existe ya un mismo Antonio en la lista
gente.Add(antonio2)
End If
La segunda interfaz que veremos será
No hay que confundir IComparable con IComparer.
IComparer permite comparar objetos que no implementan IComparable. Además, con IComparer también podemos dotar a nuestras clases de múltiples métodos de ordenación. Aunque no es el propósito de este post, con IComparer también podemos comparar objetos de distinto tipo.
Imagina que la ordenación por defecto para instancias de tipo Persona es por Edad pero también queremos implementar una nueva ordenación que ordena por Edad y Nombre. Puesto que sólo podemos implementar una vez IComparable, tendremos que realizar esta operación a través de IComparer.
Para implementar IComparer, hay que crear una nueva clase (cuyo nombre ya puede estar lleno de significado, por ejemplo, ComparadorPersonasPorEdadYNombre) e implementar en la misma la interfaz IComparer. Después, llamaremos al método Sort pasándole una instancia de esta nueva clase, con lo que sabremos exactamente por qué estamos ordenando. Además, IComparer no utiliza IComparable por lo que el tipo Persona podría no implementar IComparable y seguiría siendo perfectamente comparable a través de IComparer.
En este ejemplo, tenemos que implementar System.Collections.Generic.IComparer(Of Persona) porque lo que queremos comparar son Personas. A partir de ahí, funciona igual que IComparable, es decir, devolvemos -1, 0 o 1.
Public Class ComparadorPersonasPorEdadYNombre
Implements System.Collections.Generic.IComparer(Of Persona)
Public Function Compare(x As Persona, y As Persona) As Integer Implements System.Collections.Generic.IComparer(Of Persona).Compare
' Primero se compara la Edad y después el Nombre
If x.Edad > y.Edad Then
Return 1
ElseIf x.Edad < y.Edad Then
Return -1
Else
If x.Nombre > y.Nombre Then
Return 1
ElseIf x.Nombre < y.Nombre Then
Return -1
Else
Return 0
End If
End If
End Function
End Class
Dim comparador As New ComparadorPersonasPorEdadYNombre
gente.Sort(comparador)
¡Y finalmente estamos ordenando por Edad y Nombre! Imagina ahora que no sólo comparamos por Edad y Nombre, sino por muchos más criterios y además pudiendo seleccionar en cada caso por cuál de ellos queremos ordenar.
Después de haber visto IComparable e IComparer, cambiemos de tercio para ver una interfaz más sencilla, hablamos de ICloneable.
ICloneable crea una nueva instancia que es copia de la instancia clonada.
En este caso, un ejemplo vale más que mil palabras:
Public Function Clone() As Object Implements System.ICloneable.Clone
Dim nuevoObjeto As New Persona(Me.Nombre, Me.Edad)
Return nuevoObjeto
End Function
Otra interfaz que deberíamos conocer es IDisposable.
IDisposable permite liberar recursos no administrados (esto es recursos no gestionados por el framework de .NET).
Imagina que tenemos una clase que abre una conexión a una base de datos y la mantiene abierta mientras la instancia está viva. Una conexión a una base de datos es un recurso no administrado, es decir, no es manejado automáticamente por el recolector de basura – garbage collector – de .NET cuando la variable salga de ámbito, así que nadie cerrará por nosotros esa conexión. De este modo, tenemos que asegurarnos que nuestra clase suministra los mecanismos oportunos para liberar ese recurso administrado cuando ya no sea necesario. Pues bien, esto se consigue a través de la interfaz IDisposable.
IDisposable garantiza lo siguiente:
- Si un programador ve que la clase que utiliza implementa IDisposable, se cuidará de llamar al método Dispose e incluso utilizará nuestra clase con una instrucción Using.
- Si por algún motivo, el programador no es todo lo cuidadoso que procede, cuando el recolector de basura recolecte nuestro objeto, también llamará automáticamente al método Dispose (esta una buena solución pero como bien sabes la destrucción de objetos es .NET es indeterminista – no sabes cuándo va a suceder exactamente - y esa recolección podría ser inmediata pero también podría ser mucho después de lo que nos hubiera gustado que fuera).
Visual Studio nos ayuda enormemente con esta interfaz y nos suministra todo un patrón de implementación para que no tengamos que pensar en exceso:
#Region "IDisposable Support"
Private disposedValue As Boolean ' Para detectar llamadas redundantes
' IDisposable
Protected Overridable Sub Dispose(disposing As Boolean)
If Not Me.disposedValue Then
If disposing Then
' TODO: eliminar estado administrado (objetos administrados).
End If
' TODO: liberar recursos no administrados (objetos no administrados) e invalidar Finalize() below.
' TODO: Establecer campos grandes como Null.
End If
Me.disposedValue = True
End Sub
' TODO: invalidar Finalize() sólo si la instrucción Dispose(ByVal disposing As Boolean) anterior tiene código para liberar recursos no administrados.
Protected Overrides Sub Finalize()
' No cambie este código. Ponga el código de limpieza en la instrucción Dispose(ByVal disposing As Boolean) anterior.
Dispose(False)
MyBase.Finalize()
End Sub
' Visual Basic agregó este código para implementar correctamente el modelo descartable.
Public Sub Dispose() Implements IDisposable.Dispose
' No cambie este código. Coloque el código de limpieza en Dispose (ByVal que se dispone como Boolean).
Dispose(True)
GC.SuppressFinalize(Me)
End Sub
#End Region
Por último, veremos una interfaz muy relacionada con las colecciones. Estamos hablando de
Según MSDN, “IEnumerable expone un enumerador que admite una iteración simple en una colección no genérica”. No te preocupes por lo de “colección no genérica” porque también está disponible la interfaz IEnumerable(Of T) para colecciones genéricas, que además hereda de IEnumerable, así que los 2 códigos siguientes son válidos:
Dim gente As New List(Of Persona)
Dim antonio As New Persona("Antonio", 35)
gente.Add(antonio)
Dim dani As New Persona("Dani", 41)
gente.Add(dani)
Dim enumerador1 As IEnumerator = gente.GetEnumerator
While enumerador1.MoveNext
' enumerador1.Current es del tipo Object
Console.WriteLine(enumerador1.Current.ToString)
End While
Dim enumerador2 As IEnumerator(Of Persona) = gente.GetEnumerator
While enumerador2.MoveNext
' enumerador2.Current es del tipo Persona
Console.WriteLine(enumerador2.Current.Nombre)
End While
Como curiosidad, siempre que utilices For…Each en una colección, esa colección tendrá que haber implementado IEnumerable o IEnumerable(Of T)
IEnumerable sólo tiene un método que es GetEnumerator que devuelve un objeto de tipo IEnumerator, que a su vez sólo tiene 3 métodos: MoveNext, Current y Reset.
Si queremos poner un ejemplo práctico del uso de IEnumerable e IEnumerator, el más obvio es que fomenta el polimorfismo y podemos hacer funciones que iteren sobre colecciones sin conocer el tipo de esas colecciones. Aquí va el ejemplo:
Dim gente As New List(Of Persona)
gente.Add(New Persona("Antonio", 35))
gente.Add(New Persona("Dani", 41))
IterarSobreColeccion(gente)
Dim miArrayList As New ArrayList
miArrayList.Add(New Persona("Antonio", 35))
miArrayList.Add(New Persona("Dani", 41))
IterarSobreColeccion(miArrayList)
Dim miArray(1) As Persona
miArray(0) = New Persona("Antonio", 35)
miArray(1) = New Persona("Dani", 41)
IterarSobreColeccion(miArray)
Public Sub IterarSobreColeccion(ByVal coleccion As IEnumerable)
Dim enumerador As IEnumerator = coleccion.GetEnumerator
While enumerador.MoveNext
Console.WriteLine(enumerador.Current.ToString)
End While
End Sub
Seguro que echas en falta muchas interfaces que tú ya conoces, pero como yo estoy ahora mismo descubriéndolas… pues quizás en otro post ;-)
Un saludo!
No hay comentarios:
Publicar un comentario