jueves, 14 de julio de 2011

Colecciones genéricas en .NET

Como decía en el post Colecciones no genéricas en .NET, la razón de ser de las colecciones genéricas es establecer de forma inflexible el tipo de los elementos que gestiona la colección.

De este modo evitaremos errores en tiempo de ejecución por conversiones a un tipo erróneo, también lograremos optimizar nuestro código debido a que ya no será necesario el boxing y unboxing, y sobre todo, y para mí lo más importante, nuestro código será más sencillo de escribir, leer y entender (imagina que una colección de tipo Cliente sólo permite añadir elementos de tipo Cliente, que cuando recupero un elemento no tengo que hacer un casting al tipo Cliente, etc.)

Una cosa que hasta hace bien poco me tenía un poco loco, era la convención utilizada para trabajar con genéricos (porque te comento que cuando hablamos de genéricos hay vida más allá de las colecciones, y ahora en .NET, muchas cosas pueden ser genéricas…). Pues bien, tomando como ejemplos algunas de las colecciones genéricas que veremos en este mismo post, podemos llegar a las siguientes normas de nombrado:

  • T es un marcador que será reemplazado por un tipo concreto.
  • Key hace mención a una clave, y TKey hace mención a un parámetro clave (key) pero que igualmente será reemplazado por un tipo concreto.
  • Value hace mención a una valor, y TValue hace mención a un parámetro valor (value) pero que igualmente será reemplazado por un tipo concreto.

Sabiendo esto, ahora es más fácil, leer las siguientes firmas:

Public Class List(Of T)

Public Class Dictionary(Of TKey, TValue)

Si eres programador de Visual Basic .NET y no sabes o no te gusta C#, el equivalente de (Of T) es <T>.

Visual Basic .NET

C#

Public Class List(Of T)

public class List<T>

Public Class Dictionary(Of TKey, TValue)

public class Dictionary<TKey, TValue>


El espacio de nombres elegido para definir las colecciones genéricas es System.Collections.Generic.

En mi opinión, hay 2 clases que se llevan la palma en este espacio de nombres y son:

  • List(Of T)
  • Dictionary(Of TKey, TValue)

Además de estas, hay otras muchas:

  • HashSet(Of T)
  • LinkedList(Of T)
  • Queue(Of T)
  • SortedDictionary(Of TKey, TValue)
  • SortedList(Of TKey, TValue)
  • SortedSet(Of T)
  • Stack(Of T)

Algunas de estas clases de colección genérica son el equivalente a una colección no genérica (y perdón si no es cierto, pero conceptualmente a mí sí me lo parecen). También hay algunas clases que no tiene ninguna equivalencia (por ejemplo, LinkedList (Of T).

Colección genérica

Colección no genérica equivalente

List(Of T)

ArrayList

Dictionary(Of TKey, TValue)

Hashtable

SortedList(Of TKey, TValue)

SortedList

Queue(Of T)

Queue

Stack(Of T)

Stack


Debido a que este post sería eterno si hablará sobre cada una de las colecciones genéricas que existen, voy a centrarme en mis colecciones preferidas: List(Of T) y Dictionary(Of TKey, TValue), y así de paso, intento tener más criterio al hablar sobre esto ;-)

Si hablamos de Dictionary(Of TKey, TValue) lo primero que nos encontramos es que en las colecciones no genéricas, cada elemento se guardaba en un objeto del tipo DictionaryEntry. Sin embargo, en la colección genérica, el tipo utilizo para guardar un elemento es KeyValuePair(Of TKey, TValue).

Lógicamente, el tipo de TKey y TValue es el mismo que se utilizó durante la creación del objeto Dictionary(Of TKey, TValue). Veamos un ejemplo:

Primero, tenemos una clase persona que nos acompañará durante el resto de los ejemplos:

Public Class Persona

    Public Property Nombre As String

    Public Property Edad As Integer

End Class


Ahora creamos un diccionario para guardar personas y cuya clave serán sus apodos:

Dim gente As New Dictionary(Of String, Persona)

 

Ahora, cuando queremos añadir un objeto es el propio Visual Studio quien nos informa que el objeto suministrado en TKey tiene que ser del tipo String y que para TValue tiene que ser del tipo Persona.

clip_image001

Para finalizar con el ejemplo, añadiremos varias personas y después iteraremos sobre el diccionario:

        Dim gente As New Dictionary(Of String, Persona)

 

        gente.Add("panicoenlaxbox", New Persona With {.Nombre = "Sergio", .Edad = 35})

        gente.Add("ertanomacareno", New Persona With {.Nombre = "Antonio", .Edad = 35})

        gente.Add("elputoamo", New Persona With {.Nombre = "Dani", .Edad = 41})

 

        For Each colega As KeyValuePair(Of String, Persona) In gente

            ' colega.Value es del tipo Persona

            ' no es necesario ninguna operación de casting

            Console.WriteLine(colega.Value.Nombre)

        Next


Lo importante de este código es que es necesario iterar con un objeto del tipo KeyValuePair(Of String, Persona) y que el objeto devuelto estará fuertemente tipado, es decir, en su propiedad Key tendrá una String y en Value, devolverá directamente un objeto de tipo Persona.

clip_image002

Si hablamos ahora de List(Of T), te puede decir que debe ser la clase de colección genérica más utilizada, no sólo lo digo yo, parece que es un hábito muy extendido debido a su equilibrio entre rendimiento y facilidad de uso. Es una clase donde se pueden buscar elementos, ordenarlos, etc.

Lo que también debes tener en cuenta tanto para colecciones genéricas como no genéricas es que si trabajas con tipos propios, en el momento que quieras ordenar la colección (siempre que la colección lo permita) tendrás que implementar IComparable en el tipo o crear tu propia clase que implemente IComparer. Por otro lado, si lo que quieres es buscar tendrás que sobreescribir el método Equals. Para estas operaciones de comparación y búsqueda puedes leer este otro artículo llamado Interfaces must-know donde expongo ejemplos concretos.

Un saludo!

2 comentarios: