Cargando...

Colecciones en C#: Lista, Diccionario, Cola, Pila

Aprende colecciones en C# como List, Dictionary, Queue y Stack para gestionar datos de forma eficiente con ejemplos.

En C#, las colecciones se utilizan para almacenar múltiples valores de forma dinámica y procesarlos de manera eficiente. Los arreglos tienen un tamaño fijo, pero las colecciones pueden crecer o reducirse según sea necesario. Este artículo cubre List<T>, Dictionary<TKey,TValue> (y KeyValuePair<,>), SortedList<TKey,TValue>, Queue<T>, Stack<T>, HashSet<T> y LinkedList<T> con ejemplos.


List<T>

List<T> es una lista dinámica que almacena elementos del mismo tipo en orden. Ofrece muchos métodos para agregar/eliminar, buscar y ordenar.


using System;
using System.Collections.Generic;
using System.Linq;

var numeros = new List<int> { 5, 1, 9 };
numeros.Add(3);                       // agregar al final
numeros.Insert(1, 7);                 // insertar en un índice dado
numeros.Remove(9);                    // eliminar por valor (primera coincidencia)
numeros.RemoveAll(x => x % 2 == 1);   // eliminar todos los impares
numeros.AddRange(new[] { 10, 2, 8 });

numeros.Sort();                       // ordenar ascendente
numeros.Reverse();                    // invertir orden

Console.WriteLine(string.Join(", ", numeros));
Console.WriteLine(numeros.Contains(8)); // true

// Filtrado con LINQ
var pares = numeros.Where(n => n % 2 == 0).ToList();
Console.WriteLine(string.Join(", ", pares));

Dictionary<TKey,TValue> y KeyValuePair<TKey,TValue>

Dictionary almacena pares clave–valor. Las claves son únicas. Está basado en hash y permite búsquedas muy rápidas.


using System;
using System.Collections.Generic;

var precios = new Dictionary<string, decimal>(StringComparer.OrdinalIgnoreCase)
{
    ["Manzana"] = 5.75m,
    ["Pera"] = 7.25m
};

// Agregar o actualizar
precios["Banana"] = 12m;  // agrega si no existe, actualiza si ya existe

// Lectura segura
if (precios.TryGetValue("MANZANA", out var valor))
    Console.WriteLine($"Manzana: {valor} EUR");

// Verificar existencia de clave
if (!precios.ContainsKey("Cereza"))
    precios.Add("Cereza", 18.5m);

// Iteración con KeyValuePair
foreach (KeyValuePair<string, decimal> kv in precios)
    Console.WriteLine($"{kv.Key} → {kv.Value} EUR");

// Actualizar un valor
precios["Manzana"] = precios["Manzana"] * 1.10m; // +10% precio

Consejo: KeyValuePair<TKey,TValue> proporciona .Key y .Value al recorrer un diccionario. Para actualizar, use directamente dict[key].


SortedList<TKey,TValue>

SortedList mantiene las claves en orden ascendente y permite acceso por índice a través de Keys y Values. Combina el comportamiento de un diccionario y un arreglo.


using System;
using System.Collections.Generic;

var listaPrecios = new SortedList<string, decimal>(StringComparer.OrdinalIgnoreCase)
{
    ["Pera"] = 7.25m,
    ["Manzana"] = 5.75m,
    ["Banana"] = 12m,
    ["Cereza"] = 18.5m
};

// Claves ordenadas:
Console.WriteLine(string.Join(", ", listaPrecios.Keys)); // Banana, Cereza, Manzana, Pera

// Acceso por índice:
for (int i = 0; i < listaPrecios.Count; i++)
    Console.WriteLine($"#{i} {listaPrecios.Keys[i]} → {listaPrecios.Values[i]}");

// Comparador personalizado (orden descendente):
var descendente = new SortedList<string, decimal>(Comparer<string>.Create((a,b) => StringComparer.OrdinalIgnoreCase.Compare(b,a)));
descendente["Manzana"] = 5.75m;
descendente["Pera"] = 7.25m;
Console.WriteLine(string.Join(", ", descendente.Keys)); // orden inverso
  • SortedList mueve elementos en inserciones/eliminaciones para mantener el orden. Si hay muchas operaciones, use SortedDictionary.
  • Si necesita rangos basados en índice, SortedList es ventajosa.

Queue<T> (FIFO)

Queue funciona con el principio First In, First Out; útil para colas de tareas o flujos de trabajo.


using System;
using System.Collections.Generic;

var pedidos = new Queue<string>();
pedidos.Enqueue("Pedido#1001");
pedidos.Enqueue("Pedido#1002");
pedidos.Enqueue("Pedido#1003");

Console.WriteLine(pedidos.Peek());    // siguiente sin quitar: Pedido#1001
Console.WriteLine(pedidos.Dequeue()); // elimina: Pedido#1001
Console.WriteLine(pedidos.Count);     // 2

Stack<T> (LIFO)

Stack funciona con el principio Last In, First Out; usado en deshacer (undo), historial, etc.


using System;
using System.Collections.Generic;

var pasos = new Stack<string>();
pasos.Push("Paso-1");
pasos.Push("Paso-2");
pasos.Push("Paso-3");

Console.WriteLine(pasos.Peek()); // Paso-3
Console.WriteLine(pasos.Pop());  // Paso-3 (eliminado)
Console.WriteLine(pasos.Pop());  // Paso-2

HashSet<T>

HashSet almacena una colección de elementos únicos. Los duplicados se ignoran. Ideal para operaciones de conjuntos (unión, intersección, diferencia).


using System;
using System.Collections.Generic;

var a = new HashSet<string>(StringComparer.OrdinalIgnoreCase) { "manzana", "pera", "banana" };
var b = new HashSet<string> { "Banana", "cereza" };

var union = new HashSet<string>(a);
union.UnionWith(b);           // A ∪ B

var interseccion = new HashSet<string>(a);
interseccion.IntersectWith(b); // A ∩ B

var diferencia = new HashSet<string>(a);
diferencia.ExceptWith(b);     // A \ B

Console.WriteLine(string.Join(", ", union));        // manzana, pera, banana, cereza
Console.WriteLine(string.Join(", ", interseccion)); // banana
Console.WriteLine(string.Join(", ", diferencia));   // manzana, pera
Console.WriteLine(a.Add("Manzana"));                // false (ya existe, insensible a mayúsculas/minúsculas)

Consejo: Las pruebas de pertenencia (Contains) y las inserciones son normalmente O(1) amortizado en HashSet; excelente para filtrar duplicados en grandes volúmenes de datos.


LinkedList<T>

LinkedList es una lista doblemente enlazada. Insertar/eliminar en nodos conocidos cuesta O(1). No permite acceso aleatorio por índice; hay que recorrerla.


using System;
using System.Collections.Generic;

var lista = new LinkedList<string>();
var n1 = lista.AddFirst("Inicio");
var n2 = lista.AddLast("Fin");

lista.AddAfter(n1, "Medio-1");   // Inicio <-> Medio-1 <-> Fin
lista.AddBefore(n2, "Medio-2");  // Inicio <-> Medio-1 <-> Medio-2 <-> Fin

// Eliminar un nodo
var medio1 = n1.Next;
lista.Remove(medio1);

// Recorrer
for (var n = lista.First; n != null; n = n.Next)
    Console.WriteLine(n.Value);   // Inicio, Medio-2, Fin
  • Rápido para operaciones en cabeza/cola y para insertar/eliminar alrededor de un nodo conocido.
  • No apto para acceso aleatorio por índice; en ese caso usar List<T>.

Aplicación de ejemplo: Flujo de procesamiento de pedidos

En el siguiente escenario, se combinan diferentes colecciones: Queue para pedidos pendientes, Dictionary para stock y precios, HashSet para clientes únicos, Stack para deshacer (undo), SortedList para la lista de precios y LinkedList para “productos vistos recientemente”.


using System;
using System.Collections.Generic;

var pendientes = new Queue<string>(new[] { "ORD-1001", "ORD-1002" });
var stock = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase)
{
    ["Manzana"] = 100, ["Pera"] = 50, ["Banana"] = 30
};
var precios = new SortedList<string, decimal> { ["Pera"] = 7.25m, ["Manzana"] = 5.75m, ["Banana"] = 12m };
var clientes = new HashSet<string>();      // IDs únicos de clientes
var undo = new Stack<Action>();             // pila de deshacer
var recientes = new LinkedList<string>();   // productos vistos recientemente

void AgregarAlCarrito(string producto, int cantidad)
{
    if (stock.TryGetValue(producto, out var s) && s >= cantidad)
    {
        stock[producto] = s - cantidad;
        undo.Push(() => stock[producto] += cantidad); // acción de deshacer
        recientes.AddFirst(producto);
        Console.WriteLine($"{cantidad} x {producto} agregado. Restante: {stock[producto]}");
    }
    else
    {
        Console.WriteLine($"Stock insuficiente: {producto}");
    }
}

void ProcesarPedido(string noPedido, string clienteId)
{
    Console.WriteLine($"Procesando: {noPedido}");
    clientes.Add(clienteId);
    AgregarAlCarrito("Manzana", 3);
    AgregarAlCarrito("Pera", 2);
}

while (pendientes.Count > 0)
{
    var pedido = pendientes.Dequeue();
    ProcesarPedido(pedido, "CLI-001");
}

Console.WriteLine($"Clientes únicos: {clientes.Count}");
Console.WriteLine($"Lista de precios (ordenada): {string.Join(", ", precios.Keys)}");

// Deshacer si es necesario:
if (undo.Count > 0)
{
    var accion = undo.Pop();
    accion();
}

Console.WriteLine("Productos vistos recientemente (Top 3):");
int k = 0;
for (var n = recientes.First; n != null && k < 3; n = n.Next, k++)
    Console.WriteLine($"- {n.Value}");

Resumen (TL;DR)

  • List<T>: Lista dinámica, basada en índice. Ideal para acceso aleatorio.
  • Dictionary<TKey,TValue>: Pares clave–valor, búsqueda rápida. Iterar con KeyValuePair.
  • SortedList<TKey,TValue>: Claves ordenadas + acceso por índice. Buena para consultas por rango.
  • Queue<T> (FIFO): Flujos de trabajo/colas.
  • Stack<T> (LIFO): Deshacer, historial.
  • HashSet<T>: Elementos únicos; operaciones de conjuntos (∪, ∩, \).
  • LinkedList<T>: Lista enlazada; inserción/eliminación rápida en cabeza/cola o alrededor de un nodo conocido.

Guía: Acceso aleatorio → List, búsqueda rápida por clave → Dictionary, claves ordenadas + índice → SortedList, unicidad → HashSet, FIFO → Queue, LIFO → Stack, operaciones rápidas en cabeza/cola → LinkedList.


Artículos relacionados